Pular para conteúdo

0009 — Cliente do transform vem da aba Faturamento (invoice), não do Integrador (company)

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

Contexto

Os pedidos entravam no X-Adm com o cliente errado. O TransformService montava a identidade do cliente (propriedades/fones/contratos) 100% de data.company — a aba Integrador da PIED, que é a revenda/instaladora (CNPJ, ex. ARGON SOLAR LTDA). O X-Adm precisa do cliente final faturado — a aba Faturamento = data.invoice (tipicamente pessoa física/CPF, ex. IVO ANZINI). Bug reportado pela Maxsul (Felype, 2026-08-10), confirmado no pedido #260068273 e no dump json.json (243 pedidos REST únicos: 63 CNPJ / 180 CPF em invoice).

A raiz é dupla:

  1. o transform lê a aba errada (company em vez de invoice);
  2. invoice e freight só existem no REST — o webhook de pedido é um subconjunto (0/150 webhooks trazem invoice/freight). O upsertPedido gravava order.toString() inteiro no pied_pedido.payload, então um order.updated de webhook chegando depois de um poll REST apagava invoice/freight. Sem corrigir (2), corrigir (1) não se sustenta.

Decisão

Identidade do cliente = data.invoice sempre. company deixa de alimentar propriedades/fones/ contratos; segue alimentando só o catálogo pied_cliente no normalizado (keado pelo doc do company).

  • Documento (CgcCpf/CgcCpfCliente) = digitos(invoice.cnpj || invoice.cpf). O pied_pedido.documento_cliente passa a ser o doc do invoice; o pied_cliente segue keado pelo doc do company (dois documentos distintos no upsertPedido).
  • Nome/Fantasia/Contato = invoice.razaoSocial/invoice.nomeFantasia/invoice.telephone/ invoice.email (o invoice não tem mainContact — o nome do contato é a razão social).
  • InscEst = invoice.ie inline (dropa o lookup no pied_cliente para o cliente).
  • Endereço = entrega quando divergir, senão faturamento, com discriminador robusto CIF + completude (não diff cru):
fonte = freight.address   SE (freight.type == "CIF"  E  freight.address completo)
        senão invoice.address
completo := CEP != null  E  patio != null  E  number != null

No FOB o freight.address traz só city/state (incompleto) → cai no faturamento. Dump: FOB 0/12 completo → faturamento; CIF 230/231 completo → entrega. Endereço único (CodProp " 0") — não se manda faturamento+entrega em dois CodProp (evita travar a ordem de fabricação do X-Adm). - UF fiscal do NatOp = invoice.address.state (nota). Pode divergir de propriedades.Estado (entrega) num CIF interestadual — divergência intencional. - Merge no upsert: NormalizadorService.upsertPedido lê o pied_pedido.payload existente e mescla o pedido novo sobre ele no nível de topo (setAll só das chaves que a fonte nova traz não-nulas; ausentes são preservadas). Corrige o cliente e o bug latente do frete. Vale para os 3 callers (normalizarCronologico, normalizarPedidosElegiveis, renormalizar). - Guarda de envio: pedido sem invoice (só-webhook) não é enviado — PushService.enviarPedido devolve PULADO (estado transitório até o REST preencher), sem marcar ERRO.

Alternativas preteridas

  • Manter company como cliente — é a revenda, não o comprador faturado (o bug).
  • Discriminar endereço por type=='FOB' na unha — a completude do freight.address é mais robusta (cobre o CIF anômalo com endereço incompleto).
  • Coluna invoice dedicada no schema — não corrige o frete; o merge shallow já resolve sem migração.
  • Deep-merge do payload — as chaves em jogo (invoice/freight/payment) são de topo; shallow basta.

Consequências

  • Sem tabela nova, sem migração. Tudo deriva do raw pied_pedido.payload (REST).
  • Mudança de identidade canônica (o doc do cliente passa a ser do invoice): entra com reset da base do zero, sem backfill/re-envio dos pedidos existentes (ver reset).
  • Tradeoff do merge (read-modify-write): o upsertPedido deixa de ser função pura do input — passa a depender do estado no banco; uma chave de topo escrita não é apagada por um snapshot posterior que a dropa (intencional — nunca perder invoice/freight). A ordem cronológica global + merge converge.
  • invoice.ie presente em só 59/243 no dump — os demais gravam InscEst em branco (coerente com invoice.temInscricaoEstadual == "nao").

Em aberto

  • UF fiscal do NatOp no CIF interestadual: default invoice.address.state; confirmar com a equipe X-Adm se não deveria ser freight.address.state (reversão = 1 linha em natOpCompl).
  • Deprecar pied_cliente / poll companies? Perdem parte do propósito (InscEst agora inline); decisão adiada.