💎 Módulo 00: Requisitos, Casos de Uso e Modelagem

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.


📋 Levantamento de Requisitos

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.

Requisitos Funcionais (RF)

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)

Requisitos Não Funcionais (RNF)

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.


🎭 Diagrama de Casos de Uso

_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 de hasRole('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).


🏛️ Diagrama de Classes

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 RelatorioService não tem um RelatorioRepository? RF12 não introduz uma tabela nova — os relatórios são agregações sobre dados que já existem em movimentacao_estoque e produto. RelatorioServiceImpl calcula os totais em memória a partir do que MovimentacaoEstoqueRepository/ProdutoRepository já expõem, o mesmo padrão N+1-consciente que ProdutoServiceImpl.findAll() já usava em _01 para evitar uma query por item.


🗺️ MER (Modelo Entidade-Relacionamento)

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 HISTORICO separada? RF10/RNF05 pedem “registrar histórico das movimentações” — mas o histórico é a própria tabela MOVIMENTACAO_ESTOQUE ordenada por data_hora. Criar uma tabela HISTORICO seria duplicar dado: cada linha de MOVIMENTACAO_ESTOQUE já É 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.


🔁 Diagrama de Sequência — Registrar Saída (o caso mais 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.quantidade E salvar em MovimentacaoEstoque na 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 @Transactional do Spring garante que ambas sejam confirmadas juntas (ou revertidas juntas) — é o mesmo princípio de atomicidade (o “A” do ACID) que já aparece em _01 nos métodos anotados com @Transactional.


Como retomar (para quem for implementar)

  1. Copiar gestaodeestoques_01/gestaodeestoques/ para gestaodeestoques_02/gestaodeestoques/ como ponto de partida.
  2. Módulo 01: nova modelagem — schema.sql, Produto.estoqueMinimo, MovimentacaoEstoque, TipoMovimentacao, repositórios.
  3. Módulo 02: regras de negócio — MovimentacaoEstoqueService, validação de saldo, EstoqueInsuficienteException, papel ROLE_ESTOQUISTA.
  4. Módulo 03: API REST — controllers, busca de produtos, alerta de estoque mínimo.
  5. Módulo 04: relatórios (RF12) e frontend (novas telas da SPA).