# Alocação WMS — Busca manual de produto + Ocorrência "sem código de barras" — Spec

> Extensão da missão **Alocação WMS** (`operacao_tipo = alocacao`). Não altera as
> demais operações (`inventario_*`, etc.). Spec focada; o desenho geral do
> componente WMS está em `01-spec.md` / `02-techplan.md`.

## Contexto e problema
Na execução da Alocação WMS o operador identifica o produto **bipando o código de
barras**. Dois cenários reais quebram esse fluxo:

1. **Produto sem código de barras legível/cadastrado.** Sem código, o operador não
   consegue identificar o produto pela bipagem e fica travado — precisa de uma
   **busca manual por descrição** para localizar e endereçar o item mesmo assim.
2. **Produto que não possui código de barras no cadastro.** Quando o operador
   percebe que o produto não tem código, ele precisa **registrar uma ocorrência**
   ("esse produto está sem código de barras") que, **ao concluir a missão de
   alocação**, gera automaticamente uma **missão de Ajuste de Dados** voltada a
   atribuir o código de barras desses produtos posteriormente.

Hoje a infraestrutura para o item 2 **já existe parcialmente**: o sinal
`SugestoesCodigoBarras` (tabela `missao_codigo_sugestao`) registra sugestões de
código por produto dentro de uma missão e, na conclusão de Separação/Carregamento,
gera **uma** missão `AJUSTE_DADOS` consolidando as pendências. O `Validator` desse
sinal **já libera missões `ALOCACAO_WMS`**. Faltam: (a) a busca manual; (b) suportar
a **ausência de código** (hoje o fluxo exige um código sugerido ≥6 chars); (c)
**disparar** a geração do ajuste quando uma missão `ALOCACAO_WMS` é concluída (o
hook hoje só cobre Separação/Carregamento).

## Decisões de produto (fechadas)
- **Reusar** o pipeline `SugestoesCodigoBarras` (não criar facilidade nova de
  ocorrências). Por enquanto, ocorrência = "sem código de barras".
- O operador **apenas sinaliza a ausência** — não digita nenhum código. O código é
  atribuído **depois**, na missão de Ajuste de Dados.
- A missão `AJUSTE_DADOS` é gerada **ao concluir a missão de alocação** (batched,
  idempotente), reusando o hook de finalização existente.

## Escopo
- ✅ Faz parte:
  - **Busca manual de produto por descrição** (e `codigo_produto`) na execução da
    Alocação WMS, quando não há código de barras para bipar.
  - **Ocorrência "sem código de barras"**: o operador sinaliza, durante a alocação,
    que um produto está sem código. Registro idempotente por (missão, produto).
  - Cancelar/desfazer a ocorrência de um produto na mesma missão.
  - **Geração automática** de **uma** missão `AJUSTE_DADOS` (campo solicitado =
    `codigo_barras`) ao concluir a missão de alocação, consolidando as ocorrências
    pendentes da origem. Idempotente (não duplica em re-finalização).
  - A missão `AJUSTE_DADOS` gerada nasce como **trabalho executável** (itens
    pendentes), pois o código será **atribuído** durante o ajuste.
- ❌ Fora de escopo (YAGNI):
  - Facilidade genérica de ocorrências (outros tipos além de código de barras).
  - Ocorrências para outros campos de cadastro (peso, cubagem, endereçamento…).
  - Misturar, na mesma origem, ocorrências `ausencia` e sugestões `sugestao` com
    código (a Alocação WMS só produz `ausencia`; Separação/Carregamento seguem
    produzindo `sugestao`, sem mudança de comportamento).
  - Atribuir/gerar o código de barras automaticamente.

## Subtlety central (decisão técnica de produto)
O pipeline atual (`CriarAjusteDadosDeSugestoesLaboratorio`) cria a missão
`AJUSTE_DADOS` **já em `AGUARDANDO_APROVACAO`, com itens CONCLUÍDOS** e
`dados_coletados.codigo_barras = codigo_sugerido`; a **aprovação apenas grava** esse
valor em `produtos.codigo_barras`. Esse modelo **pressupõe que existe um valor a
aplicar**.

