# Design: Registrar ALOCACAO_WMS em Designações (dispatch)

**Data:** 2026-06-18
**Status:** Aprovado

---

## Contexto

A missão `ALOCACAO_WMS` (tipo ID 14) já tem execução funcional (operador bipa produto + localização, registra em `wms_alocacoes`, sincroniza com ERP/WMS). Falta o lado de **despacho**: registrar a missão no módulo Designações para que um gestor possa selecionar produtos e disparar a missão aos operadores.

O fluxo desejado segue o padrão dos outros tipos: selecionar quais produtos enviar → disparar → operadores decidem onde alocar ("modo livre"). Futuramente, ao conectar com o WMS, o sistema enviará obrigatoriamente onde cada produto deve ser alocado ("modo dirigido") — fora do escopo desta entrega, mas o modelo de dados já prevê.

---

## Estado atual

- `AlocacaoWmsStrategy` já existe e está registrada em `DesignacaoStrategyFactory` → o tipo aparece no wizard de designação, com seleção por produto e multi-operador.
- O tipo `ALOCACAO_WMS` (id 14) já existe no enum `TipoMissao` e foi seedado em `tipos_missao_logistica`.
- **Falta** o Satélite `GestaoMissoesAlocacaoWms` (que a strategy referencia via `getSatelite()`) — ele cria a missão + itens. Sem ele, o disparo quebra.
- O dispatcher (`Designacoes.php`) tem allowlists hardcoded (`$satelitesDesignacao`) nas linhas 47, 57 e 137 que não incluem o novo satélite → resposta de sucesso + redirect não seriam tratados.

---

## Decisões

| Decisão | Escolha |
|---|---|
| Modo de alocação | Gravar `configuracoes.modo_alocacao = 'livre'` na criação. WMS dirigido vira flip de config no futuro. |
| Aprovação | Direto ao operador — `requer_aprovacao = 0` forçado (não passa por gate). |
| Seleção de produtos | Todos os produtos ativos do catálogo. |
| Filtro "sem localização" | Fonte = tabela `wms_alocacoes` (alocações confirmadas). `codigo_enderecamento` (legado) **não** entra. |

---

## Componentes

### 1. `GestaoMissoesAlocacaoWms.php` (novo)

`componentes/Missoes/Logistica/sinais/Designacoes/Laboratorios/GestaoMissoesAlocacaoWms.php`

Espelha `GestaoMissoesAjuste`. Estende `GestaoMissoesBase` (template method). Implementa:

- `getTipoMissao(): TipoMissao` → `TipoMissao::ALOCACAO_WMS`
- `processarEnvio(array $data)` → normaliza `itens_selecionados` a partir de `produtos`; **força `$data['requer_aprovacao'] = '0'`**; chama `parent::processarEnvio($data)`.
- `buscarItens(array $selecao): array` → carrega produtos por ID (via `Produto` Inteligencia) e monta cada item:
  ```php
  [
    'produto_id'       => $produtoId,
    'quantidade'       => 1,
    'referencia_tipo'  => TipoMissao::ALOCACAO_WMS->value,
    'referencia_id'    => $produtoId,
    'dados_adicionais' => [
      'produto_nome'  => $prod->descricao,
      'codigo_produto'=> $prod->codigo_produto,
      'codigo_barras' => $prod->codigo_barras,
      'importacao_id' => $prod->importacao_id, // codigomd5 ERP, p/ sync futuro
    ],
  ]
  ```
- `getConfiguracoesAdicionais(array $data, array $selecionados): array` → `['modo_alocacao' => 'livre']`
- `extrairReferenciaId($selecionados, $itens)` → `null` (alocação não tem romaneio de referência)
- `getReferenciaTipo(): string` → `TipoMissao::ALOCACAO_WMS->value`

A persistência (missão PENDENTE + itens) é feita pelo template method da base via `MissoesLogisticas 'criacao'`. A mensagem de sucesso já vem de `AlocacaoWmsStrategy::getSuccessMessage()` (chamada pela base) — não precisa de hook no satélite.

### 2. `Designacoes.php` — wiring do dispatcher

Adicionar `'GestaoMissoesAlocacaoWms'` às 3 allowlists `$satelitesDesignacao` (linhas 47, 57, 137) para que:
- erros do laboratório passem para renderização (linha 47),
- a resposta de sucesso com `limparSelecao` seja emitida (linha 57/59),
- o redirect para `designacoes_lista` aconteça (linha 137/138).

### 3. Filtro "sem localização" (reaproveita infra existente)

