# ENG-1244 - Expedição

## Status e escopo

- Data: 2026-07-21.
- Base técnica: `origin/release/v1.9.8`.
- Entregas de código: ENG-1260, ENG-1261 e ENG-1263 em `rex-back` e `rex-front`.
- Levantamento sem implementação: ENG-1264.
- Extensão posterior: a ENG-1262 foi aprovada em
  [`2026-07-23-eng-1262-reading-audit-design.md`](2026-07-23-eng-1262-reading-audit-design.md).
- Fora do escopo desta entrega original: alterações no Rex-Estoque, push e
  abertura de PR.

Este documento registra as decisões de arquitetura, contratos, compatibilidade,
implantação, rollback lógico, riscos e pontos em aberto. O plano de execução em
`tasks/plan.md` continua sendo a fonte de verdade do trabalho.

## Invariantes da entrega

1. A geração de PDF não consulta o ERP.
2. Migrations não consultam o ERP nem qualquer serviço externo.
3. Rotas e campos consumidos pelo Rex-Estoque permanecem compatíveis.
4. Tarefas antigas com uma pré-fatura e novas tarefas com várias pré-faturas
   funcionam em paralelo.
5. Dados enviados pelo frontend não são autoridade para o saldo de produtos.
6. Operações concorrentes são serializadas no PostgreSQL por locks ordenados.
7. ENG-1264 permanece apenas documental até o ERP confirmar seu contrato.

## ENG-1260 - Representante, PDF e ressincronização

### Contexto e decisão

O relatório de tarefa consultava `ListarDetalhes` durante a geração. Isso tornava
o PDF dependente da disponibilidade do ERP e fazia representante e detalhes
serem obtidos fora do ciclo normal de sincronização.

A decisão é tornar o WS `Listar` a única fonte de sincronização desses dados:

- manter `E135PFA_CODREP`;
- fazer `left join` de `E135PFA` com `E090REP` por `CODEMP` e `CODREP`;
- expor `E090REP_NOMREP`;
- incorporar ao resultado os detalhes que o PDF já renderizava;
- persistir os valores em `stock_io.integration.parameters` pelo mecanismo
  genérico existente;
- montar os PDFs completo e resumido somente a partir do banco local;
- mostrar `Representante: <NOMREP>`, usando `-` quando o campo estiver ausente.

Os detalhes são persistidos no campo JSON `ORDER_DETAILS`, mantendo os rótulos
já usados pelo relatório: transportadora, redespacho, observações, observação
interna, observação do cliente, observação da nota, endereços e usuário gerador.
Todos são opcionais para preservar a renderização de registros legados.

### Cobertura do job periódico

O `Listar` sem IDs restringe `USU_DATA1` ao intervalo entre o dia corrente e
120 dias à frente. A definição de pré-fatura ativa no Rex é mais ampla: saída
de Expedição, motivo pré-fatura e status diferente de concluído/cancelado.
Logo, não há garantia de cobertura de 100% pelo job periódico.

Foi escolhido um fallback direcionado e repetível:

```text
php artisan senior:resync-active-pre-invoices
```

O comando percorre os ativos em lotes de 500, valida os quatro identificadores
e enfileira o mesmo `SeniorSyncOutputJob` com filtros serializáveis:
`CODEMP`, `CODFIL`, `NUMANE` e `NUMPFA`. O WS ignora a janela quando `NUMANE`
é informado. Reexecutar o comando apenas atualiza os mesmos registros; não há
criação por migration nem consulta direta do comando ao ERP.

### Alternativas rejeitadas

- Continuar usando `ListarDetalhes` no PDF: mantém indisponibilidade e latência
  do ERP no caminho de impressão.
- Reusar apenas o job sem filtros: não cobre ativos fora da janela.
- Consultar o ERP em migration: adiciona efeito externo não transacional e
  impede rollback seguro.

## ENG-1261 - Dashboard de embalagens concluídas

### Modelo e transições

Adicionar `shipping_packagings.concluded_at` como timestamp nullable, com cast
datetime no modelo. A migration fará backfill somente dos registros concluídos,
usando `updated_at`; os demais permanecerão nulos.

A invariável é:

```text
status concluído     => concluded_at preenchido
qualquer outro status => concluded_at nulo
```

`finish()` grava `now()` na transição. `start()`, `pause()` e qualquer retorno
para estado não concluído limpam o campo. A regra fica no caminho canônico de
persistência para também proteger chamadas que não passam pela interface.

### Índice e agregação

Criar índice parcial PostgreSQL sobre `concluded_at`, `responsible_id` e
`team_id`, restrito a embalagens concluídas do tipo Expedição. A consulta será
confirmada com `EXPLAIN (ANALYZE, BUFFERS)` em massa representativa.

