# Inventário WMS — Conferência Configurável — Spec

> Extensão das operações `inventario_produtos` e `inventario_endereco` dentro do
> sinal **AlocacaoWms**. Não altera `alocacao`, `movimentacao` nem as demais
> operações. A infraestrutura de missão, adapter e persistência em `wms_movimentos`
> já existe; esta spec adiciona a camada de **reconciliação de contagens múltiplas**.

## Contexto e problema

Inventário físico de armazém exige confiança no resultado. Uma única contagem
carrega erro humano inevitável — o operador lê errado, distrai-se, ou conta um
endereço errado. O padrão do setor é a **conferência**: uma segunda passagem
(pelo mesmo operador ou por outro) que confirma ou contradiz a primeira. Quando
divergem, uma terceira desempata.

Hoje o Genesis registra cada contagem isoladamente sem cruzar rodadas: não há
conceito de "primeira contagem vs. segunda conferência vs. resultado reconciliado".
A feature preenche essa lacuna de forma flexível, suportando de 1 a N rodadas e
controle opcional de operadores distintos por rodada.

## Decisões de produto (fechadas)

- A configuração fica em `missao_logistica.configuracoes` (criada na designação),
  igual a `operacao_tipo` e `modo_alocacao`. Sem tabela nova.
- Cada rodada de contagem é uma **missão independente** vinculada por
  `missao_pai_id`. Isso reutiliza toda a infraestrutura existente (designação,
  execução, conclusão, integração). Não existe uma tela de "reconciliação" separada
  — o processo é a sequência das missões-filhas.
- O resultado final aceito é a **moda** das rodadas (maioria simples). Em caso de
  empate (ex: 2 rodadas, valores diferentes), o sistema não aceita e abre nova
  rodada automaticamente até que haja maioria.
- O push ERP/WMS é disparado **apenas quando o resultado é reconciliado** (todas as
  rodadas chegaram a um acordo), não a cada contagem individual. Até lá, os
  movimentos ficam com `status = 'rascunho'` (novo valor do enum de status em
  `wms_movimentos`).
- A configuração `operadores_distintos = true` é garantida pelo sistema: ao gerar a
  missão-filha de conferência, o candidato a operador não pode ser o mesmo
  `operador_id` de nenhuma rodada anterior do mesmo lote.
- Com `contagens_minimas = 1` (default), o comportamento é idêntico ao atual: push
  imediato, `status = 'confirmada'`. Zero regressão para missões sem configuração.

## Escopo

- ✅ Faz parte:
  - Dois novos campos em `configuracoes` da missão de inventário:
    `contagens_minimas` (int, default 1) e `operadores_distintos` (bool, default
    false).
  - Novo valor `'rascunho'` no enum de `status` de `wms_movimentos`.
  - Lógica de **reconciliação** pós-conclusão de cada missão-filha: agrega as
    rodadas do lote por `(produto_id, localizacao_codigo)`, computa a moda e decide
    se aceita (confirma + push) ou abre nova rodada.
  - **Geração automática de missão-filha de conferência** ao concluir a missão pai
    (ou missão-filha anterior), quando o lote ainda não está reconciliado.
  - Controle de `operadores_distintos`: a missão-filha não pode ser aceita/executada
    pelo mesmo operador de uma rodada anterior.
  - Interface na **tela de início** da missão-filha indicando claramente que é uma
    conferência ("Rodada 2 de 3 — confirme os valores abaixo"), listando as
    contagens já registradas para o operador ver sem influenciar (read-only do lote).
  - Relatório de divergências: ao reconciliar, se houve rodadas com valores
    diferentes, gerar um registro de auditoria (`inventario_divergencias`,
    nova tabela simples) com produto, endereço, valores por rodada e resultado
    final aceito.
  - Seletor dos dois novos campos na tela de designação (Seção 3 — Configurações),
    visível apenas para operações de inventário (`inventario_produtos` /
    `inventario_endereco`). Default 1 / false.
- ❌ Fora de escopo (YAGNI):
  - Algoritmos de reconciliação alternativos (média, mediana, máximo). Apenas moda.
  - Configuração de `contagens_minimas` por SKU ou por endereço (só por missão).
  - Notificação push/email ao operador quando a missão-filha for criada.
  - Cancelamento parcial de uma rodada (toda a missão-filha é cancelada ou
    concluída).
  - Integração com WMS externo em modo pull (leitura de estoque externo para
    cruzar com a contagem interna).
  - Dashboard de acurácia de inventário (fica para relatórios).

