Este projeto evolui o Gestão de Estoques 01 — não recomeça do zero. _01 já implementa o CRUD de produtos/categorias/fornecedores, autenticação JWT e controle de acesso por papel (ADMIN/USER). Este módulo cobre os 5 artefatos que faltavam levantar antes de escrever qualquer código novo: Requisitos → Casos de Uso → Diagrama de Classes → MER → Diagrama de Sequência — a mesma metodologia usada em _01/modulo00.md e _01/modulo01.md, agora aplicada ao que falta.
O sistema “Gestão de Estoques” original (RF01-RF13/RNF01-RNF06, levantado com o cliente) tinha um núcleo já implementado em _01 e um restante que fica para esta versão.
| ID | Requisito | Onde está |
|---|---|---|
| RF01 | Cadastrar produtos | ✅ Já em _01 |
| RF02 | Alterar e excluir produtos | ✅ Já em _01 |
| RF03 | Cadastrar categorias | ✅ Já em _01 |
| RF04 | Cadastrar fornecedores | ✅ Já em _01 |
| RF05 | Registrar entradas de produtos | 🆕 Novo em _02 |
| RF06 | Registrar saídas de produtos | 🆕 Novo em _02 |
| RF07 | Atualizar automaticamente a quantidade em estoque | 🆕 Novo em _02 (consequência de RF05/06) |
| RF08 | Consultar o estoque atual | ✅ Já em _01 (listagem de produtos) |
| RF09 | Pesquisar produtos | 🆕 Novo em _02 |
| RF10 | Registrar histórico das movimentações | 🆕 Novo em _02 |
| RF11 | Alertar quando um produto atingir o estoque mínimo | 🆕 Novo em _02 |
| RF12 | Gerar relatórios de estoque | 🆕 Novo em _02 |
| RF13 | Controlar usuários e seus níveis de acesso | ✅ Já em _01 (ganha um papel novo aqui: ROLE_ESTOQUISTA) |
| ID | Requisito | Onde está |
|---|---|---|
| RNF01-04, RNF06 | Autenticação, controle de acesso por perfil, senha criptografada, interface web | ✅ Já em _01 |
| RNF05 | Registro das movimentações realizadas | 🆕 Novo em _02 (mesmo gap de RF10) |
Conclusão do gap: o núcleo que falta é movimentação de estoque (RF05-07/RF10/RNF05) + estoque mínimo/alerta (RF11) + busca (RF09) + relatórios (RF12). Todo o resto (RF01-04/RF08/RF13, RNF01-04/RNF06) é reaproveitado do código de _01, copiado como ponto de partida.
_01 tinha 2 atores (Usuário e Administrador). Esta versão adiciona um terceiro: o Estoquista — o profissional que efetivamente movimenta o estoque no dia a dia, sem precisar dos poderes administrativos completos (gerenciar usuários, por exemplo).
flowchart LR
Usuario(["👤 Usuário"])
Estoquista(["📦 Estoquista"])
Admin(["🛡️ Administrador"])
subgraph SISTEMA["Sistema de Gestão de Estoques"]
UC1(["Fazer Login"])
UC2(["Consultar Produtos"])
UC3(["Pesquisar Produtos"])
UC4(["Consultar Alerta de Estoque Mínimo"])
UC5(["Registrar Entrada"])
UC6(["Registrar Saída"])
UC7(["Consultar Histórico de Movimentações"])
UC8(["Gerar Relatórios"])
UC9(["Gerenciar Produtos/Categorias/Fornecedores"])
UC10(["Gerenciar Usuários"])
end
Usuario --> UC1
Usuario --> UC2
Usuario --> UC3
Usuario --> UC4
Estoquista --> UC1
Estoquista --> UC2
Estoquista --> UC3
Estoquista --> UC4
Estoquista --> UC5
Estoquista --> UC6
Estoquista --> UC7
Admin --> UC1
Admin --> UC5
Admin --> UC6
Admin --> UC7
Admin --> UC8
Admin --> UC9
Admin --> UC10
UC5 -.->|inclui| UC7
UC6 -.->|inclui| UC7
💡 Por que o Administrador também registra movimentações? No RBAC deste projeto,
ROLE_ADMINé um superconjunto de permissões — o admin não perde a capacidade de fazer o que o Estoquista faz, apenas o Estoquista não ganha o que só o Admin faz (gerenciar usuários, por exemplo). No código isso vira@PreAuthorize("hasAnyRole('ADMIN', 'ESTOQUISTA')")no endpoint de registrar movimentação, em vez dehasRole('ESTOQUISTA')sozinho.
💡 Por que RF08 (Gerar Relatórios) não aparece para o Estoquista? É uma decisão de escopo: relatórios gerenciais (RF12) são uma visão agregada para tomada de decisão, tipicamente papel de quem gerencia o estoque, não de quem só o movimenta no dia a dia. O Estoquista já vê o essencial pelo alerta de estoque mínimo (UC4) e pelo histórico (UC7).
Estende o diagrama de _01/modulo01.md com as classes novas (MovimentacaoEstoque, TipoMovimentacao) e o campo novo em Produto.
classDiagram
class Produto {
-Integer id
-String nome
-int quantidade
-BigDecimal preco
-int estoqueMinimo
-Integer categoriaId
-Integer fornecedorId
}
class TipoMovimentacao {
<<enumeration>>
ENTRADA
SAIDA
}
class MovimentacaoEstoque {
-Long id
-Integer produtoId
-TipoMovimentacao tipo
-int quantidade
-LocalDateTime dataHora
-Long usuarioId
}
class MovimentacaoEstoqueRepository {
<<interface>>
+findByProdutoIdOrderByDataHoraDesc(Integer) List~MovimentacaoEstoque~
+findAllOrderByDataHoraDesc() List~MovimentacaoEstoque~
}
class MovimentacaoEstoqueService {
<<interface>>
+registrar(MovimentacaoEstoqueFormDTO) MovimentacaoEstoqueDTO
+findAll() List~MovimentacaoEstoqueDTO~
+findByProdutoId(Integer) List~MovimentacaoEstoqueDTO~
}
class RelatorioService {
<<interface>>
+gerarRelatorioMovimentacoes(LocalDate, LocalDate) RelatorioMovimentacoesDTO
+gerarRelatorioEstoqueBaixo() List~ProdutoDTO~
}
Produto "1" --> "0..*" MovimentacaoEstoque : sofre
MovimentacaoEstoque --> TipoMovimentacao : tem
MovimentacaoEstoqueRepository ..> MovimentacaoEstoque : gerencia
MovimentacaoEstoqueService ..> MovimentacaoEstoqueRepository : usa
RelatorioService ..> MovimentacaoEstoqueRepository : agrega
💡 Por que
RelatorioServicenão tem umRelatorioRepository? RF12 não introduz uma tabela nova — os relatórios são agregações sobre dados que já existem emmovimentacao_estoqueeproduto.RelatorioServiceImplcalcula os totais em memória a partir do queMovimentacaoEstoqueRepository/ProdutoRepositoryjá expõem, o mesmo padrão N+1-consciente queProdutoServiceImpl.findAll()já usava em_01para evitar uma query por item.
Estende o erDiagram de _01/modulo01.md com a tabela nova MOVIMENTACAO_ESTOQUE e o campo novo estoque_minimo em PRODUTO.
erDiagram
CATEGORIA {
int id PK
varchar nome
}
FORNECEDOR {
int id PK
varchar nome
varchar cnpj
}
PRODUTO {
int id PK
varchar nome
int quantidade
decimal preco
int estoque_minimo
int categoria_id FK
int fornecedor_id FK
}
USUARIO {
bigint id PK
varchar login
varchar senha
boolean ativo
}
PAPEL {
bigint id PK
varchar nome
}
USUARIO_PAPEL {
bigint usuario_id PK_FK
bigint papel_id PK_FK
}
MOVIMENTACAO_ESTOQUE {
bigint id PK
int produto_id FK
varchar tipo
int quantidade
timestamp data_hora
bigint usuario_id FK
}
CATEGORIA ||--o{ PRODUTO : "classifica"
FORNECEDOR ||--o{ PRODUTO : "fornece"
USUARIO ||--o{ USUARIO_PAPEL : "possui"
PAPEL ||--o{ USUARIO_PAPEL : "concedido a"
PRODUTO ||--o{ MOVIMENTACAO_ESTOQUE : "sofre"
USUARIO ||--o{ MOVIMENTACAO_ESTOQUE : "registra"
💡 Por que não existe uma tabela
HISTORICOseparada? RF10/RNF05 pedem “registrar histórico das movimentações” — mas o histórico é a própria tabelaMOVIMENTACAO_ESTOQUEordenada pordata_hora. Criar uma tabelaHISTORICOseria duplicar dado: cada linha deMOVIMENTACAO_ESTOQUEjá É um evento histórico imutável (nunca é alterada ou apagada, só inserida). Essa é a mesma decisão de design que sistemas financeiros usam para livros-razão (ledgers): o evento em si é o histórico.
Diferente de “Registrar Entrada” (que só soma), “Registrar Saída” precisa validar saldo suficiente antes de confirmar — é o fluxo que justifica a transação e a exceção de negócio nova (EstoqueInsuficienteException).
sequenceDiagram
participant SPA as 🖥️ router.js
participant Ctrl as MovimentacaoEstoqueController
participant Svc as MovimentacaoEstoqueServiceImpl
participant ProdRepo as ProdutoRepository
participant MovRepo as MovimentacaoEstoqueRepository
SPA->>Ctrl: POST /api/movimentacoes {produtoId, tipo: SAIDA, quantidade}
Ctrl->>Svc: registrar(formDTO)
Svc->>ProdRepo: findById(produtoId)
ProdRepo-->>Svc: Produto (quantidade atual)
alt quantidade solicitada > quantidade em estoque
Svc-->>Ctrl: throw EstoqueInsuficienteException
Ctrl-->>SPA: 400 Bad Request {"error": "Estoque Insuficiente"}
else saldo suficiente
Note over Svc: @Transactional — as duas escritas abaixo<br/>ocorrem juntas ou nenhuma ocorre
Svc->>ProdRepo: save(produtoComQuantidadeAtualizada)
ProdRepo-->>Svc: Produto atualizado
Svc->>MovRepo: save(novaMovimentacao SAIDA)
MovRepo-->>Svc: MovimentacaoEstoque salva
Svc-->>Ctrl: MovimentacaoEstoqueDTO
Ctrl-->>SPA: 201 Created
end
💡 Por que atualizar
Produto.quantidadeE salvar emMovimentacaoEstoquena mesma transação? Se só uma das duas escritas fosse persistida (por uma falha no meio do caminho), o sistema ficaria inconsistente: ou a quantidade do produto não reflete o histórico, ou existe uma movimentação registrada que nunca “aconteceu” de verdade. O@Transactionaldo Spring garante que ambas sejam confirmadas juntas (ou revertidas juntas) — é o mesmo princípio de atomicidade (o “A” do ACID) que já aparece em_01nos métodos anotados com@Transactional.
gestaodeestoques_01/gestaodeestoques/ para gestaodeestoques_02/gestaodeestoques/ como ponto de partida.schema.sql, Produto.estoqueMinimo, MovimentacaoEstoque, TipoMovimentacao, repositórios.MovimentacaoEstoqueService, validação de saldo, EstoqueInsuficienteException, papel ROLE_ESTOQUISTA.