Pular para o conteúdo principal

De-para: da resposta da fonte ao canônico

O de-para (responseMapping) é o que transforma a resposta crua de um ERP no registro canônico do SFA. Ele é configuração, não código: quem integra escreve regras na tela do conector, e o runtime as interpreta a cada execução.

Onde fica: Integrações → Conectores → editar → De-para, com duas visões, Tabela e JSON. A tabela cobre o caso comum (uma linha por campo). A aba JSON é onde se escreve o que a tabela ainda não representa — hoje, flatten e filter.

O formato​

{
"itemsPath": "value", // onde está o array de registros na resposta
"flatten": { … }, // explosão 1→N (pai → filhos)
"filter": [ … ], // descarte antes de projetar
"rules": [ … ] // o de-para em si
}

Precedência na projeção: rules → fields (legado) → item cru. Mapeamentos antigos no formato fields (rename simples) são convertidos para rules ao abrir o editor.

Chave desconhecida não se perde

O editor preserva as chaves que ele não edita (flatten, filter, e o que vier depois): elas entram no parse e voltam no salvar. Ainda assim, prefira editá-las na aba JSON — é lá que você as vê.

Uma regra (FieldRule)​

interface FieldRule {
targetField: string; // campo canônico de destino (tem de existir na entidade alvo)
source?: string; // caminho na origem (ex.: "ItemCode", "data.razaoSocial")
template?: string; // alternativa a `source`; tem precedência
type?: "string" | "number" | "boolean" | "date" | "json";
transforms?: FieldTransform[]; // aplicados NA ORDEM, antes da coerção
defaultValue?: unknown; // usado pelo transform `default`
required?: boolean;
}

Ordem de avaliação de cada regra:

extrai (template se houver, senão source) → transforms, em ordem → coerção de tipo → checagem de obrigatório.

O targetField é validado contra os campos da entidade alvo — campo inexistente vira erro no dry-run, não lixo no banco.

Origem: um caminho ou um template​

A coluna Origem aceita as duas formas, e a escolha é pelo que você digita:

O que você escreveComo é gravadoQuando usar
ItemCodesourceo valor vem de um campo só
{{PriceList}}*{{_parent.ItemCode}}templatejuntar mais de um campo (chave natural composta)
AliasName || CardNamesourceso dado está num campo ou noutro, conforme o cadastro

Quando o campo vem vazio em alguns registros​

Separe as origens com ||: vale a primeira que trouxer valor.

AliasName || CardName || {{CardCode}}-sem-nome

É o caso clássico do SAP B1: o nome fantasia está em AliasName para uns clientes e só em CardName para outros. Cada alternativa pode ser um caminho ou um template — então a última pode ser uma composição, como acima.

Se nenhuma trouxer valor, entra o Valor padrão da linha (o transform default). A ordem completa é: alternativas → valor padrão → demais transforms → coerção de tipo.

0 e false não são vazios

Vazio, aqui, é null, ausente, ou string só com espaços. Um preço 0 ou um booleano false são respostas legítimas da fonte — pular para a próxima origem nesse caso seria inventar dado.

Um template que é exatamente {{campo}} devolve o valor cru (preserva número, booleano, objeto); misturado com texto, interpola como string.

Filtros por campo​

Dentro de um template, cada campo aceita filtros encadeados com |, aplicados antes de o valor entrar na string:

FiltroResultado
trim / lower / upper / asciiapara as pontas / caixa baixa / caixa alta / sem acento
nospaceremove todo espaço, inclusive o interno
digitssó os dígitos (30.534.322/0001-81 → 30534322000181)
slug[:sep]sem acento, minúsculo, não-alfanumérico vira sep (padrão -)
replace:de:paratroca literal (não é regex); para vazio remove
{{SalesEmployeeName|slug:.}}.{{SalesEmployeeCode}}@smart.com
→ vago.thais.abc.1.219@smart.com

O replace é literal de propósito: uma expressão regular vinda da configuração roda por registro, e uma expressão catastrófica derrubaria a sincronização inteira. Os casos que pediriam regex já têm filtro dedicado.

Filtro ≠ transform

O filtro age em cada campo antes da concatenação; o transform age no valor final da regra. {{a|nospace}}-{{b}} tira o espaço só de a; o transform trim aparia a string inteira.

Valor padrão​

O Valor padrão entra quando a origem vem vazia, nula ou ausente. Na tela, digitar um valor padrão liga sozinho o transform default (e apagar desliga) — o campo só existe por causa dele.

  • Aceita {{campos}}: "quando não vier e-mail, monte {{nome}}.{{codigo}}@empresa.com". A diferença para o template é que o template sempre manda; o valor padrão só entra quando a origem falhou.
  • Valor fixo = origem em branco + valor padrão preenchido. Uma regra sem source e sem template não extrai nada, então o padrão é tudo o que sobra.

Transforms​

TransformNa telaO que faz
trimtrimtira espaços das pontas
lower / upperlower / uppercaixa baixa / alta
cast_number→númtexto → número (aceita vírgula decimal: "1.234,56")
cast_bool→booltexto → booleano (aceita tYES/tNO do SAP B1)
parse_date→datamarca como data (a conversão acontece na coerção)
defaultdefault valuevalor vazio → o Valor padrão da linha
split_list→listaescalar ou "a,b,c" → lista