## Glossário

| Termo | Significado nesta spec |
|---|---|
| **Lote de inventário** | Conjunto de movimentos gerados pelas rodadas de contagem de uma missão pai e suas filhas, identificado por `missao_pai_id` (ou pelo próprio id quando é a missão raiz) |
| **Rodada** | Uma missão de inventário (pai ou filha) que conta o mesmo escopo |
| **Reconciliado** | Estado em que todas as rodadas obrigatórias terminaram e a moda foi aceita |
| **Rascunho** | Movimento gravado em uma rodada ainda não reconciliada |

---

## User stories

### US1 — Gestor configura o nível de conferência na designação

**Como** gestor de armazém **eu quero** definir quantas contagens são necessárias e
se precisam ser de operadores diferentes **para** calibrar o nível de rigor do
inventário de acordo com o produto ou a criticidade da operação.

**Critérios de aceite:**

- AC1.1: Na Seção 3 (Configurações) da designação de missão de Operação WMS, quando
  a operação escolhida for `inventario_produtos` ou `inventario_endereco`, aparecem
  dois controles adicionais:
  - **Contagens mínimas**: seletor numérico 1–5 (default: 1).
  - **Operadores distintos**: toggle (default: desligado). Só habilitável quando
    Contagens mínimas ≥ 2.
- AC1.2: Com Contagens mínimas = 1, o toggle de operadores distintos fica desabilitado
  e o comportamento é idêntico ao atual (sem conferência).
- AC1.3: Os valores escolhidos são persistidos em
  `missao_logistica.configuracoes.contagens_minimas` e
  `missao_logistica.configuracoes.operadores_distintos`.
- AC1.4: Missões criadas sem os campos (legado) assumem `contagens_minimas = 1` e
  `operadores_distintos = false` — comportamento atual preservado.

---

### US2 — Operador executa a primeira contagem (rodada 1)

**Como** operador de inventário **eu quero** contar os produtos normalmente **para**
registrar a primeira rodada sem mudança no meu fluxo habitual.

**Critérios de aceite:**

- AC2.1: O fluxo de execução da primeira rodada é idêntico ao atual (`inventario_produtos`
  ou `inventario_endereco`): bipar, informar quantidade, confirmar.
- AC2.2: Cada contagem é gravada em `wms_movimentos` com `status = 'rascunho'`
  quando `contagens_minimas > 1`; com `status = 'confirmada'` quando
  `contagens_minimas = 1` (comportamento atual).
- AC2.3: A conclusão da missão (botão "Concluir") está disponível normalmente; ao
  acionar, o sistema avalia se o lote está reconciliado.
- AC2.4: Com `contagens_minimas > 1`, ao concluir a missão pai, o sistema gera
  automaticamente a missão-filha de conferência (rodada 2) e informa ao operador:
  _"Contagem registrada. Missão de conferência criada para validação."_
- AC2.5: A missão pai passa para `status = 'concluida'`; a missão-filha nasce em
  `status = 'disponivel'` (ou designada, se `operadores_distintos = false` e há
  apenas um operador na equipe).

---

### US3 — Operador executa a conferência (rodada 2+)

**Como** operador de conferência **eu quero** ver as contagens da rodada anterior e
registrar minha própria contagem **para** validar ou contradizer os valores sem ser
influenciado antes de confirmar.

**Critérios de aceite:**

- AC3.1: A tela de início da missão-filha exibe claramente:
  _"Conferência — Rodada 2 de 3"_ (ou o número correspondente).
- AC3.2: Durante a execução, o operador **não vê** os valores das rodadas anteriores
  enquanto está contando — só vê após confirmar cada item, para não ancorar a
  contagem.
- AC3.3: Após confirmar cada item, a tela mostra um indicador discreto:
  _"✓ Valor registrado"_ ou _"⚠ Divergência detectada"_ (sem revelar o valor da
  outra rodada até o fechamento).
- AC3.4: Com `operadores_distintos = true`, o sistema impede que o operador que
  executou qualquer rodada anterior do mesmo lote inicie esta missão-filha. A tela
  exibe: _"Esta conferência deve ser feita por outro operador."_
- AC3.5: Com `operadores_distintos = false`, qualquer operador habilitado (incluindo
  o mesmo da rodada anterior) pode executar.

---

### US4 — Sistema reconcilia o resultado ao término de todas as rodadas

