Modelagem de dados — PIED e X-Adm¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-07-11
O modelo de dados consolidado da integração. São duas famílias de tabelas, no mesmo Postgres compartilhado, com donos distintos:
| Família | Dono (Flyway) | Papel | Sincroniza p/ o cliente? |
|---|---|---|---|
Espelho do X-Adm (contratos, propriedades, fones, itensped, estoque) |
Integrador | Estado desejado no formato do ERP — o alvo da integração | ✅ sim (PowerSync) |
PIED (pied_*) |
maxsul-pied | Captura crua da PIED + normalização interna | ❌ não (interno) |
O maxsul-pied grava na família espelho pela API REST do Integrador (/api/v1/xadm), nunca por
SQL direto (ver Projeto §2.2). A transformação de uma família na outra — de-para, regras
de negócio e códigos de retorno — vive em Mapeamento e regras.
1. Família espelho do X-Adm (dono: Integrador)¶
Contrato autoritativo: documento interno MAXSUL2025_610110. Cinco entidades que reproduzem o modelo do ERP. Este schema é consolidado aqui e replicado no repositório do Integrador (que é o dono do Flyway).
Modelo único do X-Adm. Estas tabelas são as mesmas para todos os clientes do Integrador — não são específicas da Maxsul. Uma
contratos(chaveChaveCP) serve compra e venda; cada fluxo preenche o subconjunto de colunas que usa. A integração da Maxsul usa as colunas de venda abaixo — algumas podem precisar entrar no molde compartilhado do Integrador, sem colidir com outros clientes.
Convenções (valem para as cinco tabelas)¶
- Chave natural da fonte PIED (ex.:
xPed,CodigoAlt,CgcCpf) +idUUID v7 surrogate (Postgres 18uuidv7(), ordenável por tempo) + soft-delete (deleted BOOLEAN,deleted_at).Nota (identidade no X-Adm).
xPed/CodigoAltsão códigos da PIED (transporte): servem de chave natural da entrada enquanto o X-Adm ainda não gerou a sua identidade (Chave*—ChaveCP/ChaveEst). No espelho do integrador eles são armazenados prefixadospied_*(rastreabilidade), e a identidade do produto éChaveEst/CodProd— nunca o código do parceiro. O fio (JSON que este projeto posta) segue comCodigoAlt/xPedinalterado. Ver decisão 0013 do integrador. - Tipos ZIM → Postgres:
VastInt(a-b dec)→NUMERIC;Date(8)(AAAAMMDD) →DATE;Char(n)/VarAlpha(n)→TEXT/VARCHAR. - Colunas preenchidas pelo lado X-Adm (chegam vazias, voltam pelo write-back, §5 do livro):
Chave*(ChaveCP,ChaveProp,ChaveEst,ChaveFone,ChaveItem) eCodRetorno/MsgRetorno. - Colunas só do PowerSync (sequenciais gerados na gravação):
idempropriedadeseitensped(marcadas ⟳ abaixo).
1.1 contratos — cabeçalho do pedido¶
| Campo | Tipo | Tam | Conteúdo |
|---|---|---|---|
xPed 🔑 |
Char | 15 | Código do pedido de venda no PIED. |
CgcCpfCliente |
Char | 14 | CNPJ ou CPF do cliente. |
ChaveUnid |
Char | 5 | Centro de custos do pedido. |
CodMat |
Char | 10 | Chave do funcionário que cadastrou o pedido. |
DtEm |
Date | 8 | Data de emissão. |
DataInc |
Date | 8 | Data de inclusão. |
HoraInc |
Char | 4 | Hora de inclusão. |
VlTot |
VastInt | 0-2 dec | Valor do pedido. |
NatOp |
Char | 5 | Filtro de contabilização (natureza de operação). |
Compl |
Char | 2 | Complemento do filtro de contabilização. |
TpVda |
Char | 1 | F = FOB, C = CIF. |
ClassForma |
Char | 2 | Forma de pagamento. |
Prazo |
Char | 8 | Data de entrega. |
ChaveCP ⬅X-Adm |
Char | 20 | Chave unique do pedido no X-Adm (gerada na inclusão). |
ChaveProp ⬅X-Adm |
Char | 11 | Chave unique do cliente no X-Adm. |
CodRetorno ⬅X-Adm |
Char | 3 | Código de retorno do cadastro. |
MsgRetorno ⬅X-Adm |
VarAlpha | 128 | Mensagem de retorno. |
1.2 propriedades — cliente / endereço¶
| Campo | Tipo | Tam | Conteúdo |
|---|---|---|---|
id ⟳🔑 |
VastInt | — | Sequencial unique (gerado no PowerSync). |
CgcCpf |
Char | 14 | CNPJ ou CPF do cliente. |
CodProp |
Char | 2 | Código do endereço do cliente. |
NomeProp |
VarAlpha | 50 | Nome/razão social do cliente. |
Fantasia |
VarAlpha | 30 | Nome fantasia. |
Endereco |
VarAlpha | 60 | Endereço. |
CEP |
Char | 8 | CEP. |
Bairro |
VarAlpha | 20 | Bairro. |
Cidade |
VarAlpha | 30 | Cidade (o X-Adm tem tabela de municípios). |
Estado |
Char | 3 | Estado (gravar com um espaço antes, totalizando 3 chars). |
Numero |
Char | 6 | Número do endereço. |
InscEst |
Char | 15 | Inscrição Estadual ou RG. |
ChaveProp ⬅X-Adm |
Char | 11 | Chave unique do cliente no X-Adm. |
CodRetorno ⬅X-Adm |
Char | 3 | Código de retorno. |
MsgRetorno ⬅X-Adm |
VarAlpha | 128 | Mensagem de retorno. |
1.3 fones — contato do cliente¶
| Campo | Tipo | Tam | Conteúdo |
|---|---|---|---|
CgcCpf 🔑 |
Char | 14 | CNPJ/CPF do cliente (no X-Adm relaciona por ChaveProp). |
DDD |
Char | 5 | DDD do contato. |
Telefone |
Char | 10 | Telefone do contato. |
Nome |
VarAlpha | 50 | Nome do contato. |
Email |
VarAlpha | 100 | E-mail do contato. |
ChaveFone ⬅X-Adm |
Char | 14 | Chave unique do contato no X-Adm. |
ChaveProp ⬅X-Adm |
Char | 11 | Chave unique do cliente no X-Adm. |
CodRetorno ⬅X-Adm |
Char | 3 | Código de retorno. |
MsgRetorno ⬅X-Adm |
VarAlpha | 128 | Mensagem de retorno. |
Divergência decidida da doc-mãe (MAXSUL2025_610110): o
fonesagora carregaCodRetornocomo as demais espelho — princípio universal (toda tabela espelho carrega seu código de retorno, para oGET /retorno/{tabela}responder uniforme, api-integrador §7). O livro original omitiaCodRetornoemfones.
1.4 itensped — itens do pedido¶
| Campo | Tipo | Tam | Conteúdo |
|---|---|---|---|
id ⟳🔑 |
VastInt | — | Sequencial unique (gerado no PowerSync). |
xPed |
Char | 15 | Código do pedido no PIED. |
CodigoAlt |
Char | 15 | Código do produto no PIED. |
Qtde |
VastInt | 0-3 dec | Quantidade. |
Valor |
VastInt | 0-5 dec | Preço unitário. |
Total |
VastInt | 0-2 dec | Valor total do item. |
TaxaFrete |
VastInt | 0-5 dec | Valor unitário do frete. |
ChaveCp ⬅X-Adm |
Char | 20 | Chave unique do pedido no X-Adm. |
ChaveEst ⬅X-Adm |
Char | 11 | Chave unique do produto no X-Adm. |
ChaveItem ⬅X-Adm |
Char | 27 | Chave unique do item no X-Adm. |
CodRetorno ⬅X-Adm |
Char | 3 | Código de retorno. |
MsgRetorno ⬅X-Adm |
VarAlpha | 128 | Mensagem de retorno. |
No caso Kit (§ Mapeamento), o item usa também
DescCompl(Char) para classificar o gerador por potência — coluna auxiliar do transform, ver Mapeamento.
1.5 estoque — produto¶
| Campo | Tipo | Tam | Conteúdo |
|---|---|---|---|
CodigoAlt 🔑 |
Char | 15 | Código do produto no PIED. |
EAN13 |
Char | 13 | Código de barras. |
CodProdAlt |
Char | 8 | Código do produto base a partir do qual o novo estoque é cadastrado. |
NomeProd |
VarAlpha | 128 | Nome do produto. |
Venda |
VastInt | 0-5 dec | Valor unitário de venda. |
VendaPz |
VastInt | 0-5 dec | Valor unitário de venda a prazo. |
Saldo |
VastInt | 0-3 dec | Saldo no X-Adm. |
ChaveEst ⬅X-Adm |
Char | 11 | Chave unique do produto no X-Adm. |
CodRetorno ⬅X-Adm |
Char | 3 | Código de retorno. |
MsgRetorno ⬅X-Adm |
VarAlpha | 128 | Mensagem de retorno. |
🔑 chave de negócio · ⟳ sequencial só do PowerSync · ⬅X-Adm preenchido pelo ERP (write-back).
1.6 Relacionamentos¶
| Chave primária | Chave estrangeira |
|---|---|
propriedades.CgcCpf |
contratos.CgcCpfCliente |
propriedades.CgcCpf |
fones.CgcCpf |
propriedades.ChaveProp |
contratos.ChaveProp |
propriedades.ChaveProp |
fones.ChaveProp |
contratos.ChaveCP |
itensped.ChaveCP |
contratos.xPed |
itensped.xPed |
estoque.ChaveEst |
itensped.ChaveEst |
estoque.CodigoAlt |
itensped.CodigoAlt |
erDiagram
PROPRIEDADES ||--o{ CONTRATOS : "CgcCpf / ChaveProp"
PROPRIEDADES ||--o{ FONES : "CgcCpf / ChaveProp"
CONTRATOS ||--o{ ITENSPED : "xPed / ChaveCP"
ESTOQUE ||--o{ ITENSPED : "CodigoAlt / ChaveEst"
2. Família PIED (dono: maxsul-pied)¶
Tabelas internas do maxsul-pied, não sincronizadas. Dividem-se em landing (captura crua) e
normalizadas (fase 2).
2.1 Landing — captura raw (fase 1, em produção)¶
Criadas pela migração Flyway V1__landing_pied.sql. Guardam o payload como chegou — o transform
da fase 2 lê daqui. A granularidade é fiel e barata (a página/o POST inteiro, não o item explodido).
erDiagram
PIED_WEBHOOK {
uuid id PK "default uuidv7()"
text evento "campo event do payload (nullable)"
jsonb payload "corpo cru (NOT NULL)"
jsonb headers "headers HTTP, credenciais mascaradas"
timestamptz recebido_em "default now()"
}
PIED_REST {
uuid id PK "default uuidv7()"
text entidade "produtos | clientes | pedidos"
int pagina "1-based"
jsonb payload "envelope completo da página (NOT NULL)"
int total_items "data.totalItems"
timestamptz buscado_em "default now()"
}
PIED_CURSOR {
text entidade PK "produtos | clientes | pedidos"
date last_update_after "cursor do filtro incremental (só pedidos)"
int ultima_pagina "checkpoint do backfill (0 = recomeça da pág 1)"
timestamptz atualizado_em
}
PIED_CURSOR ||--o{ PIED_REST : "governa o delta de pedidos"
pied_webhook— uma linha por POST recebido.eventoextraído do campoeventquando presente (order.created,budget.updated…); corpo não-JSON é envelopado como string JSON (payloadéNOT NULL, nada é rejeitado).headersguarda a requisição para diagnóstico, comX-Pied-Secret/Authorizationmascarados.pied_rest— uma linha por página buscada no poll; opayloadé o envelope{error, data:{items, totalItems}}completo.pied_cursor— estado do poll por entidade.last_update_afteré a data usada como?lastUpdateAfterna próxima rodada; só pedidos usam (produtos/clientes são sempre varridos por inteiro — a PIED ignora o filtro neles) e só passa a valer após o backfill completar.ultima_paginaé o checkpoint do backfill: como a cota horária da PIED (429) interrompe um scan grande no meio, guarda-se a última página capturada para retomar dali na próxima rodada em vez de re-escanear do início (V2; migração da premissa "cursor avança só ao completar", que gerava re-scan infinito — pendências P1, decisão do 429). Ao completar (página vazia), zera-seultima_paginae, em pedidos, liga-se olast_update_afterincremental.- Índices:
pied_webhook (recebido_em),pied_webhook (evento),pied_rest (entidade, buscado_em).
Nomenclatura (definida): as landing ficam como estão —
pied_webhook,pied_rest,pied_cursor; a fase 2 apenas acrescenta as normalizadas (pied_produto/pied_cliente/pied_pedido), sem renomear.Retenção (definida): o landing
pied_*é mantido indefinidamente enquanto barato — é a trilha de auditoria e a fonte de reprocessamento da fase 2. Expurgo só se o volume medido no MVP exigir; nesse caso, arquivar/expurgar páginas antigas depied_restjá processadas, preservandopied_webhook.
2.2 Normalizadas — negócio interno (materializadas na V3__normalizadas.sql)¶
O transform explode o raw em tabelas de negócio próprias, fonte estável e deduplicada do de-para.
Criadas pela migração V3__normalizadas.sql (Postgres 18, uuidv7() surrogate + chave natural +
soft-delete); o NormalizadorService faz upsert idempotente por chave natural a partir de
pied_rest/pied_webhook. Schema exato: a migração é a fonte (não duplicado aqui).
| Tabela | Chave natural | Origem | Colunas típicas |
|---|---|---|---|
pied_produto |
product_code |
poll equipments |
name, manufacturer, model, baseprice, payload |
pied_cliente |
documento (CNPJ/CPF só dígitos) |
poll companies + company embutido no pedido |
company_name, fantasy_name, state_inscription (InscEst), payload |
pied_pedido |
code |
poll requests/order + webhook order.* |
pied_id (→ xPed), documento_cliente, deal_status (+ _anterior), máquina de status, payment_status (+ _anterior), pago_em, parcial_desde, cancelado_pied_em, importacao_manual_obs/_em (V12, "Importado manualmente"), alerta_cod_retorno (V13, dedup do alerta de erro terminal), last_update, payload |
pied_pedidoé a estrutura central: carrega a máquina de status da entrega (CAPTURADO → NA_FILA → ENVIANDO → ENVIADO → CONFIRMADO | ERRO_XADM;ERRO= falha de push) e as colunas de retorno (cod_retorno/msg_retorno/chave_xadm,content_hash). Elas nascem na V3, mas só são dirigidas nas fatias de push/reconciliação (adiante na Fase 2). Oalerta_cod_retornoguarda o último código 1XX já alertado por e-mail: a reconciliação só envia quando consegue gravar um código diferente, e o campo zera quando o pedido confirma ou é reenfileirado.- Colunas de pagamento (carimbos por transição no upsert):
pago_em(→received, gate de envio),parcial_desde(→partial, aviso de parcial travado >5 dias) ecancelado_pied_em(set-once,CONFIRMADO→cancelled, dispara o e-mail de cancelamento pós-import). Ver decisão 0010. pied_produtovem só do pollequipments; osproducts[]embutidos no pedido são consumidos inline pelo de-para, não materializados — evita sobrescrever os campos ricos do produto com o item esparso do pedido (o pedido cru fica empied_pedido.payload).- Orçamento (
budget) não vira tabela normalizada — não vai ao X-Adm. - Recência do evento (V11): o upsert de
pied_pedidorecusa payload cujolastUpdateseja anterior ao já persistido — snapshot atrasado não reaplica umpayment.statusvencido nem dispara os carimbos de transição. Empate (>=) passa, porque é o enriquecimento rebuscando o mesmo snapshot peloinvoice.upsertdevolvebooleane o alerta de cancelamento pós-import só sai quando a linha foi mesmo alterada. A comparação é no segundo cheio (date_trunc('second', …)dos dois lados): webhook e REST carimbam o mesmo instante com precisões diferentes (.512Zvs. segundo cheio), e comparar cru prendia o pedido emNA_FILApara sempre — ver 0018 A.1. - Cursor da normalização (
pied_normalizacao_estado, V11): singletonid = 1com omax(recebido_em/buscado_em)do raw já normalizado — a rodada lê só o raw novo (mais uma margem de 10 min, contra commit fora de ordem) em vez de reprocessar a história inteira a cada 5 min. Reprocesso completo =UPDATE pied_normalizacao_estado SET ate_ts = NULL. Motivação e trade-offs na decisão 0018.