split_list existe para os campos canônicos que são lista (companyExternalCodes, priceTableExternalCodes…): sem ele, um ERP que devolve o código solto grava 1 onde o consumidor espera ["1"].

A ordem dos transforms muda o resultado

Eles rodam na ordem em que você marca — a tela numera cada um e mostra a cadeia (default value → →lista).

Com origem vazia e valor padrão 01:

OrdemResultado
["default", "split_list"]["01"] ✅
["split_list", "default"][] ❌ — a lista vazia não é "vazia" para o default, que só olha nulo/ausente/""

A ordem inversa também tem uso legítimo (["trim", "default"] cobre a origem que veio só com espaços), então o runtime respeita o que você escreveu em vez de reordenar por conta própria.

Tipos e coerção​

A coerção nunca derruba a execução: ela devolve o valor e o erro, que aparece no dry-run.

  • number — aceita "1.234,56" no estilo brasileiro.
  • boolean — aceita true/1/sim/s/yes/y e false/0/nao/não/n/no (mais tYES/tNO do SAP).
  • date — vira ISO; data inválida vira erro.
  • string — se o valor for objeto ou array, serializa em JSON e sinaliza erro: quase sempre significa que a origem aponta um nó aninhado em vez da folha.

flatten — um registro por filho​

Quando a fonte devolve um envelope pai com um array de filhos (item com suas listas de preço, pedido com seus itens), cada filho vira um registro, com o pai preservado para as regras alcançarem:

{ "path": "ItemPrices", "parentKey": "_parent", "keepEmpty": false }

Cada filho sai como { ...filho, _parent: pai } — daí o {{_parent.ItemCode}} nos templates. keepEmpty: true emite o pai como registro único quando não há filhos.

O flatten não mexe na paginação

A contagem que avança a página ($skip/offset) é sempre a de itens que a fonte devolveu, não a de registros que o de-para produziu. Uma página de 500 itens que vira 5.000 registros continua avançando de 500 em 500 — e uma página que o filter esvazia não é confundida com fim de dados.

filter — descartar antes de projetar​

AND de condições, avaliadas sobre o item já achatado:

[{ "path": "cod_gestor", "op": "notEmpty" }]

Operadores: notEmpty | empty | eq | ne | in. Existe porque nem toda linha da fonte é um registro do canônico — no endpoint de vendedores da Wessel, só as linhas com gestor representam uma aresta de hierarquia; sem o filtro, a chave natural viraria lixo.

Exemplo completo — tabela de preço do SAP B1​

O conector sap-price-table-items lê GET /Items com $select=ItemCode,ItemName,ItemPrices e precisa emitir um registro por item × lista de preço.

{
"itemsPath": "value",
"flatten": { "path": "ItemPrices", "parentKey": "_parent" },
"rules": [
{
"targetField": "externalCode",
"template": "{{PriceList}}*{{_parent.ItemCode}}",
"type": "string",
"required": true
},
{ "targetField": "tableCode", "source": "PriceList", "type": "string" },
{ "targetField": "productCode", "source": "_parent.ItemCode", "type": "string" },
{ "targetField": "price", "source": "Price", "type": "number" }
]
}

O externalCode é a chave natural do registro canônico (é por ele que o upsert decide criar ou atualizar). Como nenhum campo do SAP identifica o par item×lista sozinho, ele é composto por template.

Exemplo de valor padrão em lista​

No sap-user, o SAP não devolve a empresa do usuário, e o canônico espera uma lista de códigos:

{
"targetField": "companyExternalCodes",
"source": "",
"type": "json",
"transforms": ["default", "split_list"],
"defaultValue": "01",
"required": true
}

Origem em branco → o default põe "01" → o split_list transforma em ["01"]. Invertendo os dois transforms o resultado seria [] (ver o aviso acima).

Conferir antes de executar​

Três botões no editor do conector fecham o ciclo, e nenhum deles grava nada:

  1. Descobrir campos — faz uma chamada viva e alimenta o autocomplete da coluna Origem com os caminhos reais da fonte.
  2. Testar mapeamento (dry-run) — roda o de-para sobre uma amostra e mostra, por registro, entrada → saída + os erros por campo. É o "ver como vai salvar".
  3. Dados / Reprocessar — re-aplica o mapeamento sobre o payload já guardado no staging, sem rechamar a fonte. É o caminho normal depois de corrigir uma regra.
Campo obrigatório sem regra

Um campo obrigatório da entidade que não está na tabela é avisado em faixa amarela (com o botão Adicionar os campos) e vira erro no dry-run. A falha seria silenciosa: sem a chave natural, o registro não vira canônico, fica só no staging, e a tela de Dados mostra menos registros do que a fonte tem.

A aba JSON​

O editor de JSON é o mesmo em todo o sistema (de-para de entrada, de-para de saída, corpo dos nós de pipeline):

  • JSON válido / linha X, coluna Y — mensagem acima do campo, validado a cada tecla.
  • Dobrar tudo / Expandir tudo, e a seta na margem dobra um objeto por vez (duplo clique num { faz o mesmo). Um bloco dobrado vira uma pílula 41 linhas.
  • Formatar reindenta; Tab indenta e Shift+Tab desindenta (Esc antes do Tab sai do campo).
  • {{campos}} aparecem como pílula; Ctrl+Espaço abre as sugestões.

Referência completa do contrato (incluindo o que ainda não está na tela): docs/integrations-model.md §4, no repositório.