O peso total será calculado no banco:

```text
shipping_packaging_products.amount * product_measures.gross_weight
```

A agregação não carrega embalagens/produtos individualmente em PHP e agrupa por
time e responsável, retornando quantidade de embalagens, soma de volumes e peso
total. O período é obrigatório e filtra exclusivamente `concluded_at`.

### Contrato do endpoint

```http
GET /api/v1/report/shipping-packaging/dashboard
    ?concluded_at[]=01/07/2026
    &concluded_at[]=31/07/2026
    &responsible[]=7
```

```json
{
  "data": [
    {
      "team": {"id": 3, "description": "Time A"},
      "responsible": {"id": 7, "name": "Pessoa"},
      "packagings_count": 15,
      "volumes_sum": 27,
      "total_weight": 913.45
    }
  ]
}
```

A rota reutiliza autorização e visibilidade do dashboard atual. O frontend
adiciona uma terceira aba com os mesmos filtros e estados de loading, vazio e
erro, separando visualmente os resultados por time.

## ENG-1263 - Uma tarefa para várias pré-faturas

### Compatibilidade e vínculo

O endpoint legado `.../store/{stockIO}` não muda de semântica. Tarefas antigas
continuam com `picking_orders.stock_io_id` preenchido. Tarefas criadas pelo novo
fluxo usam `stock_io_id = null`; `picking_order_products` passa a ser a fonte de
vínculo com todas as pré-faturas.

Um resolver canônico une o vínculo direto antigo e os vínculos via produtos,
remove duplicatas e é usado por listagens, detalhes, policies, filtros,
finalização, logs, notificações e relatórios. Resources e rotas legadas mantêm
seus campos e tipos para não quebrar o Rex-Estoque.

### Candidatos

```http
GET /api/v1/stock/expedition/output/picking-order/candidates
    ?stock_io_ids[]=10
    &stock_io_ids[]=20
```

```json
{
  "data": [
    {
      "id": 10,
      "team": {"id": 3, "description": "Time A"},
      "pre_invoice": {
        "NUMANE": "100",
        "NUMPFA": "200",
        "CODCLI": "1",
        "NOMCLI": "Cliente",
        "CODREP": "9",
        "NOMREP": "Representante",
        "PRVFAT": "2026-07-21"
      },
      "products": [
        {
          "id": 501,
          "available_amount": 12.0,
          "product": {"id": 8, "description": "Produto"}
        }
      ]
    }
  ]
}
```

O endpoint exige administrador de Expedição, lote não vazio, status Pendente ou
Iniciado e um único time. Cliente e representante não são critérios de
compatibilidade. Não existe consulta ao ERP.

### Criação múltipla

```http
POST /api/v1/stock/expedition/output/picking-order/store
```

```json
{
  "stock_io_ids": [10, 20],
  "responsible_id": 7,
  "observation": "Separar com prioridade",
  "urgent": true,
  "stock_io_products": [
    {"id": 501, "type": 1},
    {"id": 702, "type": 2}
  ]
}
```

Quantidade não integra o contrato de autoridade do cliente. Dentro de uma única
transação, o backend:

1. rejeita IDs duplicados;
2. carrega e bloqueia pré-faturas em ordem de ID;
3. carrega e bloqueia produtos em ordem de ID;
4. revalida permissão, setor, motivo, status, time e pertencimento;
5. calcula o saldo disponível no banco;
6. cria uma tarefa com `stock_io_id = null` e seus pivôs;
7. reverte tudo em qualquer erro de domínio.

Uma transação concorrente espera o lock e, ao revalidar, recebe `422` por saldo
indisponível. Falta de permissão retorna `403`. Não são criados pivôs parciais.

### Frontend e PDF

Somente administradores elegíveis veem checkboxes. Após a primeira seleção,
saídas de outros times ficam desabilitadas. A seleção sobrevive ao carregamento
de mais linhas e é invalidada quando filtros removem os itens escolhidos.

A tela multi usa IDs na query string, carrega candidatos agrupados por
pré-fatura/cliente, inicia produtos desmarcados e envia apenas IDs/tipos. O saldo
integral é aplicado pelo backend. Leitura individual/em massa, responsável,
observação e urgente permanecem disponíveis.

No PDF, o cabeçalho contém somente dados globais. Cada grupo mostra pré-fatura,
cliente, representante e faturamento. O completo lista produtos por grupo; o
resumido mostra apenas os grupos. A posição da tarefa só aparece quando existe
uma única pré-fatura semanticamente associada.

## ENG-1264 - Levantamento futuro, sem implementação

### Objetivo futuro

