# Alocação de Localização WMS — Plano técnico

## Resumo de decisão arquitetural

**A feature NÃO vira um Sinal novo isolado — ela estende a infraestrutura de missões logísticas existente.** Adicionamos um 13º `TipoMissao` (`ALOCACAO_WMS`) e plugamos o fluxo nas três famílias de strategies que já governam toda missão (Execução, Lista, Hook), exatamente como Inventário/Pesagem foram plugados. A execução do operador vai num `AlocacaoWmsHandler` dentro do sinal `App` (espelhando `InventarioHandler`). A persistência de cada alocação ganha **tabela dedicada `wms_alocacoes`** (não usar `dados_adicionais` — justificado abaixo, porque há 1:N e estados de integração que precisam de índice/query). A validação de código bipado consulta a nova tabela de referência `wms_localizacoes`. A integração ERP/WMS é **assíncrona** via duas `HookSincronizadorBase` (uma ERP, uma WMS) disparadas em background a partir de um worker — o operador confirma e segue sem esperar.

Banco-alvo é **PostgreSQL** (`gx_goldie` / `dev_gx_goldie`), não MySQL — as migrations seguem `SERIAL`/`TIMESTAMP WITHOUT TIME ZONE`/índices parciais, como `001_create_missao_codigo_sugestao.sql`.

## Sinal de referência

- **Imitar (execução no app)**: `componentes/Missoes/Logistica/sinais/App/Handlers/InventarioHandler.php` + `componentes/Constelacoes/Logistica/MissaoLogistica/Strategies/Execucao/InventarioExecucaoStrategy.php` — porque Inventário já é a missão "sem sequência obrigatória, parcial e retomável" (US2/US3/US6) e gira em torno de localização física.
- **Imitar (sinal vertical-slice / Sonda / Validator / migration / Adapter)**: `componentes/Missoes/Logistica/sinais/SugestoesCodigoBarras/` — referência V2 mais recente e completa: dispatcher fino, `Sondas/` com `ModelInteligencia`, `Validators/` dedicados, `Migrations/NNN_*.sql` Postgres, `Adapters/<Dominio>/`, `Testes/` espelhando a árvore.
- **Imitar (bipagem de código)**: `componentes/Constelacoes/Logistica/MissaoLogistica/Satelites/MissoesBipagem.php::biparItem` (resolução produto + busca item da missão) e `PRD/bipagem-codigo-barras-fator/` (padrão de dois códigos).
- **Imitar (hook assíncrono)**: `componentes/Constelacoes/Logistica/MissaoLogistica/Strategies/HookSincronizadorBase.php` + `AjusteDadosHookStrategy.php` (payload flexível com `codigomd5`/`importacao_id`).
- **Imitar (registro de strategy)**: `ExecucaoStrategyFactory.php`, `MissaoListaStrategyFactory.php` (mapa enum→classe — só adicionar a chave).
- **NÃO imitar**: `sinais/Designacoes/`, `sinais/Pessoas/`, `Inteligencias/` (V1 legado — usar Sondas, conforme `feedback_sondas_v2`). Não copiar `Callbacks/Registry` do `Edicaorota` (ele existe porque múltiplos módulos consomem a edição de rota; aqui há **um** fluxo, não introduzir Registry prematuro — ver "Padrões de extensibilidade").

## Estrutura proposta

### Novos arquivos

