Pular para conteúdo

Mapeamento e regras de negócio — PIED → X-Adm

Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-07-21

Como o dado da PIED vira o estado desejado nas tabelas espelho do X-Adm (Modelagem §1). Fonte autoritativa: documento interno MAXSUL2025_610110. O transform lê o raw/normalizado (Modelagem §2) e produz os registros que o maxsul-pied empurra ao Integrador via /api/v1/xadm (Projeto §2.2).

Entrada de referência: o payload de webhook { "event": "order.created", "data": { … } }, com company e products[] embutidos (exemplo completo no MAXSUL2025_610110).


1. Regras de negócio transversais

  • Só pedido finalizado vai ao X-Adm. O X-Adm faz apenas INSERT de pedido — nunca UPDATE. Um pedido só é enviado quando finalizado na PIED; antes disso fica retido no landing. Depois de enviado, alteração na PIED não reflete no X-Adm por este caminho (caminho só de ida — pedido enviado nunca volta à fila). O campo que marca "finalizado" será determinado a partir dos dados raw da Fase 1 (decisão adiada de propósito — evidência antes de suposição, decisão 0001).
  • Corolário — cadastros compartilhados (propriedades/fones/estoque). A chave deles é o documento/produto, não o pedido: um PUT posterior (de outro pedido do mesmo cliente) atualiza o dado no espelho. Mas o espelho preserva o cod_retorno (mão única) — sem re-arme, o ERP nunca é reavisado e o console exibe o dado novo como se entregue fosse (caso 260081441, endereço corrigido no espelho e parado no 006). Desde 2026-09-17 o Integrador rearma sozinho a linha 006 cujo dado mudou (volta a 000 para recoleta); 1XX segue com o operador.
  • Campo não informado grava em branco, não em nulo.
  • Documentos e datas normalizados: CNPJ/CPF/CEP/telefone em dígitos (sem máscara); datas derivadas de data.lastUpdate.
  • Saneamento de texto (convenção X-Adm, decisão 0007): campos texto-livre viajam sem diacríticos e em caixa alta (NFD + strip \p{Mn} + toUpperCase(ROOT)). Allowlist: NomeProp, Fantasia, Endereco, Bairro, Cidade, Numero, fones.Nome, NomeProd. Email fica de fora (local-part case-sensitive por RFC); códigos, dígitos e UF também não são saneados.
  • Kit × não-Kit decide a explosão de itens/estoque (§4). Detecção por data.kind like "%Kit%".

    ⚠️ O MAXSUL2025_610110 usa data.type like "%Kit%" na regra de NatOp, mas data.type = "order" e o rótulo "Kit" aparece em data.kind ("Kit Express"). Adotamos kind; confirmar com dado real.

  • Cliente, frete e pagamento vêm no próprio pedido, mas só no REST: data.invoice (cliente faturado) e data.freight não vêm no webhook order.* (subconjunto). Para não perdê-los quando um webhook chega depois de um poll REST, o upsert do pied_pedido.payload faz merge shallow de topo (chave que a fonte nova traz não-nula vence; ausente é preservada) — ver decisão 0009.
  • A inscrição estadual do cliente vem inline em data.invoice.ie (dropa o lookup no pied_cliente). O poll v1/companies / webhook company.* seguem alimentando pied_cliente (catálogo do Integrador), keado pelo doc do company.

2. Cliente → propriedades + fones (1 registro cada)

O cliente é a aba Faturamento (data.invoice), NÃO o Integrador (data.company) (decisão 0009). company é a revenda/instaladora (CNPJ); invoice é o comprador faturado (tipicamente CPF), que é quem o X-Adm precisa. O company segue alimentando só o catálogo pied_cliente no normalizado — não a propriedades/ fones.

propriedades

Campo X-Adm Origem / regra
CgcCpf data.invoice.cnpj se nulo, senão data.invoice.cpf (dígitos).
NomeProp data.invoice.razaoSocial.
Fantasia data.invoice.nomeFantasia.
Endereco/CEP/Bairro/Cidade/Estado/Numero do endereço escolhido (ver abaixo): patio/CEP/neighborhood/city/state/number. Estado com um espaço antes (3 chars).
InscEst data.invoice.ie inline (vem no próprio pedido; dropa o lookup no pied_cliente), só-dígitos (a PIED manda formato inconsistente — ex. MT 13.821.564-2; IE é sempre numérica). Ausente → branco.
CodProp fixo " 0" (um registro por documento; endereço único — ver decisão 0009).

