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: umPUTposterior (de outro pedido do mesmo cliente) atualiza o dado no espelho. Mas o espelho preserva ocod_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 no006). Desde 2026-09-17 o Integrador rearma sozinho a linha006cujo dado mudou (volta a000para recoleta);1XXsegue 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.Emailfica 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 deNatOp, masdata.type="order"e o rótulo "Kit" aparece emdata.kind("Kit Express"). Adotamoskind; confirmar com dado real. - Cliente, frete e pagamento vêm no próprio pedido, mas só no REST:
data.invoice(cliente faturado) edata.freightnão vêm no webhookorder.*(subconjunto). Para não perdê-los quando um webhook chega depois de um poll REST, o upsert dopied_pedido.payloadfaz 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 nopied_cliente). O pollv1/companies/ webhookcompany.*seguem alimentandopied_cliente(catálogo do Integrador), keado pelo doc docompany.
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. Ocompanysegue alimentando só o catálogopied_clienteno normalizado — não apropriedades/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) usainvoice.address.state(nota), que pode divergir depropriedades.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) comcpfpresente;PJ=cpfnulo (invoice só comcnpj, ou sem doc). Segue a mesma fonte fiscal da UF —invoice, nãocompany(revenda, sempre CNPJ) — por decisão 0009.Compl=1só em SC não-Kit;0nos 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*/CodRetornonão são do int-pied. - Guard: componente sem
productCodeou semquantitynão vira linha. - Premissa: os
productCodede componente já existem no catálogo do X-Adm — o int-pied não emiteestoquedeles (só referencia). Pedido pago é imutável → emite uma vez, sem remoção/DELETE. - O transform sempre emite o array
formulasnoPUT(o integrador-server/013 trata o array). Ocontent_hashabraçaformulasautomaticamente.
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; opied_clienteguarda 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.DescCompldo Kit (§4.2) não é enviado. A tabelaitenspeddo Integrador não tem essa coluna; a classificação de potência do Kit vive emestoque.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, DDI55opcional). O de-para descarta o DDI e fatia DDD (2) + número. PrazoeEAN13em 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.