Como a ocorrência é de **ausência** (não há código), a missão gerada **não pode**
ser desse tipo "já pronto, só aprovar". Ela deve nascer como **missão de trabalho
real**: itens **pendentes**, `configuracoes.campos_solicitados = ['codigo_barras']`,
para que um operador **atribua** o código durante a execução do ajuste (mesmo
formato de uma `AJUSTE_DADOS` designada manualmente via `GestaoMissoesAjuste`). É
exatamente o "posteriormente" pedido.

## User stories

### US1 — Buscar produto por descrição quando não há código de barras
**Como** operador de alocação **eu quero** pesquisar o produto pela descrição (ou
código interno) **para** endereçá-lo mesmo sem conseguir bipar o código de barras.

**Critérios de aceite:**
- AC1.1: Na tela de execução da alocação existe um campo de **busca manual** que
  consulta `produtos` por `descricao ILIKE` e por `codigo_produto`, no escopo do
  tenant (block), com limite (~20 resultados).
- AC1.2: Cada resultado mostra `descricao`, `codigo_produto`, `codigo_barras`
  (quando houver) e um indicador `tem_codigo_barras`.
- AC1.3: Selecionar um resultado **stagea** o produto exatamente como uma bipagem
  bem-sucedida (mesmo caminho de confirmação/alocação).
- AC1.4: Se o produto selecionado **não tem** `codigo_barras`, a UI oferece a ação
  "Sinalizar sem código de barras" (US2).
- AC1.5: A busca **não cria** nenhum registro (read-only); só identifica o produto.

### US2 — Registrar ocorrência "produto sem código de barras"
**Como** operador de alocação **eu quero** sinalizar que um produto está sem código
de barras **para** que uma missão de ajuste seja aberta depois para corrigir o
cadastro.

**Critérios de aceite:**
- AC2.1: A ação registra uma ocorrência pendente na origem (missão de alocação
  ativa) para o produto, **sem exigir nenhum código** (`tipo = 'ausencia'`).
- AC2.2: Idempotente por (missão de origem, produto): repetir a ação não duplica;
  mantém uma única pendência.
- AC2.3: Só é aceita se a missão de origem é `ALOCACAO_WMS` **ativa** (não
  concluída/aguardando aprovação/cancelada) e o produto **realmente não tem**
  `codigo_barras` cadastrado.
- AC2.4: O operador pode **cancelar** a ocorrência do produto (soft-delete →
  `cancelada`) enquanto a missão está aberta.
- AC2.5: As regras de **código** do fluxo `sugestao` (mínimo de 6 chars, dedupe por
  código, conflito cross-mission por código) **não** se aplicam ao caminho
  `ausencia`.

### US3 — Gerar a missão de Ajuste de Dados ao concluir a alocação
**Como** gestor **eu quero** que, ao concluir a missão de alocação, as ocorrências
"sem código" virem **uma** missão de Ajuste de Dados **para** que o time corrija os
códigos depois, em lote.

**Critérios de aceite:**
- AC3.1: Na transição da missão `ALOCACAO_WMS` para **concluída**, o hook de
  finalização dispara a geração do ajuste (mesmo ponto usado por
  Separação/Carregamento), agora habilitado para `ALOCACAO_WMS`.
- AC3.2: Se há ≥1 ocorrência pendente, gera **exatamente uma** missão
  `AJUSTE_DADOS` com `configuracoes.campos_solicitados = ['codigo_barras']` e
  **um item pendente por produto** (status pendente, sem `dados_coletados` de
  código — o código será atribuído na execução).
- AC3.3: **Idempotente**: re-finalizar a mesma origem não cria um segundo ajuste
  (guard `jaTemAjusteParaOrigem`). As ocorrências consumidas são linkadas ao ajuste
  (`consumida_em_ajuste_id`) e saem de "pendente".
- AC3.4: Sem ocorrências pendentes → nada acontece (nenhuma missão criada).
- AC3.5: O caminho `sugestao` (Separação/Carregamento, com código) permanece
  **inalterado** — itens concluídos, missão em `AGUARDANDO_APROVACAO`,
  auto-aplicação na aprovação.