```
componentes/Missoes/Logistica/sinais/AlocacaoWms/
├── AlocacaoWms.php                                  # dispatcher fino (controller = nome do dir)
├── Enums/
│   ├── StatusAlocacao.php                           # pendente | confirmada | cancelada
│   ├── OrigemLeitura.php                            # codigo_barras | manual
│   └── EstadoIntegracao.php                         # pendente | enviado | falha
├── Sondas/
│   ├── WmsAlocacaoSonda.php                         # tabela wms_alocacoes (1:N produto↔localização)
│   └── WmsLocalizacaoSonda.php                      # tabela wms_localizacoes (referência/validação)
├── Laboratorios/
│   ├── ConfirmarAlocacaoLaboratorio.php             # orquestra Validator → Sonda → marca integração pendente
│   └── ListarAlocacoesLaboratorio.php               # consulta alocações por missão/produto
├── Validators/
│   └── ConfirmarAlocacaoValidator.php               # AC3.4/3.5/4.1-4.5 (missão ativa, permissão, produto na missão, loc existe, qtd condicional)
├── Migrations/
│   ├── 001_create_wms_localizacoes.sql
│   ├── 002_create_wms_alocacoes.sql
│   └── 003_seed_tipo_missao_alocacao_wms.sql        # INSERT em tipos_missao_logistica (id=13)
└── Testes/
    ├── Enums/StatusAlocacaoTddTest.php
    ├── Sondas/WmsAlocacaoSondaTddTest.php
    ├── Sondas/WmsLocalizacaoSondaTddTest.php
    ├── Validators/ConfirmarAlocacaoValidatorTddTest.php
    └── Laboratorios/ConfirmarAlocacaoLaboratorioTddTest.php

componentes/Constelacoes/Logistica/MissaoLogistica/
├── Strategies/Execucao/
│   └── AlocacaoWmsExecucaoStrategy.php              # extends BaseExecucaoStrategy (ordenação livre/por código)
└── Strategies/
    ├── ErpAlocacaoWmsHookStrategy.php               # extends HookSincronizadorBase (payload importacao_id)
    └── WmsAlocacaoWmsHookStrategy.php               # extends HookSincronizadorBase (base WMS externa)

componentes/Missoes/Logistica/sinais/App/
├── Handlers/
│   └── AlocacaoWmsHandler.php                       # extends BaseHandler (espelha InventarioHandler)
├── Strategies/Lista/
│   └── AlocacaoWmsListaStrategy.php                 # extends MissaoListaStrategyBase
└── html/alocacaowms/
    ├── menu.php
    ├── item.php                                     # tela de bipagem dos 2 códigos
    └── lista.php                                    # lista de produtos pendentes + filtros
```

### Arquivos alterados

- `componentes/Constelacoes/Logistica/MissaoLogistica/Enums/TipoMissao.php` — adicionar `case ALOCACAO_WMS = 'ALOCACAO_WMS';` e mapear em `name()/id()/label()/fromId()` com **id 13**.
- `componentes/Constelacoes/Logistica/MissaoLogistica/Strategies/Execucao/ExecucaoStrategyFactory.php` — `'ALOCACAO_WMS' => AlocacaoWmsExecucaoStrategy::class`.
- `componentes/Missoes/Logistica/sinais/App/Strategies/Lista/MissaoListaStrategyFactory.php` — `'ALOCACAO_WMS' => AlocacaoWmsListaStrategy::class`.
- `componentes/Missoes/Logistica/sinais/App/App.php` — registrar `AlocacaoWmsHandler` em `$handlerClasses`, `$handlerMap`/`$routeMap` (`'alocacaowms' => [AlocacaoWmsHandler::class, 'alocacaowms']`), e delegates `Luz_*`.
- `componentes/Constelacoes/Logistica/MissaoLogistica/Satelites/MissoesBipagem.php` — adicionar `'alocarLocalizacao' => 'alocarLocalizacao'` no mapa de métodos (delega ao `ConfirmarAlocacaoLaboratorio`; **não** reimplementar lógica de quantidade do picking aqui).
- `componentes/Constelacoes/Logistica/MissaoLogistica/Satelites/MissoesStatus.php` (ou trait nova `PosConfirmacaoAlocacaoTrait`) — enfileirar disparo assíncrono dos dois hooks (ver "Endpoints / fluxo").
- Tela de criação de missão do gestor (sinal a confirmar — **assunção: `Central`**, ver Riscos) — incluir `ALOCACAO_WMS` na lista de tipos criáveis e expor o flag `quantidade_obrigatoria`.
- `linguagens/pt-br.json`, `linguagens/en.json` (se existir), `linguagens/Distribuicao/pt-br.json` — chaves `alocacao_wms_titulo`, `alocacao_wms_bipar_localizacao`, `alocacao_wms_bipar_produto`, `alocacao_wms_confirmar`, `alocacao_wms_loc_invalida`, `alocacao_wms_produto_fora_missao`, `alocacao_wms_qtd_obrigatoria`. (Detalhe de views/i18n segue `genesis-views-i18n` — não detalhar JS aqui.)