A tela `designar_unificado` já serializa `primaryItems` como JSON e tem filtro client-side com toggles (`semEndereco` etc.), hoje gated em `tipo === 'ajuste'`. O cálculo `semEnderecoIds = data.filter(p => !p.localizacao && !p.codigo_enderecamento)` já roda para todos os tipos.

**Fonte de verdade = `wms_alocacoes`** (não `codigo_enderecamento`):

- **`AlocacaoWmsStrategy::getPrimaryItems()`** — hoje retorna só `id/descricao/total_itens`. Passa a incluir `codigo_produto`, `codigo_barras` e **`localizacao`** (código da última alocação confirmada em `wms_alocacoes` para o block atual, via query batched; vazio se não houver). **Não envia `codigo_enderecamento`.**
  - Como `codigo_enderecamento` fica ausente nos itens de Alocação WMS, o cálculo `!localizacao && !codigo_enderecamento` reduz naturalmente a `!localizacao` — keying 100% em `wms_alocacoes`, sem alterar o comportamento do Ajuste.
- **`designar_unificado.php`** — renderizar o toggle (apenas o de localização) para `tipo === 'alocacao_wms'`, com rótulo **"Sem localização"** (id próprio, ex.: `toggle-sem-loc`). Default desligado, consistente com os filtros do Ajuste.
- **`designar_unificado.js` `getFiltered()`** — estender o branch de produtos de `tipo === 'ajuste'` para incluir `'alocacao_wms'`, aplicando ao menos a busca textual e o filtro de localização (reusa `semEnderecoIds`/`semEndereco`).

---

## Fluxo resultante

```
Wizard → "Alocação WMS" → seleciona produtos (filtro opcional "Sem localização") + operadores → "Criar Missão"
  → POST setSatelite('GestaoMissoesAlocacaoWms')->setOpcao('enviarMissoes')
  → GestaoMissoesAlocacaoWms::processarEnvio (requer_aprovacao=0, modo_alocacao='livre')
  → MissoesLogisticas 'criacao' → missão tipo 14 PENDENTE + itens
  → operador vê na app e executa pela tela AlocacaoWms já existente (modo livre)
```

---

## TDD

O construtor de `GestaoMissoesBase` auto-dispara o fluxo (switch no `$opcao`). Para isolar a lógica nova, os testes usam `ReflectionClass::newInstanceWithoutConstructor()` e invocam os métodos protegidos diretamente. Técnica padrão para classes com construtor com efeito colateral; testa comportamento real contra dados reais (`dev_gx_goldie`).

Testes (em `Designacoes/Testes/Laboratorios/GestaoMissoesAlocacaoWmsTddTest.php`):

1. `getTipoMissao()` → `TipoMissao::ALOCACAO_WMS`
2. `getReferenciaTipo()` → `'ALOCACAO_WMS'`
3. `extrairReferenciaId(...)` → `null`
4. `getConfiguracoesAdicionais([], [])` → `['modo_alocacao' => 'livre']`
5. `buscarItens([ids reais block 22])` → retorna 1 item por produto, com `dados_adicionais.produto_nome`, `codigo_produto`, `codigo_barras`, `importacao_id` populados e `referencia_tipo = 'ALOCACAO_WMS'`.
6. `buscarItens([])` → `[]` (seleção vazia).

O wiring do dispatcher (allowlists em `Designacoes.php`) e o filtro client-side são validados na verificação manual — testar o roteamento do framework e JS do wizard em CLI é impraticável e sem precedente no repo.

---

## Critérios de aceite

1. O tipo "Alocação WMS" aparece no wizard de designação (já funciona).
2. Selecionar produtos + operadores e clicar "Criar Missão" cria uma `missao_logistica` tipo 14 em status PENDENTE, com `requer_aprovacao=0` e `configuracoes.modo_alocacao='livre'`.
3. A missão criada tem 1 item por produto selecionado, com `dados_adicionais` populado.
4. Após criar, a tela redireciona para `designacoes_lista` e mostra mensagem de sucesso.
5. O toggle "Sem localização" filtra a lista de produtos para mostrar apenas os sem alocação confirmada em `wms_alocacoes`.
6. A missão criada é executável pela tela AlocacaoWms existente (operador aloca em modo livre).

---

## Fora de escopo

- Modo dirigido (WMS envia localização obrigatória) — `modo_alocacao` futuro.
- Mudanças na tela/lógica de execução (já funciona).
- Escrita de volta em `produtos.codigo_enderecamento`.
- Testes automatizados de roteamento do framework e do JS do wizard.