Criar uma tela de **saídas disponíveis para faturamento**, visível somente ao
nível Faturamento, contendo exclusivamente registros no estado
`RELEASED_FOR_BILLING = 8`. A ação futura enviará ao ERP a liberação de
faturamento, mas nenhum botão, rota de envio, cliente ERP ou alteração de estado
é implementado nesta entrega.

### Fluxo proposto sujeito a contrato do ERP

1. Usuário com nível Faturamento consulta saídas no estado 8.
2. Usuário seleciona uma ou mais saídas elegíveis.
3. Um endpoint interno autenticado valida novamente permissão e estado.
4. O Rex registra tentativa, correlação e chave idempotente antes do envio.
5. Um job assíncrono chama o ERP com timeout e política de retentativa.
6. Resultado e erro são auditados sem armazenar credenciais ou dados sensíveis.
7. A transição posterior no Rex depende da confirmação formal de sucesso do ERP.

O formato do endpoint interno, payload e resposta permanece **a confirmar**.
Nenhum exemplo de dados do ERP é definido neste documento.

### Controles que o contrato futuro deve definir

- autenticação técnica entre Rex e ERP e rotação de credenciais;
- chave idempotente estável por operação;
- prevenção de envio simultâneo/duplicado;
- correlação entre usuário, saída, tentativa, job e retorno do ERP;
- timeout de conexão e resposta;
- classificação de erros retentáveis e permanentes;
- backoff, limite de tentativas e fila de falhas;
- auditoria de pedido e resultado com mascaramento de dados;
- conciliação para respostas perdidas após processamento no ERP;
- comportamento do reenvio manual e automático;
- observabilidade: contagem, latência, falhas e itens presos;
- rollback lógico quando o ERP confirma e o Rex falha ao persistir.

### Alternativas e trade-offs a validar

- Envio síncrono simplifica o retorno ao usuário, mas o acopla à latência e à
  disponibilidade do ERP. Job assíncrono permite retentativas e isolamento,
  porém exige estado consultável, correlação e feedback posterior na tela.
- Idempotência fornecida pelo ERP é a proteção mais forte contra duplicidade
  após perda de resposta. Uma chave controlada apenas pelo Rex bloqueia
  duplicatas locais, mas não prova se o ERP processou uma tentativa sem retorno.
- Lote reduz chamadas e pode melhorar vazão, mas precisa de resultado por item e
  tratamento de sucesso parcial. Envio unitário simplifica auditoria e retry,
  ao custo de mais chamadas. A granularidade depende do contrato confirmado.
- Retentativa automática atende falhas transitórias, mas não deve alcançar
  erros funcionais. Códigos retentáveis, limite, backoff e ação manual precisam
  ser definidos pelo ERP antes da implementação.
- Atualização otimista da tela oferece resposta imediata, mas pode exibir um
  estado que o ERP recusará. Manter a saída pendente até confirmação é mais
  conservador e exige comunicar claramente o processamento assíncrono.

Essas opções não constituem contrato. A escolha permanece bloqueada até a
confirmação formal de método, granularidade, idempotência e semântica de erro.

### Perguntas obrigatórias para Vitor/ERP

1. Qual método e qual WS serão usados?
2. Quais campos, tipos, obrigatoriedades e limites compõem o pedido?
3. O envio é por saída, por pré-fatura ou em lote?
4. Qual chave idempotente o ERP aceita e por quanto tempo a retém?
5. Qual campo confirma sucesso inequívoco?
6. Quais códigos/estruturas representam erros retentáveis e permanentes?
7. Como o ERP responde a um reenvio já processado?
8. Qual transição de estado deve ocorrer no Rex após sucesso?
9. Quais timeouts máximos são suportados?
10. Qual ambiente e quais dados estarão disponíveis para homologação?
11. Quem é responsável pela entrega e suporte do lado ERP?
12. Como consultar/conferir uma operação cujo retorno foi perdido?

### Dependências e estimativas separadas

- Rex: estimativa será fechada após o contrato responder às perguntas acima e
  não inclui desenvolvimento do ERP.
- ERP: estimativa e responsável devem ser fornecidos por Vitor/equipe ERP; não
  fazem parte da estimativa do Rex.
- Homologação integrada: estimada separadamente após definição de ambiente,
  massa de dados e critérios de aceite de ambos os lados.

## Rollout operacional

### 1. Publicar o WS

1. Publicar `webservices/combrSoelXPrefaturas/Listar.txt` no Senior.
2. Em homologação, executar `Listar` para uma pré-fatura conhecida.
3. Confirmar aliases `E135PFA_CODREP`, `E090REP_NOMREP` e todos os detalhes.
4. Confirmar que a consulta sem filtros continua paginando e que a chamada
   direcionada ignora a janela apenas quando `NUMANE` foi informado.