## Sondas e schema

> Postgres. Colunas `block*` são preenchidas pelo framework via `customerControl`; declarar **sem** `DEFAULT` (`feedback_block_columns`). Writes sempre via `update()`/`create()` do `ModelInteligencia` (`feedback_no_raw_sql`).

### `wms_localizacoes` (Sonda: `WmsLocalizacaoSonda`) — tabela de referência/validação

Catálogo consultado na bipagem (AC3.5/4.4). É **apenas leitura** pela feature (regra: não gerenciamos layout de armazém). Populada por carga externa/ERP fora do escopo desta feature.

```sql
CREATE TABLE wms_localizacoes (
    id SERIAL PRIMARY KEY,
    codigo            VARCHAR(64) NOT NULL,   -- código físico bipável (corredor/prateleira/posição)
    descricao         VARCHAR(255),
    ativo            SMALLINT NOT NULL DEFAULT 1,
    -- block padrão Galaxia (framework popula; sem DEFAULT)
    data_cadastro       TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(),
    block_filial_id     INTEGER,
    block_usuario_id    INTEGER,
    block_empresa_id    INTEGER,
    block               INTEGER
);
CREATE UNIQUE INDEX uniq_wms_localizacoes_codigo ON wms_localizacoes (codigo, block);
CREATE INDEX idx_wms_localizacoes_ativo ON wms_localizacoes (ativo, block);
```
**`propeties`**: `customerControl: true` — a base de localizações é por tenant (multi-cliente), igual `MissaoCodigoSugestaoSonda`.

### `wms_alocacoes` (Sonda: `WmsAlocacaoSonda`) — registro 1:N produto↔localização

```sql
CREATE TABLE wms_alocacoes (
    id SERIAL PRIMARY KEY,
    missao_logistica_id   INTEGER NOT NULL REFERENCES missao_logistica(id) ON DELETE CASCADE,
    missao_item_id        INTEGER,            -- item pendente da missão (opcional p/ reconciliação)
    produto_id            INTEGER NOT NULL,
    localizacao_codigo    VARCHAR(64) NOT NULL,
    wms_localizacao_id    INTEGER REFERENCES wms_localizacoes(id) ON DELETE SET NULL,
    quantidade            NUMERIC(14,3),       -- NULL permitido (decisão de produto #2)
    origem_leitura        VARCHAR(20) NOT NULL DEFAULT 'codigo_barras', -- codigo_barras | manual
    status               VARCHAR(20) NOT NULL DEFAULT 'confirmada',     -- confirmada | cancelada
    operador_id           INTEGER NOT NULL,
    confirmada_em         TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(),
    -- estados de integração (AC5.2/5.3) — colunas dedicadas pra serem indexáveis/retentáveis
    integracao_erp        VARCHAR(20) NOT NULL DEFAULT 'pendente',      -- pendente | enviado | falha
    integracao_wms        VARCHAR(20) NOT NULL DEFAULT 'pendente',
    integracao_erp_em     TIMESTAMP WITHOUT TIME ZONE,
    integracao_wms_em     TIMESTAMP WITHOUT TIME ZONE,
    integracao_erro       TEXT,                -- última mensagem de falha (não some silenciosamente)
    -- block padrão Galaxia (framework popula; sem DEFAULT)
    data_cadastro       TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(),
    block_filial_id     INTEGER,
    block_usuario_id    INTEGER,
    block_empresa_id    INTEGER,
    block               INTEGER
);
CREATE INDEX idx_wms_alocacoes_missao    ON wms_alocacoes (missao_logistica_id, block);
CREATE INDEX idx_wms_alocacoes_produto   ON wms_alocacoes (produto_id, block);
-- fila de retry de integração (worker varre pendente/falha)
CREATE INDEX idx_wms_alocacoes_integ_erp ON wms_alocacoes (integracao_erp, block) WHERE integracao_erp IN ('pendente','falha');
CREATE INDEX idx_wms_alocacoes_integ_wms ON wms_alocacoes (integracao_wms, block) WHERE integracao_wms IN ('pendente','falha');
```
**`propeties`**: `customerControl: true` — alocação é por tenant.