Endereço da propriedade — entrega × faturamento (decisão 0009):

fonte = data.freight.address   SE (freight.type == "CIF"  E  freight.address completo)
        senão data.invoice.address
completo := CEP != null  E  patio != null  E  number != null
  • CIF com endereço completo → endereço de entrega (freight.address).
  • FOB (traz só city/state) ou CIF incompleto → faturamento (invoice.address).
  • A UF fiscal do NatOp (§3) usa invoice.address.state (nota), que pode divergir de propriedades.Estado (entrega) num CIF interestadual — divergência intencional.

fones

Campo X-Adm Origem / regra
CgcCpf igual à propriedades (doc do invoice).
Nome data.invoice.razaoSocial (invoice não tem mainContact).
DDD data.invoice.telephone — parte dentro dos parênteses.
Telefone data.invoice.telephone — parte fora dos parênteses.
Email data.invoice.email.

3. Pedido → contratos (1 registro)

Campo X-Adm Origem / regra
xPed data.code (número do pedido; cabe no CHAR(15) do X-Adm — data.id é ObjectId de 24).
CgcCpfCliente data.invoice.cnpj se nulo, senão data.invoice.cpf (dígitos) — cliente = Faturamento.
ChaveUnid fixo " 1 29".
CodMat fixo " 1 0 134".
DtEm / DataInc / HoraInc data.lastUpdate.
VlTot data.finalValue + data.invoice.serviceToAddInvoiceValue (JSON não traz o total da NF; serviço ausente = 0).
NatOp regra por estado × Kit (§4.1).
Compl regra por estado × Kit (§4.1).
TpVda data.freight.type: FOB → F; qualquer outro (inclui CIF) → C (default) — via poll REST. Frete ausente → branco (em prod 100% dos pedidos trazem CIF/FOB).
ClassForma de-para de data.payment.type/condition → código X-Adm — de-para a definir (§5).
Prazo data de entrega — confirmar se há campo na PIED (§5).

4. Produto e item → estoque + itensped

A explosão depende de o pedido ser um Kit (data.kind like "%Kit%").

4.1 NatOp / Compl (cabeçalho contratos)

Estado Kit? Doc NatOp Compl
SC sim — 5.101 0
≠ SC sim PJ (invoice.cpf nulo) 6.101 0
≠ SC sim PF (invoice.cpf ≠ nulo) 6.107 0
SC não — 5.102 1
≠ SC não PJ (invoice.cpf nulo) 6.102 0
≠ SC não PF (invoice.cpf ≠ nulo) 6.108 0

PF × PJ só distingue fora de SC. PF = cliente faturado (data.invoice) com cpf presente; PJ = cpf nulo (invoice só com cnpj, ou sem doc). Segue a mesma fonte fiscal da UF — invoice, não company (revenda, sempre CNPJ) — por decisão 0009. Compl = 1 só em SC não-Kit; 0 nos demais (independe de PF/PJ).

4.2 Se Kit (data.kind like "%Kit%") — 1 item sintético

O Kit vira um gerador fotovoltaico, classificado pela potência (data.totalPower).

itensped (1 registro):

Campo Regra
xPed data.code (número do pedido; cabe no CHAR(15) do X-Adm — data.id é ObjectId de 24).
CodigoAlt data.code (mesmo do estoque[0] — o item referencia o produto do kit; o Integrador resolve itensped por (xPed, CodigoAlt), então não pode ser vazio).
DescCompl " 1 0E 20000" se totalPower ≤ 75; " 1 0E 20001" se > 75.
Qtde fixo 1.
Valor / Total data.finalValue + data.invoice.serviceToAddInvoiceValue (JSON não traz o total da NF; serviço ausente = 0).
TaxaFrete data.freight.price (nível pedido) — via poll; ratear por item se necessário.

estoque (1 registro):

Campo Regra
CodProdAlt " 20000" se totalPower ≤ 75; " 20001" se > 75.
CodigoAlt data.code (número do pedido; cabe no CHAR(15) do X-Adm — data.id é ObjectId de 24).
NomeProd "GERADOR FOTOVOLTAICO DE CORRENTE CONTINUA COM POTENCIA " + totalPower + " kW".
Venda data.finalValue.
VendaPz data.originalValue.

4.3 Se não-Kit — 1 item por produto

Para cada data.products[]:

itensped:

Campo Regra
xPed data.code (número do pedido; cabe no CHAR(15) do X-Adm — data.id é ObjectId de 24).
CodigoAlt products[].productCode.
Qtde products[].quantity.
Valor products[].singlePrice.
Total products[].totalPrice.
TaxaFrete data.freight.price (nível pedido) — via poll; ratear por item se necessário.

estoque:

Campo Regra
CodProdAlt fixo "".
CodigoAlt products[].productCode.
NomeProd products[].name.
Venda / VendaPz products[].singlePrice.

4.4 Se Kit — FORMULAS (BOM), 1 linha por componente

Além do item sintético (§4.2), um pedido kit emite a fórmula (BOM): um registro por data.products[] (os componentes reais do kit). Não-kit não gera fórmula. Ver decisão 0009 para o cliente e a spec .ia/006.

Campo (wire) Regra
xPed data.code.
CodProd products[].productCode (código do componente no X-Adm — CodProd, não CodigoAlt; numérico 5 díg, ex. 22162).
Qtde products[].quantity.
  • Só esses 3 campos. pied_formula_id/Chave*/CodRetorno não são do int-pied.
  • Guard: componente sem productCode ou sem quantity não vira linha.
  • Premissa: os productCode de componente já existem no catálogo do X-Adm — o int-pied não emite estoque deles (só referencia). Pedido pago é imutável → emite uma vez, sem remoção/DELETE.
  • O transform sempre emite o array formulas no PUT (o integrador-server/013 trata o array). O content_hash abraça formulas automaticamente.

5. Lacunas — campos que a API PIED não fornece

Campos que ainda dependem de regra de negócio ou confirmação (ver Pendências):

Campo X-Adm Entidade Situação
ClassForma (forma de pagamento) contratos a PIED expõe payment.type/condition; falta o de-para para o catálogo X-Adm (01 Dinheiro … 17 PIX … 99 Outros).
Prazo (data de entrega) contratos confirmar se a PIED tem o campo de data de entrega.
EAN13 estoque código de barras não vem da API — deixar em branco (enriquecer depois, se necessário).

Reclassificados como resolvíveis (o MAXSUL2025_610110 os dava como "não tem na API", mas a API validada os fornece): TpVda ← freight.type, TaxaFrete ← freight.price, InscEst ← data.invoice.ie (inline no pedido — §2).


6. Códigos de retorno (máquina de status)

O resultado da gravação no X-Adm volta pelo write-back nas colunas CodRetorno/MsgRetorno (Modelagem §1), percorrendo os códigos definidos no MAXSUL2025_610110:

Faixa Código Significado
Processamento (0XX) 000 Novo no PowerSync, não cadastrado no X-Adm.
001 Em processamento no PowerSync.
002 Em processamento no X-Adm.
003 Gravando retorno do X-Adm.
006 Gravado no X-Adm.
Erro terminal (1XX) 110 Inconsistência de dígito verificador.
111 Ausência de dado obrigatório.
120 Ausência de cadastro auxiliar (ex.: município não cadastrado).
121 Inconsistência no cadastro.
Erro de processamento (9XX) 980 Falha na execução do X-Adm.
999 Conflito de transação.

7. Divergências conscientes da implementação (Fase 2, 2026-07-13)

Onde o TransformService diverge deste documento — de propósito, porque o contrato real do Integrador e o modelo de dados normalizado mandam no que é possível/aceito:

  • CodProp é sempre " 0". A regra "endereço diferente → 1 + maior CodProp" (§2) pressupõe múltiplos endereços por documento; o pied_cliente guarda uma linha por documento (chave natural), então a regra não tem como disparar. Se a Maxsul precisar de múltiplos endereços, o modelo normalizado muda primeiro.
  • DescCompl do Kit (§4.2) não é enviado. A tabela itensped do Integrador não tem essa coluna; a classificação de potência do Kit vive em estoque.CodProdAlt (20000/20001), então nada se perde.
  • DDD/Telefone por dígitos, não por parênteses. O §2 supôs (42) 99999-0000; o dado real é corrido (5542999990000, DDI 55 opcional). O de-para descarta o DDI e fatia DDD (2) + número.
  • Prazo e EAN13 em branco (a PIED não os expõe) — regra "branco não nulo".
  • Rateio do frete: default = proporcional ao valor (§4, opção A), configurável para "total no 1º item" (opção B) via pied.integracao.transform.ratear-frete=false.