5. Não avançar se aliases, encoding ou joins divergirem do contrato.

### 2. Publicar o Rex

1. Implantar o commit da ENG-1260 e as entregas seguintes aprovadas.
2. Executar migrations da versão somente após backup e validação do banco.
3. Reiniciar workers para carregar a nova assinatura serializável do job.
4. Validar healthcheck, logs de inicialização e geração de um PDF single.

### 3. Ressincronizar pré-faturas ativas

No mesmo release do Rex, executar primeiro a simulação:

```bash
php artisan senior:resync-active-pre-invoices --dry-run
```

Exigir `Registros invalidos: 0`. Se houver inválidos, corrigir os identificadores
locais antes de continuar. Depois enfileirar:

```bash
php artisan senior:resync-active-pre-invoices
```

O comando pode ser repetido com segurança. Não executar antes da publicação e
validação do WS.

### 4. Monitorar e validar

1. Manter workers da fila ativos até zerar os jobs dessa execução.
2. Acompanhar o canal `integration` por `integration.start`,
   `integration.done`, `integration.error` e `integration.stale_skip`.
3. Acompanhar `failed_jobs` e a profundidade da tabela/fila `jobs`.
4. Reexecutar somente depois de tratar erros permanentes.
5. Confirmar 100% de população com consulta equivalente a:

```sql
select count(*) as ativos_sem_representante
from stock_ios sio
join integrations i
  on i.integrable_type = 'App\\Models\\StockIO\\StockIO'
 and i.integrable_id = sio.id
where sio.type = 2
  and sio.reason = 1
  and sio.sector = 2
  and sio.status not in (3, 9)
  and (
    nullif(i.parameters->>'CODREP', '') is null
    or nullif(i.parameters->>'NOMREP', '') is null
  );
```

O aceite operacional é `ativos_sem_representante = 0`, salvo registros cujo ERP
formalmente não possua representante; essas exceções devem ser listadas e
aprovadas pelo negócio, não ocultadas.

## Rollback lógico

1. Pausar novos disparos e deixar os jobs em execução terminarem.
2. Se o problema estiver apenas no Rex, voltar o artefato do Rex para o commit
   anterior; os campos adicionais do WS são aditivos e podem permanecer.
3. Se o WS estiver incorreto, voltar sua versão somente após retirar o Rex que
   depende dos novos dados, preservando a ordem inversa do rollout.
4. Para ENG-1261, executar o `down` da migration somente após confirmar que a
   versão anterior não lê `concluded_at`.
5. Para ENG-1263, voltar backend e frontend juntos; tarefas multi já criadas
   exigem manter código compatível com `stock_io_id = null`, portanto rollback
   para uma versão sem essa leitura requer bloqueio operacional e avaliação dos
   registros existentes.
6. Não apagar jobs, tarefas ou pivôs para simular rollback.

## Riscos e mitigação

| Risco | Mitigação |
|---|---|
| WS sem filtros não cobre ativos fora da janela | Comando direcionado por identificadores locais |
| Rex publicado antes do WS | Gate operacional WS -> Rex -> ressincronização |
| Alias/encoding do WS divergente | Teste em homologação antes do deploy do Rex |
| Backfill bloquear tabela | Medir volume, janela observada e índice após backfill |
| Agregação duplicar peso | Subconsulta agregada e fixtures com vários produtos |
| Concorrência alocar o mesmo saldo | Locks ordenados e revalidação transacional |
| Consumidor assumir `stock_io_id` direto | Resolver canônico e matriz single/multi |
| N+1 em listagens/relatórios | Eager loading, contagem de queries e revisão de plano |
| PDF quebrar em grupos longos | Artefatos completo/resumido e inspeção via Poppler |
| Regressão no Rex-Estoque | Manter rotas/resources legados e testes de contrato |

## Evidências obrigatórias antes de liberar

- testes Unit/Feature de Integration, ShippingPackaging, PickingOrder, StockIO e
  Report;
- migrations `up/down/up` em PostgreSQL;
- teste concorrente real com duas conexões/processos PostgreSQL;
- `EXPLAIN (ANALYZE, BUFFERS)` da agregação em massa representativa;
- PDFs single com/sem representante, multi completo e multi resumido;
- `pdfinfo`, `pdftoppm` e inspeção visual de QR Code, nomes e quebras;
- testes `node:test`, lint e build do frontend;
- revisão de transações, N+1, compatibilidade single/multi e contratos legados;
- confirmação de ausência de alterações no Rex-Estoque e nas cópias principais.