**Por que tabela dedicada e NÃO `missao_logistica_itens.dados_adicionais`** (decisão sobre a dúvida do PRD): (1) decisão de produto #1 — **múltiplas localizações por produto** (1:N) — não cabe num JSONB de coluna 1:1 do item sem virar array difícil de consultar; (2) AC5.2/5.3 exigem **estado de integração por alocação com retry/varredura** — precisa de índice parcial, inviável em JSONB; (3) a feature "acumula, não sobrescreve", então cada confirmação é uma **linha** nova. `dados_adicionais` continua livre para metadados efêmeros, mas a verdade da alocação mora em `wms_alocacoes`.

## Endpoints / fluxo

> A feature é **interna ao Genesis** (sinais + Satélite/Observatório do framework), não REST `/api/v1`. Os "endpoints" são ações de sinal roteadas pelo `App.php`/dispatcher e pelo Satélite `MissoesBipagem`. (Skill `genesis-api-handler` não se aplica — não há handler REST novo.)

| Ator | Entrada | Roteia para | Laboratório/Strategy | Auth |
|---|---|---|---|---|
| Gestor cria missão | tela criação (Central) → `MissoesPersistencia::criacao` com `tipo=ALOCACAO_WMS` | satélite existente | reusa criação de missão + itens | sessão Galaxia |
| Operador lista pendentes | `App` rota `alocacaowms` | `AlocacaoWmsHandler::alocacaowms` | `AlocacaoWmsExecucaoStrategy` (ordenação) + filtros | sessão + `loadActivatedMission` (tipo+status+permissão) |
| Operador confirma alocação | `MissoesBipagem` opção `alocarLocalizacao` (via form Galaxia) | `ConfirmarAlocacaoLaboratorio::executar` | `ConfirmarAlocacaoValidator` → `WmsAlocacaoSonda::create` | sessão + validator AC4.1/4.2 |
| Integração ERP/WMS | worker assíncrono | `MissoesStatus`/trait enfileira | `ErpAlocacaoWmsHookStrategy` + `WmsAlocacaoWmsHookStrategy` | bearer token do hook |