**Como** gestor **eu quero** que o sistema decida automaticamente o valor aceito ao
término das rodadas obrigatórias **para** não depender de intervenção manual no caso
feliz (todos concordam).

**Critérios de aceite:**

- AC4.1: Ao concluir a última rodada obrigatória (rodada N), o sistema agrupa os
  movimentos do lote por `(produto_id, localizacao_codigo)` e calcula a **moda** das
  quantidades contadas.
- AC4.2: Se há **maioria clara** (≥ ⌈N/2⌉+1 rodadas com o mesmo valor), o resultado
  é aceito: os movimentos rascunho da moda são promovidos para `status = 'confirmada'`
  e o push ERP/WMS é disparado para eles; os movimentos das rodadas minoritárias
  ficam `status = 'cancelada'`.
- AC4.3: Se **não há maioria** (empate — ex: rodada 1 contou 10, rodada 2 contou
  12, sem rodada 3), o sistema abre **automaticamente mais uma rodada** (missão-filha
  adicional) e informa ao gestor: _"Divergência detectada — rodada adicional criada."_
- AC4.4: Quando há apenas 2 rodadas e os valores diferem (empate impossível de
  resolver), a rodada extra (3ª) é criada automaticamente até que haja maioria.
  Não existe teto de rodadas automáticas (gestor pode cancelar a missão se quiser
  encerrar).
- AC4.5: Após reconciliação com sucesso, os movimentos rascunho descartados são
  registrados na tabela `inventario_divergencias` para auditoria (produto,
  endereço, valor de cada rodada, resultado aceito, timestamp).
- AC4.6: Com `contagens_minimas = 1`, nada disso é executado; push ocorre
  diretamente na confirmação (comportamento atual, zero regressão).

---

### US5 — Gestor acompanha e intervém em lotes com divergência

**Como** gestor **eu quero** ver quais inventários estão com divergência aberta e,
se necessário, forçar um valor **para** não deixar o lote em aberto indefinidamente.

**Critérios de aceite:**

- AC5.1: Na tela de gerenciamento de missões (`MissoesAprovacao` / `MissoesStatus`),
  missões-filhas de conferência aparecem agrupadas com a missão pai (mesmo visual
  que missões vinculadas já existentes no sistema).
- AC5.2: Um lote com rodada extra aberta por divergência exibe o badge _"Divergência"_
  na missão pai.
- AC5.3: O gestor pode, a qualquer momento, cancelar o lote inteiro (cancela a
  missão pai e todas as filhas abertas) sem afetar outros lotes.
- AC5.4: _(Fase 2, fora deste escopo)_ Interface para o gestor aceitar manualmente
  um valor divergente sem nova rodada.

---

## Regras de negócio

| # | Regra |
|---|---|
| RN1 | `contagens_minimas` mínimo = 1. Valor 0 ou negativo é rejeitado na criação. |
| RN2 | `operadores_distintos = true` só é válido quando `contagens_minimas ≥ 2`. |
| RN3 | Movimentos de rodadas não-reconciliadas têm `status = 'rascunho'` e **não entram** no push ERP/WMS nem no cálculo de saldo local. |
| RN4 | `missao_pai_id` da missão-filha aponta para a missão raiz do lote (não para a filha anterior), simplificando a query de agrupamento. |
| RN5 | A reconciliação é por par `(produto_id, localizacao_codigo)`; um par pode estar reconciliado enquanto outro ainda está em disputa no mesmo lote. O push dispara apenas para os pares reconciliados. |
| RN6 | Se um produto/endereço foi contado em alguma rodada mas **não** em outra (operador esqueceu), o valor ausente é tratado como **não contado** (não como zero) e a rodada faltante entra como diferente — forçando nova rodada ou exigindo intervenção. |
| RN7 | Missões com `contagens_minimas = 1` não criam `missao_pai_id` em nenhum movimento e nunca geram filhas. |

---

## Fora do escopo — explicitado

- Reconciliação manual pelo gestor (AC5.4 adiado explicitamente).
- Notificações (push/SMS/email) ao atribuir missão-filha.
- Configuração de reconciliação por SKU ou por categoria de produto.
- Relatório de acurácia de inventário por período (usa `inventario_divergencias`, mas a tela de relatório fica para outra spec).
- Suporte a `contagens_minimas` em `alocacao` ou `movimentacao` (semanticamente não faz sentido: essas operações são transacionais, não contagens).