## Modelo de dados (alteração)
Tabela existente `missao_codigo_sugestao` (em `gx_goldie`/dev `dev_gx_goldie`):
- `codigo_sugerido` passa a aceitar **NULL** (era `NOT NULL`).
- Nova coluna `tipo VARCHAR(20) NOT NULL DEFAULT 'sugestao'` — domínio:
  `'sugestao' | 'ausencia'`.
- Migration **idempotente e aditiva** (`ALTER COLUMN ... DROP NOT NULL`,
  `ADD COLUMN IF NOT EXISTS`). Retrocompatível: linhas existentes ficam `sugestao`.
- Índices/uniques atuais permanecem válidos (a unicidade pendente é por
  `(missao_origem_id, produto_id, block)`, independente de `tipo`).

## Pontos de mudança (resumo, sem código)
1. **Migration** em `SugestoesCodigoBarras/Migrations/` (drop NOT NULL + coluna
   `tipo`).
2. **Sonda** `MissaoCodigoSugestaoSonda`: método `registrarAusencia(origemId,
   produtoId, operadorId)` (não exige código; `tipo='ausencia'`). `$required` não
   pode forçar `codigo_sugerido` nesse caminho.
3. **Validator** `SugerirCodigoBarrasValidator`: ramo `ausencia` que mantém
   (missão existe/ativa/tipo permitido, produto existe e **sem** `codigo_barras`) e
   pula as regras de código.
4. **Alocação — busca manual**: `BuscarProdutoLaboratorio` + ação `buscarProduto`
   no dispatcher `AlocacaoWms.php`.
5. **Alocação — ocorrência**: ação `sinalizarSemCodigo` (e `cancelarSemCodigo`) no
   dispatcher, delegando ao fluxo do sinal `SugestoesCodigoBarras`.
6. **Gerador** `CriarAjusteDadosDeSugestoesLaboratorio`: ramificar por `tipo` —
   ocorrências `ausencia` produzem missão **executável** com itens **pendentes**
   (`campos_solicitados=['codigo_barras']`), espelhando `GestaoMissoesAjuste`. O
   ramo `sugestao` fica intacto.
7. **Hook de conclusão**: incluir `TipoMissao::ALOCACAO_WMS->id()` na whitelist do
   gatilho em `MissoesStatus` (e o espelho em `MissoesAprovacao`).
8. **Front** da execução da alocação: campo de busca manual + ação "sinalizar sem
   código" (i18n PT via `show(...)`).

## Testes (alvos)
- Sonda: `registrarAusencia` cria pendência com `tipo='ausencia'` e `codigo_sugerido`
  nulo; idempotência por (origem, produto).
- Validator: aceita `ausencia` em `ALOCACAO_WMS` ativa com produto sem código;
  rejeita produto já com código / missão finalizada; **não** exige ≥6 chars.
- Busca manual: retorna por descrição e por `codigo_produto`, escopo do tenant.
- Gerador: ocorrências `ausencia` → 1 missão `AJUSTE_DADOS` com itens **pendentes**
  e `campos_solicitados=['codigo_barras']`; idempotência; ramo `sugestao` inalterado.
- Hook: conclusão de `ALOCACAO_WMS` com pendências dispara a geração.
- Não-regressão: fluxo `sugestao` de Separação/Carregamento permanece igual.

## Riscos / pontos a validar na implementação
- **Caminho de conclusão da Alocação WMS** (sessão livre): confirmar **qual**
  transição leva à geração — `MissoesStatus::concluirOuFinalizarOperador` e/ou
  `MissoesAprovacao`. Habilitar a whitelist no ponto correto (ou em ambos).
- **`$required` da Sonda** não pode quebrar o insert do caminho `ausencia`
  (gotcha conhecido de `ModelInteligencia`).
- Execução da `AJUSTE_DADOS` com item pendente precisa permitir o operador
  **atribuir** o `codigo_barras` (verificar `AjusteDadosExecucaoStrategy` /
  `html/ajustedados/item.php`).