**Fluxo de confirmação (US3/US4/US5):**
1. Front envia os dois códigos (`localizacao_codigo`, `codigo`/`produto_id`) + `origem_leitura` + `quantidade?` para `MissoesBipagem::alocarLocalizacao`.
2. `ConfirmarAlocacaoValidator` valida na ordem: missão existe e ativa (AC4.1) → operador tem permissão (AC4.2, reusa `MissoesStatus::verificarAcesso`) → produto pertence à missão (AC3.4/4.3, reusa lógica de `MissoesBipagem::resolverProduto`+`buscarItemDaMissao`) → localização existe em `wms_localizacoes` (AC3.5/4.4) → se `missao.quantidade_obrigatoria` então quantidade > 0 (decisão #2). Qualquer falha retorna `Message::error` específico, sem gravar (AC4.5).
3. `WmsAlocacaoSonda::create` insere a linha com `integracao_erp='pendente'`, `integracao_wms='pendente'`. Marca o item da missão como concluído e atualiza status da missão via `MissoesStatus` (AC3.6/6.1/6.2/6.3) — **reaproveita** o ciclo de status existente.
4. Resposta **imediata** ao operador (decisão #3 — assíncrono). O envio ao ERP/WMS NÃO bloqueia.
5. Worker assíncrono (ver Riscos) varre `wms_alocacoes` com `integracao_* IN ('pendente','falha')`, chama os dois hooks, e grava `enviado`/`falha`+`integracao_erro`+timestamp. ERP usa `importacao_id ↔ codigomd5` (AC5.4, `erp_importacao_id_mapping`).

## Padrões de extensibilidade

- **Strategies (Execução/Lista/Hook)**: padrão V2 dominante das missões. A spec **exige** porque cada tipo de missão tem ordenação, card de lista e integração próprios — é o mecanismo já estabelecido (Inventário, Pesagem…). Adicionar uma chave no mapa de cada factory é a extensão idiomática, sem `if/else` no core.
- **Dois Hooks separados (ERP e WMS)**: a spec **exige** (AC5.1/5.2) que ERP e WMS tenham estado de integração **independente** (um pode falhar e o outro não). Dois `HookSincronizadorBase` com colunas `integracao_erp`/`integracao_wms` separadas, em vez de um hook monolítico.
- **NÃO introduzir `Callbacks/Registry`** (como o `Edicaorota` tem): há **um** consumidor/fluxo de alocação. Registry seria padrão prematuro. Se no futuro outros módulos quiserem reagir à confirmação, aí sim se extrai um Registry — não agora.
- **`Adapters/`**: criar **apenas se** a criação da missão de Alocação precisar ser disparada por outro sinal (ex.: Armazém gerar alocações a partir de produtos sem localização). Para o escopo atual (gestor cria manualmente), **N/A** — não criar Adapter especulativo.

## Ordem de implementação (sequencial)

1. **Enums** (`StatusAlocacao`, `OrigemLeitura`, `EstadoIntegracao`) + `TipoMissao::ALOCACAO_WMS` (id 13) → roda `Testes/Enums/*TddTest.php`.
2. **Migrations** `001`/`002`/`003` → roda contra `dev_gx_goldie` e valida (`integration` test de schema). `003` semeia `tipos_missao_logistica`.
3. **Sondas** `WmsLocalizacaoSonda`, `WmsAlocacaoSonda` → `Testes/Sondas/*TddTest.php`.
4. **Validator** `ConfirmarAlocacaoValidator` (unit, com leituras de produto/missão) → `Testes/Validators/*TddTest.php`.
5. **Laboratórios** `ConfirmarAlocacaoLaboratorio`, `ListarAlocacoesLaboratorio` → `Testes/Laboratorios/*TddTest.php`.
6. **`AlocacaoWmsExecucaoStrategy`** + registro em `ExecucaoStrategyFactory`.
7. **`AlocacaoWmsListaStrategy`** + registro em `MissaoListaStrategyFactory`.
8. **`MissoesBipagem::alocarLocalizacao`** delegando ao Laboratório.
9. **`AlocacaoWmsHandler`** + registro/delegates em `App.php` + dispatcher `AlocacaoWms.php`.
10. **Views** `html/alocacaowms/*` + JS de bipagem (dois códigos) + i18n (`genesis-views-i18n`).
11. **Hooks** `ErpAlocacaoWmsHookStrategy`, `WmsAlocacaoWmsHookStrategy` + ponto de disparo assíncrono + worker de varredura.
12. **Criação no Central**: expor tipo + flag `quantidade_obrigatoria` na tela do gestor.

## Riscos e decisões não-óbvias

1. **Não existe runner assíncrono pronto** — os hooks atuais (`HookSincronizadorBase`) rodam **inline** na request com cURL timeout 10s (vistos em `PosInicioSeparacaoTrait`/`MissoesStatus`). A decisão de produto #3 (assíncrono) exige um mecanismo novo. **Mitigação**: gravar `integracao_*='pendente'` na confirmação (rápido) e processar via (a) `register_shutdown_function`/`fastcgi_finish_request` após responder ao operador, OU (b) um job/cron que varre os índices parciais `idx_wms_alocacoes_integ_*`. Recomendo (b) como fonte da verdade (idempotente, com retry de `falha`), com (a) como otimização opcional. **Decidir o runner com o time de infra antes da etapa 11.**
2. **Origem da criação da missão (dúvida em aberto)** — assunção do TL: **criação pelo `Central`** (tela de gestão de missões já existente), pois é onde gestor seleciona produtos+responsável e onde os outros tipos nascem. `Armazem` fica fora desta entrega. Confirmar com PO no gate.
3. **Re-alocação (dúvida em aberto)** — assunção do TL alinhada à decisão #1 (acumula): bipar um produto que já tem localização **cria nova linha** em `wms_alocacoes` (1:N), **não** sobrescreve. A "localização efetiva" para exibição é a mais recente (`MAX(confirmada_em)`), mas o histórico permanece. Sem modal de confirmação de sobrescrita (não há sobrescrita).
4. **Quantidade condicional (decisão #2)** — flag por missão. Assunção: campo `quantidade_obrigatoria` em `missao_logistica.configuracoes` (JSON já existente, lido em `BaseHandler`) **ou** coluna no tipo de missão. Recomendo `configuracoes` (sem migration em `missao_logistica`, definido na criação). Validator lê esse flag; default = não obrigatória.
5. **Entrada manual (dúvida em aberto)** — assunção: digitação manual permitida para **ambos** os códigos (loc e produto), sempre registrando `origem_leitura='manual'` (AC3.3). É registro de auditoria, não bloqueio.
6. **Tipo de missão é global, não por tenant** (`tipos_missao_logistica` sem `block` de customer, ver `TipoMissaoLogisticaSonda`) — o seed `003` insere id 13 uma vez; `customerControl=false` nessa tabela.

## Breaking changes

Nenhum. Tudo é adição: novo `case` no enum (ids 1-12 inalterados), novas chaves nos mapas das factories, novo handler registrado, duas tabelas novas, uma linha de seed. Campos de andar 1/andar 2 e demais tipos de missão permanecem intactos (regra de negócio §2 da spec).

## Plano de teste

- **Unit (TDD em `Testes/`)**: `ConfirmarAlocacaoValidator` (todos AC4.x + flag de quantidade), `StatusAlocacao`/`OrigemLeitura`/`EstadoIntegracao` enums, `WmsAlocacaoSonda`/`WmsLocalizacaoSonda` (create/find/marcar integração).
- **Integration (contra `dev_gx_goldie`, bootstrap com `$_SESSION` na raiz + `dashboard` por `integration_test_session_groups`)**: schema das migrations; fluxo confirmar alocação end-to-end (produto na missão + loc válida → linha em `wms_alocacoes`, item concluído, status da missão atualizado); rejeição (produto fora da missão, loc inexistente); 1:N (duas alocações no mesmo produto não se sobrescrevem); varredura de integração marca `enviado`/`falha`.
- **Manual (smoke do usuário)**: gestor cria missão Alocação WMS no Central com 2 produtos → operador abre no `App`, filtra lista, bipa loc+produto (ambas as ordens), confirma → item some dos pendentes, missão vira parcial → segunda alocação conclui a missão → conferir `wms_alocacoes` e logs de hook em `/tmp/genesis_webhook.log`.

## Out of scope (confirmando spec)

Da spec (seção "Fora de escopo"), mantidos:
- Não substituir/alterar andar 1 e andar 2 (continuam intactos).
- Sem movimentação/abate de estoque — só registro de localização.
- Sem fluxos de retorno/devolução de localização.
- Sem UI de gerenciamento de layout de armazém — `wms_localizacoes` é **só consultada** (carga externa popula a tabela).

Acréscimos técnicos do TL:
- Sem REST `/api/v1` novo — feature é interna via sinais/satélites.
- Sem `Callbacks/Registry` e sem `Adapters/` nesta entrega (extensibilidade prematura).
- Criação pelo `Armazem` adiada (assunção: só `Central` agora).
- Não implementamos o produtor de dados de `wms_localizacoes` (ETL/integração inbound do layout) — assumimos a tabela populada por processo externo.
