Pular para conteúdo

0017 — Enriquecimento sob demanda do pedido preso na fila

Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-08 · Decidido em: 2026-09-04

Contexto

O pedido chega por duas vias: webhook (tempo real) e poll REST (backstop, a cada 6h). O payload do webhook não traz o objeto invoice (aba Faturamento) — só o REST traz. E o invoice é o insumo do cliente do Faturamento (0009), então o PushService tem um guard de prontidão: sem invoice, o enfileiramento devolve PULADO — sem marcar ERRO, sem mudar o status.

Resultado: pedido pago que chega por webhook fica em NA_FILA até o próximo poll — até ~6h parado, sem erro, sem alerta e (antes da revisão do 0006) anunciado ao cliente como "Enviando ao X-Adm". Aconteceu em produção no 1º pedido real: 260078922 chegou por webhook às 14:17Z de 2026-09-04, ficou preso, e só saiu (ENVIADO 14:58Z, CONFIRMADO 15:00Z) depois de alguém puxar a página do REST na mão.

Restrição do contrato da PIED: não existe busca de pedido por id. Só GET /requests/order/{página}/{limite}?lastUpdateAfter=AAAA-MM-DD — ou seja, "buscar aquele pedido" é sempre varrer a janela daquela data. E a PIED impõe dois tetos por hora (chamadas e tempo de processamento), então varredura é um recurso escasso.

Decisão

O TransformJob abre cada rodada (5 min) chamando o novo integracao/EnriquecimentoService: havendo NA_FILA sem invoice, ele busca a janela no REST, grava o raw em pied_rest e a normalização da mesma rodada aplica o payload completo — o pedido destrava em ≤5 min em vez de ~6h.

  • No job que já sabe quem está preso, não num job novo: o TransformJob já roda a cada 5 min, já tem guarda de cluster (0014) e já é o dono da fila. Zero superfície nova (sem toggle, sem lock, sem @Scheduled extra).
  • Antes da normalização, não depois: assim a página baixada é aproveitada na mesma rodada.
  • Janela do preso mais antigo, menos 1 dia. lastUpdateAfter tem granularidade de dia e semântica after; usar a data exata arriscaria excluir o próprio pedido. Reprocessar um dia a mais é barato (upsert idempotente por code).
  • Custo contido, por construção: nenhuma chamada quando não há preso (o caminho comum); para assim que todos os presos apareceram numa página; teto de 5 páginas por rodada; e falha de rede/cota (429) vira WARN sem derrubar a rodada — é backstop, e o poll de 6h continua sendo a rede de segurança.
  • Só grava raw. Quem transforma em pied_pedido é a normalização, como no poll — o serviço não duplica regra de negócio nem avança o cursor do poll (é leitura lateral, como o "Puxar da PIED").

Alternativas consideradas

  • Disparar no webhook (event-driven puro), ao detectar order.* sem invoice: era o pedido literal ("assim que chegar o pagamento") e daria latência de segundos. Preterida: sem endpoint por id, cada disparo ainda varre páginas, e uma rajada de webhooks vira uma rajada de varreduras contra os tetos horários — precisaria de debounce e teto próprios para chegar onde o job já chega. Ganho real (5 min → segundos) não paga o risco de cota.
  • Job dedicado (EnriquecimentoJob, intervalo e toggle próprios): preterida por over-engineering — mais um @Scheduled, mais uma config, mais um advisory lock e mais testes para fazer o que o TransformJob faz na mesma cadência.
  • Deixar como estava (esperar o poll): rejeitada — 6h de atraso silencioso num pedido pago é o pior dos mundos: o cliente não vê erro, o operador não vê alerta e o X-Adm não recebe.
  • Encurtar PIED_POLL_INTERVALO: rejeitada — produtos/clientes não têm cursor incremental, então cada rodada re-varre tudo e incha pied_rest. Barateia o sintoma inflando o landing.

Consequências

  • Espera do pedido preso cai de ~6h para ≤5 min; o caso "preso" deixa de depender de alguém perceber e clicar em "Puxar da PIED" (que continua existindo como atalho manual).
  • Uma chamada REST a mais por rodada apenas quando há preso — o estado normal (fila pronta ou vazia) não gasta cota.
  • Novo componente EnriquecimentoService + PiedPedidoRepository.presosSemInvoice(...); o TransformJob ganhou uma dependência.
  • NA_FILA deixa de ser um estado de espera longa e passa a ser transitório. Pedido preso além de uma rodada vira anomalia — as causas residuais estão abaixo.

Quando ainda prende (e como sair)

O enriquecimento encurta a espera, não a elimina. Preso por mais de uma rodada é uma destas:

Causa Sinal
PIED_TOKEN vazio Enriquecimento: PIED_TOKEN vazio — pulado.
PIED_INTEGRACAO_HABILITADA=false O TransformJob sai antes de tudo — nem enriquece nem drena. Pedido posto em NA_FILA pelo gate manual do /console fica parado
Cota (429), PIED fora do ar, timeout Enriquecimento: falha ao buscar na PIED — … (retoma na próxima rodada).
Preso fora do teto de 5 páginas … N ainda sem dado. rodada após rodada
Pedido sumiu da janela na PIED Idem, e nunca resolve sozinho
Guard de recência recusando o REST O enriquecimento acha o pedido (0 ainda sem dado.), o pied_rest tem o invoice, e mesmo assim o pied_pedido.payload nunca ganha o invoice — ver 0018 A.1

Diagnóstico:

-- na fila E sem invoice = ainda esperando dado
SELECT code, deal_status, payment_status, atualizado_em
FROM pied_pedido
WHERE status = 'NA_FILA'
  AND jsonb_typeof(payload -> 'invoice') IS DISTINCT FROM 'object';

Se essa consulta acusa preso e o pied_rest já tem o invoice do mesmo code, o problema não é captura: é o guard de recência recusando o snapshot do REST — nesse caso nem "Puxar da PIED" nem "Re-normalizar" resolvem (ambos passam pelo mesmo upsert), e o conserto está em 0018 A.1. Comparar as duas fontes:

SELECT p.code, p.last_update AS no_pedido,
       (i ->> 'lastUpdate') AS no_rest,
       jsonb_typeof(i -> 'invoice') AS invoice_no_rest
FROM pied_pedido p
JOIN pied_rest r ON r.entidade = 'pedidos'
JOIN LATERAL jsonb_array_elements(r.payload -> 'data' -> 'items') i ON i ->> 'code' = p.code
WHERE p.status = 'NA_FILA'
ORDER BY r.buscado_em DESC LIMIT 20;

Conserto manual — o botão "Puxar da PIED" do /console (peek: grava o raw, não avança o cursor, idempotente por code; n aceita 1 ou 5 e só normaliza os estados-venda):

curl -X POST "https://pied.maxsul.xadm.biz/console/puxar-pied?n=5" \
  -H "Content-Type: application/x-www-form-urlencoded" --data ""

Depois disso o pedido sai na rodada seguinte (≤5 min), ou na hora pelo Enviar do detalhe (POST /console/{code}/enviar). O CONFIRMADO + chave_xadm fecham pelo ReconciliacaoJob.

Relacionadas