Guia de integração do ERP com o Softeum Pedidos
Para a equipe de TI: como o ERP recebe, confirma e recusa os pedidos e envia os cadastros, com exemplos prontos.
docs.softeum.com.br/guia/
Seções desta página
Para a equipe de TI da indústria (o cliente do Softeum). O Softeum lê os pedidos de compra que chegam por e-mail,
confere e deixa o pedido pronto para o ERP. O ERP recebe o pedido de um de dois jeitos — o Softeum avisa
(um POST assinado no endereço do ERP) ou o ERP busca (na API REST, com uma chave de acesso) —, lança o pedido de
venda e confirma com o número dele. O ERP também pode enviar os cadastros (produtos, compradores, vendedores e tabelas
de preço) pela mesma API. Este guia tem tudo o que é preciso, com exemplos prontos para rodar.
Serve para qualquer ERP: SAP, TOTVS (Protheus, Winthor, Datasul, RM), Sankhya, Senior, Omie, Bling, Tiny/Olist, Microsoft Dynamics, Oracle NetSuite, Odoo ou um ERP próprio. A seção 14 traz o caminho de cada um.
Suporte da Softeum: diiego.martins@softeum.com.br. Nunca mande por e-mail a chave de acesso, o segredo ou a senha do SFTP: quem digita é o administrador, no painel.
Situação (25/10/2026)
Recurso Situação Aviso de pedido pronto ( pedido.aprovado), assinado com HMAC, com reenvio automático e confirmação pela respostaDisponível Aviso de teste (botão Enviar teste do painel) Disponível Pedido de teste para a busca (botão Criar pedido de teste do painel) Disponível (12/11/2026) API REST de pedidos: listar, buscar um, confirmar, recusar, baixar o documento original No ar — https://api.softeum.com.br/v1API REST de cadastros: produtos, compradores, vendedores, tabelas de preço No ar — mesmo endereço
O formato completo de cada campo também está no arquivo OpenAPI 3.1 (no painel: botão Arquivo OpenAPI, no topo deste guia e da tela Integração com o ERP). Abre no Swagger Editor, Postman ou Insomnia, e geradores como o OpenAPI Generator criam as classes a partir dele. Se este guia e o arquivo discordarem, vale o arquivo — e avise a Softeum.
Sumário
- Comece aqui: aviso ou busca
- Como funciona
- Configurar no painel
- O aviso
pedido.aprovado - Os campos do pedido e onde lançar cada um
- Conferir a assinatura do aviso
- Responder ao aviso e não lançar duas vezes
- Exemplos prontos e simulador
- Aviso de teste e pedido de teste
- O que o painel mostra
- Problemas comuns
- Antes de ir para produção
- Perguntas frequentes
- API REST: referência
- Por ERP: o caminho de cada um
- Entrega por arquivo (SFTP)
0. Comece aqui: aviso ou busca
| Aviso (o Softeum entrega) | Busca (o ERP vai buscar) | |
|---|---|---|
| Como chega | POST assinado no endereço do ERP, assim que o pedido fica pronto |
O ERP chama GET /v1/pedidos?situacao=pronto de tempos em tempos |
| O ERP precisa de | Um endereço HTTPS público que receba POST |
Só sair para a internet (o ERP pode ficar dentro da rede da empresa) |
| Demora | Segundos | O intervalo da busca (1 a 5 minutos é o comum) |
| Confirmação | Na resposta ao aviso ({"numero_erp": "..."}) ou depois, pela API |
POST /v1/pedidos/{id}/confirmacao |
| Comece por | receptor.mjs / .py / .php / Program.cs (seção 7) |
buscar-pedidos.mjs / buscar_pedidos.py (seção 7) |
Os dois podem conviver: o aviso entrega, e a API serve para confirmar depois, recusar com o motivo ou buscar o que
ficou para trás. O pedido entregue pelo aviso deixa de aparecer em situacao=pronto.
ERP que importa pedidos de uma pasta (SAP pela interface de arquivos, Protheus, ERPs que leem XML, CSV ou TXT): há um terceiro caminho, sem programar nada. O Softeum grava cada pedido aprovado como um arquivo, no layout do ERP, na pasta de um servidor SFTP. Veja a seção 15.
Pelo aviso, em 4 passos
- Suba um endereço HTTPS público no ERP que receba
POSTcom JSON. Comece por um dos receptores prontos da seção 7: eles já conferem a assinatura, ignoram o aviso de teste, não lançam o mesmo pedido duas vezes e devolvem o número do ERP. Falta só escrever o lançamento no seu ERP. - Cadastre o endereço no painel (Integração → Como o ERP recebe → Aviso no endereço do ERP) e guarde o segredo que aparece.
- Teste com o simulador da seção 7 (no seu ambiente, sem depender do painel) e depois com o botão Enviar teste do painel.
- Lance um pedido de verdade e devolva o número: o pedido aparece como confirmado no painel.
Pela busca, em 5 passos
- Crie a chave de acesso no painel (Integração → Como o ERP recebe → O ERP busca os pedidos). Ela começa com
sp_live_e aparece uma vez só: guarde no cofre de senhas do ERP. - Comece pelo
buscar-pedidos.mjsoubuscar_pedidos.py(seção 7): eles buscam os prontos, chamam o seu lançamento e confirmam (ou recusam com o motivo). Falta só escrever o lançamento no seu ERP. - Agende a busca a cada 1 a 5 minutos (cron, Agendador de Tarefas do Windows, job do ERP).
- Teste com um pedido de teste, antes do primeiro pedido de verdade: o administrador clica em Criar pedido de
teste no painel (no mesmo lugar da chave) e um pedido pronto, montado com os compradores e os produtos do cadastro
da indústria, aparece na busca com
"teste": true. Lance no ERP de homologação e confirme; crie outro e recuse, para testar a recusa (seção 8). O ERP de produção recusa o pedido de teste. - Lance um pedido de verdade: ele aparece como confirmado no painel e some de
situacao=pronto.
Sem endereço de entrega cadastrado, o pedido pronto fica esperando a busca do ERP (no painel: "Pronto para enviar").
Palavras deste guia
| Palavra | O que é |
|---|---|
| Aviso | O POST que o Softeum faz no endereço do ERP quando um pedido fica pronto (um webhook). |
| Busca | O ERP chama a API REST para pegar os pedidos prontos. |
| Chave de acesso | Texto sp_live_... que identifica a sua empresa na API REST (cabeçalho Authorization: Bearer). |
| Endereço de entrega | A URL HTTPS do ERP que recebe os avisos. Uma por empresa. |
| Segredo | Texto whsec_... que só o painel e o ERP conhecem; com ele o ERP confere que o aviso veio do Softeum. |
| Pedido pronto | Lido, conferido e aprovado (direto ou pelo time de faturamento). Só pedido pronto vai ao ERP. |
| Confirmado | O ERP lançou e devolveu o número do pedido de venda. |
| Comprador | O cliente da indústria que fez o pedido. |
| Vendedor | O representante da indústria que encaminhou o e-mail do pedido. |
1. Como funciona
Comprador ──e-mail──▶ Vendedor ──encaminha──▶ Contas do Outlook da indústria
│
Softeum lê (XML, planilha, PDF, texto)
e confere: comprador, produtos (de-para),
preços, campos obrigatórios, duplicados
│
┌────────────────────────────┴───────────────────┐
tudo certo tem pendência
│ │
│ time de faturamento revisa
│ no painel e aprova (ou recusa)
▼ │
pedido PRONTO ◀───────────────────────────────────────┘
│
┌─────────────────┴──────────────────────────────┐
AVISO (com endereço de entrega) BUSCA (pela API)
│ │
POST assinado no endereço do ERP GET /v1/pedidos?situacao=pronto
│ │
2xx com {"numero_erp": "..."} → CONFIRMADO POST /v1/pedidos/{id}/confirmacao → CONFIRMADO
2xx sem número → ENVIADO POST /v1/pedidos/{id}/recusa → ERRO (com o motivo)
sem 2xx → reenvio em 1, 5, 30, (sem confirmar, o pedido continua PRONTO
120 e 360 min → ERRO no painel e volta na próxima busca)
- O ERP só recebe pedido aprovado. Pedido com pendência (preço fora da tolerância, produto sem de-para, CNPJ desconhecido, valor acima do limite configurado etc.) fica com o time de faturamento até ser resolvido. Pedido recusado pelo time nunca chega ao ERP.
- Todo item já vem com o produto do catálogo da indústria identificado (
itens[].produto). O ERP não faz de-para. - Um e-mail pode gerar mais de um pedido: cada anexo com pedido (PDF, planilha, XML, foto) vira um pedido, com
idpróprio e aviso próprio. Um arquivo só com vários pedidos dentro ainda não é separado: vira um pedido. - O Softeum não envia nada ao comprador. Quem fala com o comprador é a indústria.
2. Configurar no painel
Com um usuário administrador da indústria:
- Integração → Como o ERP recebe → Aviso no endereço do ERP: informe o endereço HTTPS do ERP.
- Tem que começar com
https://e ser público: endereço de rede interna,localhost, IP privado,*.locale*.internalsão recusados — ao salvar e de novo a cada envio. - Não coloque usuário e senha no endereço. Para identificar a origem, use um caminho ou um parâmetro
(
https://erp.suaempresa.com.br/softeum/pedidos?origem=softeum). - Não seguimos redirecionamento:
301,302e307contam como falha. Informe o endereço final. - Qualquer porta é aceita (ex.:
https://erp.suaempresa.com.br:8443/...). - O certificado TLS precisa ser válido, com a cadeia completa, emitido por uma autoridade pública.
- Tem que começar com
- Ao salvar pela primeira vez, o painel mostra o segredo de assinatura (
whsec_+ 48 caracteres). Guarde no cofre de senhas do ERP. Ele pode ser revisto (Mostrar, Copiar) e trocado (Gerar novo segredo); ao trocar, o anterior para de valer na hora — atualize o ERP antes. - Clique em Enviar teste (seção 8) e confira se o ERP respondeu
2xx.
Depois disso, todo pedido aprovado vai para o endereço. Trocar o endereço mantém o mesmo segredo.
Para a API REST, crie a chave em Integração → Como o ERP recebe → O ERP busca os pedidos (só o administrador).
A chave começa com sp_live_, aparece uma vez só (guarde no cofre de senhas do ERP) e pode ser revogada a qualquer
momento — revogar corta o acesso na hora e não tem volta. Use uma chave por sistema (produção, homologação) para poder
revogar uma sem parar a outra. Quem só busca pela API não precisa cadastrar endereço de entrega.
3. O aviso pedido.aprovado
3.1 A requisição
POST https://erp.suaempresa.com.br/softeum/pedidos
Content-Type: application/json
User-Agent: Softeum-Pedidos
Softeum-Evento: pedido.aprovado
Softeum-Entrega: 0199a3c4-5f10-7a2b-8c3d-4e5f60718294
Softeum-Assinatura: t=1759261325,v1=5d1c...e9a0
{ "id": "...", "tipo": "pedido.aprovado", "criado_em": "...", "pedido": { ... } }
| Cabeçalho | O que é |
|---|---|
Softeum-Evento |
O tipo do aviso: pedido.aprovado (ou teste, no aviso de teste). |
Softeum-Entrega |
O id do aviso. É o mesmo em todos os reenvios do mesmo pedido. |
Softeum-Assinatura |
t=<segundos desde 1970, UTC>,v1=<HMAC-SHA256 em hexadecimal minúsculo> (seção 5). |
O corpo é JSON em UTF-8. Leia o corpo como texto bruto antes de converter: a assinatura é calculada sobre os bytes exatos que chegaram.
3.2 O corpo
{
"id": "0199a3c4-5f10-7a2b-8c3d-4e5f60718294",
"tipo": "pedido.aprovado",
"criado_em": "2026-09-30T16:42:05-03:00",
"pedido": {
"id": "0199a3c4-5f10-7a2b-8c3d-4e5f60718293",
"numero_comprador": "45882",
"situacao": "pronto",
"origem": "pdf",
"recebido_em": "2026-09-30T16:13:00-03:00",
"aprovado_em": "2026-09-30T16:42:00-03:00",
"comprador": { "cnpj": "12345678000195", "razao_social": "Distribuidora Rio Claro Ltda", "codigo_erp": "C001" },
"vendedor": { "nome": "Carlos Menezes", "email": "carlos.menezes@valeazul.com.br", "codigo_erp": null },
"condicao_pagamento": "28/42/56 dias",
"data_pedido": "2026-09-29",
"data_entrega": "2026-10-07",
"endereco_entrega": "CD Rio Claro, Av. Brasil, 2150",
"observacoes": null,
"itens": [
{
"linha": 4,
"codigo_comprador": "004359",
"descricao_comprador": "LIMPA VIDROS REFIL 500ML CX12",
"produto": { "sku": "VA-LVD-500R", "codigo_erp": "4359", "descricao": "Limpa-vidros refil 500 ml", "ean": "7896543000415" },
"quantidade": 30,
"unidade": "CX",
"quantidade_erp": 360,
"unidade_erp": "UN",
"preco_unitario": 76.04,
"valor_total": 2281.2,
"data_entrega": null
}
],
"valor_total": 2281.2,
"revisao": { "revisado_por": "Marina Costa", "revisado_em": "2026-09-30T16:40:00-03:00", "motivos": ["preco"] },
"numero_erp": null,
"arquivo_original": "https://.../pedido-45882.pdf?token=..."
}
}
Pedido aprovado direto, sem revisão, vem com "revisao": null. Vendedor sem e-mail cadastrado vem com
"vendedor": null.
4. Os campos do pedido e onde lançar cada um
Datas e horas vêm em ISO 8601 com o fuso da empresa (2026-09-30T16:42:00-03:00); datas sem hora, aaaa-mm-dd.
Números usam ponto decimal. Todo campo listado vem sempre no JSON; "ou null" quer dizer que o valor pode ser nulo.
4.1 Onde cada campo entra no pedido de venda
| No pedido de venda do ERP | Use | Observação |
|---|---|---|
| Chave de integração (evita duplicar) | pedido.id |
Grave numa coluna própria com restrição de unicidade. |
| Cliente | comprador.codigo_erp |
Sem código, procure pelo comprador.cnpj. |
| Nº do pedido do cliente | numero_comprador |
Pode vir vazio. Não é único entre clientes. |
| Vendedor / representante | vendedor.codigo_erp |
vendedor pode ser null. |
| Condição de pagamento | condicao_pagamento |
Texto livre: traduza para o código do ERP (seção 12). |
| Data do pedido (emissão) | data_pedido |
null = o documento não traz: use recebido_em. |
| Data de entrega | data_entrega |
null = sem data pedida. Com uma data por item, a de cada item vem em itens[].data_entrega. |
| Endereço de entrega | endereco_entrega |
null = endereço do cadastro do cliente. |
| Observação | observacoes |
|
| Produto do item | itens[].produto.codigo_erp |
Sem código, procure pelo sku. |
| Quantidade e unidade | itens[].quantidade + itens[].unidade |
Ou quantidade_erp + unidade_erp, com o preço convertido (veja o alerta em 4.4). |
| Preço unitário | itens[].preco_unitario |
Líquido, por unidade. |
| Total do item | itens[].valor_total |
quantidade × preco_unitario. |
| Total do pedido | valor_total |
Sem frete e sem impostos. |
4.2 Identificação e datas
| Campo | Tipo | O que é e como usar |
|---|---|---|
id |
uuid | Identificador do pedido no Softeum. Use como chave única para não lançar o mesmo pedido duas vezes. |
teste |
true (só no pedido de teste) |
Vem só no pedido de teste, criado no painel para a TI testar a busca (seção 8); nos outros pedidos a chave nem aparece. Lance no ERP de homologação; no de produção, recuse com o motivo, e ele sai da fila (os exemplos da seção 7 recusam sozinhos). Novo em 12/11/2026. |
numero_comprador |
texto (pode ser "") |
Número do pedido no documento do comprador. Vem vazio quando o documento não trazia número e o time aprovou assim. |
situacao |
texto | No aviso é sempre pronto. |
origem |
texto | De onde foi lido: pdf (inclusive digitalizado), word (documento Word, .docx), excel, xml, imagem (foto ou print do pedido, anexado ou colado no e-mail), corpo (texto do e-mail) ou importado. A lista pode crescer: trate um valor que você não conhece como genérico, sem recusar o pedido. |
recebido_em |
data e hora | Quando o e-mail chegou à conta do Outlook (a indústria pode ter mais de uma). |
aprovado_em |
data e hora | Quando o pedido ficou pronto. Igual em todos os reenvios. |
data_pedido |
aaaa-mm-dd ou null |
A data em que o comprador fez o pedido, como o documento traz (a emissão: "Data Emissão", "Data do pedido"). O e-mail encaminhado dias depois não muda esta data. null quando o documento não traz: use recebido_em. Novo em 11/11/2026. |
4.3 Comprador, vendedor e condições
| Campo | Tipo | O que é e como usar |
|---|---|---|
comprador.cnpj |
14 dígitos | Só números. Do cadastro de compradores do Softeum (o comprador ligado ao pedido), e não o lido do documento: um dígito lido errado não chega ao ERP. Mudou em 02/11/2026 (antes vinha o CNPJ lido). |
comprador.razao_social |
texto | Do cadastro de compradores do Softeum. |
comprador.codigo_erp |
texto ou null |
Código do cliente no seu ERP. Cadastrar no Softeum (Compradores ou planilha) evita a busca por CNPJ. |
vendedor |
objeto ou null |
Quem encaminhou o e-mail. null quando o vendedor não tem e-mail cadastrado. |
vendedor.nome / email |
texto | Nome e e-mail do vendedor. |
vendedor.codigo_erp |
texto ou null |
Código do vendedor/representante no ERP. |
condicao_pagamento |
texto ou null |
Como o comprador escreveu (28/42/56 dias, 30 DDL). |
data_entrega |
aaaa-mm-dd ou null |
Data de entrega pedida. Quando o documento traz uma por item e todas iguais, é ela. Com datas diferentes nos itens, o pedido passou pela revisão, e a de cada item vem em itens[].data_entrega. |
endereco_entrega |
texto ou null |
Como veio no documento, numa linha só. |
observacoes |
texto ou null |
Observações do pedido. |
4.4 Itens (itens[], pelo menos um)
| Campo | Tipo | O que é e como usar |
|---|---|---|
linha |
inteiro | Posição do item no documento do comprador. Pode pular números: item tirado na revisão não vem. |
codigo_comprador |
texto ou null |
Código do produto como o comprador escreveu. Só para conferência. |
descricao_comprador |
texto ou null |
Descrição como o comprador escreveu. Só para conferência. |
produto.sku |
texto | SKU do produto no catálogo da indústria (já identificado pelo de-para). |
produto.codigo_erp |
texto ou null |
Código do produto no seu ERP: use para lançar. null = produto sem código do ERP no cadastro; procure pelo sku e corrija o cadastro. |
produto.descricao |
texto | Descrição do catálogo da indústria. |
produto.ean |
texto ou null |
EAN/GTIN do catálogo. |
quantidade |
número, até 4 casas | Quantidade na unidade do comprador (unidade). |
unidade |
texto ou null |
Unidade do comprador (CX, FD, UN). |
quantidade_erp |
número | A mesma quantidade convertida para unidade_erp pelo fator do de-para (30 CX de 12 → 360 UN). Sem conversão, igual a quantidade. |
unidade_erp |
texto | Unidade de quantidade_erp, do cadastro do produto, nunca do texto do comprador: a embalagem do produto vendido por medida (KG, L, M) ou com 1 unidade na embalagem (CX); UN na embalagem com mais de uma. É a do cadastro de quando a conta de quantidade_erp foi feita (na leitura, ou na revisão quando o produto ou a conversão mudou), gravada junto com ela: o cadastro corrigido depois não muda a unidade de uma quantidade já calculada (o produto em CX com 1 unidade, corrigido para CX com 12 depois da leitura, segue como 10 CX, e não 10 UN). Mudou em 02/11/2026: antes vinha do texto do comprador ou um UN fixo (25 CX de frango vendido por KG, 250 em quantidade_erp, saíam como 250 UN). |
preco_unitario |
número, até 4 casas | Preço líquido por unidade do comprador, já com o desconto do pedido (4.7) e decidido na revisão quando houve. |
valor_total |
número, 2 casas | quantidade × preco_unitario. |
data_entrega |
aaaa-mm-dd ou null |
A data de entrega deste item, quando o documento do comprador traz uma por item (o pedido do Protheus). null: vale a data_entrega do pedido. Novo em 11/11/2026. |
desconto |
objeto, null ou ausente |
Só na indústria que lê o desconto de cada item (a equipe Softeum liga): o desconto escrito no próprio item, { "percentual": 10.5, "tipo": "comercial" } (percentual maior que 0 e até 100; tipo comercial, especial ou promocional), e null no item sem desconto. Nas outras indústrias a chave não aparece. O preco_unitario já vem com esse desconto: não aplique de novo. |
Atenção à unidade do preço.
preco_unitarioé porunidade(a do comprador), não porunidade_erp. Se o ERP lança o item emquantidade_erp/unidade_erp, o preço por unidade do ERP évalor_total ÷ quantidade_erp(no exemplo: 2.281,20 ÷ 360 = 6,3367 por UN). Lançarquantidade_erpcompreco_unitariomultiplica o valor do item pelo fator da embalagem — 12 vezes, no exemplo.
4.5 Totais, revisão e arquivo
| Campo | Tipo | O que é |
|---|---|---|
valor_total |
número | Soma de itens[].valor_total, já com as decisões da revisão. Sem frete e sem impostos: o ERP calcula IPI, ICMS-ST etc. |
revisao |
objeto ou null |
Preenchido quando o pedido passou pela revisão: revisado_por, revisado_em e motivos — códigos preco, depara, cnpj, faltam, catalogo, duplicado, valor, leitura, sempre_revisar (o comprador da lista "sempre revisar" ou bloqueado, o vendedor reconhecido por quem encaminhou o e-mail, e não pelo remetente, ou o e-mail recebido há 7 dias ou mais). Só o que mandou o pedido para a revisão: os avisos (a regra em "só avisar", o produto sem preço na tabela do comprador) não entram. Útil para auditoria. |
numero_erp |
null |
No aviso é sempre null (o ERP ainda não lançou). |
arquivo_original |
URL | Documento original do comprador (PDF, planilha, XML ou o e-mail .eml). Vale 1 hora a partir do envio: para guardar o arquivo, baixe na hora. Os arquivos ficam guardados conosco por 12 meses a partir do recebimento. Sem arquivo (ou depois dos 12 meses), vem um endereço que não baixa nada. |
Prepare o ERP para campos novos: ignore campos que não conhece e não dependa da ordem dos campos — o formato pode ganhar campos com o tempo.
4.6 O que o pedido não traz: o ERP completa
O pedido traz o que o comprador pediu. O pedido de venda do ERP pede também dados internos da indústria, que nenhum comprador informa. Toda integração precisa decidir estes, e o time do ERP vai perguntar por eles:
| Dado do pedido de venda | Como se chama em alguns ERPs | Como preencher |
|---|---|---|
| Empresa ou filial que fatura | Filial (Protheus, Winthor), empresa (Sankhya), estabelecimento (Datasul), organização de vendas (SAP) | Fixo na integração. Indústria que fatura por mais de uma filial: defina a regra (por exemplo, pela UF do cliente) e fale com a Softeum se precisar de um campo no pedido. |
| Tipo de operação | TES (Protheus), tipo de venda (Winthor), TOP (Sankhya), tipo de ordem (SAP), natureza de operação (Tiny/Olist, Datasul), etapa (Omie) | Fixo: a operação de venda normal da indústria. |
| Tabela de preço | Tabela de preço (Protheus, Tiny/Olist), região de preço (Winthor) | Do cadastro do cliente no ERP. O preço do item já vem pronto (4.7). |
| Forma de cobrança e plano de pagamento | Cobrança e plano de pagamento (Winthor), forma de pagamento | Traduza condicao_pagamento (seção 12) ou use a do cadastro do cliente. |
| Transportadora e frete (CIF/FOB) | Transportadora, tipo de frete | Do cadastro do cliente ou fixo. |
| Canal, setor, natureza financeira, centro de custo, depósito | Canal de distribuição e setor de atividade (SAP), natureza e centro de custo (Sankhya), armazém (Protheus) | Fixos na integração. |
| Data de emissão | A data do lançamento no ERP. aprovado_em diz quando o pedido ficou pronto. |
Guarde esses valores numa configuração da integração (arquivo ou tabela), não no meio do código: muda a filial ou a operação, troca-se o valor sem programar.
4.7 Preço: lance o preço que veio
preco_unitarioé o preço que vale para o pedido. Quando a indústria tem tabelas de preço no Softeum, ele já foi conferido contra elas, e a indústria escolhe em Configurações → Regras de conferência → Preço fora da tolerância qual preço segue:- Revisão (padrão): dentro da tolerância segue o preço do comprador; fora dela, o time de faturamento decide o preço na revisão;
- Preço do pedido: segue sempre o preço que o comprador mandou;
- Preço da tabela: segue sempre o preço da tabela de preço do comprador, no jeito da linha do pedido (o preço da embalagem, da unidade ou da conversão da linha, como a conferência decidiu; com ou sem IPI). Produto sem preço na tabela segue com o preço do comprador. Nas três, com o produto na tabela do comprador, a linha cujo preço não está na base da quantidade (o da unidade numa linha em caixas, ou o contrário) passa antes pela revisão: o pedido não chega com a quantidade numa base e o preço na outra. Sem o preço do produto na tabela, não há com o que comparar: a linha segue na unidade escrita no pedido, com o preço do comprador (a linha sem unidade, de produto com mais de uma unidade na embalagem, passa pela revisão).
- O desconto do pedido já está no preço. O desconto dado no próprio item vem no
preco_unitariodele. O desconto dado sobre o total do pedido (o documento traz "Desconto: R$ 345,92" no rodapé, e não em cada item) também vem no preço: ele é distribuído nos itens em proporção ao valor de cada linha, e a soma dos itens dá o total do documento. Não lance o desconto de novo no cabeçalho do pedido do ERP: ele seria aplicado duas vezes. Por isso o preço pode vir com até 4 casas decimais (76.0412). - Desconto de cada item (só na indústria que liga). Para o ERP que lança o desconto item a item (o percentual e o
tipo, e não o preço já com o desconto), a equipe Softeum liga a leitura do desconto de cada item da indústria. Os
pedidos lidos depois disso trazem, em cada item, o desconto escrito no documento:
"desconto": { "percentual": 10.5, "tipo": "comercial" }, ounullno item sem desconto (4.4). Opreco_unitariocontinua com o desconto: o preço de tabela do pedido épreco_unitario ÷ (1 − percentual ÷ 100)(11,0712 com 10,5% → 12,37). O ERP que lança o desconto lança o preço de tabela com ele; o que lança opreco_unitarionão lança o desconto: nunca os dois. Nas outras indústrias, e nos pedidos lidos antes de ligar, a chave não vem. - Lance o pedido com o preço informado, sem recalcular pela tabela do ERP. Recalcular troca o preço que o comprador aceitou e o pedido sai diferente do que foi conferido.
- Se o ERP bloqueia pela política comercial dele (desconto acima do permitido, preço abaixo do mínimo, cliente com
crédito bloqueado), não lance pela metade: recuse pela API (
POST /v1/pedidos/{id}/recusa) com o motivo. O pedido volta para o time com o seu texto. - O preço vem sem impostos: o ERP calcula IPI, ICMS-ST e os demais (seção 12). Atenção também à unidade do preço (4.4).
4.8 Cliente e produto com código composto
codigo_erp é texto livre de até 60 caracteres, igual para compradores, produtos e vendedores. Quando o ERP
identifica o registro por mais de uma parte, cadastre as partes juntas no Softeum e separe na integração:
- Protheus: cliente é código + loja (
000123+01). Cadastre000123-01e separe pelo hífen. O modelo pronto do arquivo também aceita00012301(a loja depois das 6 posições doA1_COD). - SAP: o número do cliente tem 10 dígitos, com zeros à esquerda (
0000100001). Cadastre já com os zeros, ou complete na integração. - ERP com o id interno diferente do código visível (Omie, Bling, Tiny/Olist): cadastre o código que o seu lançamento usa e, se a API do ERP pede o id interno, busque o id pelo código antes de lançar.
4.9 Nota fiscal: pedido e item do comprador (xPed e nItemPed)
Muitos compradores (redes, indústrias, grandes distribuidores) exigem na NF-e o número do pedido de compra deles
(xPed) e o número do item nesse pedido (nItemPed), nos campos próprios do XML, e recusam a nota sem eles.
xPed: usenumero_comprador. A NF-e aceita até 15 caracteres; alguns compradores pedem só números. Siga a regra do comprador.nItemPed:linhaé a posição do item no documento do comprador (1, 2, 3...). Serve quando o pedido dele numera os itens em sequência. Quando numera de outro jeito (10, 20, 30, comum em pedido gerado por SAP), o Softeum ainda não traz o número impresso no item: fale com a Softeum antes de faturar para esse comprador.- Guarde os dois no pedido de venda do ERP: é de lá que a nota fiscal sai.
4.10 O que ainda não vem no pedido
Para planejar a integração sem surpresa:
- Endereço de entrega em partes (rua, número, bairro, cidade, UF, CEP): vem numa linha só, como o comprador
escreveu (
endereco_entrega).null= o endereço do cadastro do cliente no ERP. - Comprador pessoa física (CPF): ainda não é aceito; o comprador é sempre uma empresa (CNPJ).
- Número do item impresso no pedido do comprador (4.9).
- Transportadora e tipo de frete pedidos pelo comprador (4.6).
- Aviso de cancelamento: não existe (seção 12).
- Vários pedidos dentro do mesmo arquivo: chegam como um pedido só (seção 1).
5. Conferir a assinatura do aviso (obrigatório)
Sem conferir a assinatura, qualquer pessoa que descubra o endereço consegue criar pedidos falsos no ERP.
- Leia o cabeçalho
Softeum-Assinaturae separetev1. - Recuse se
testiver a mais de 5 minutos do relógio do servidor (para mais ou para menos). Mantenha o relógio sincronizado (NTP). - Monte o texto
"<t>.<corpo bruto>": o valor det, um ponto e o corpo exatamente como chegou, sem reformatar o JSON. - Calcule
HMAC-SHA256desse texto com o segredo inteiro como chave (incluindo o prefixowhsec_), em UTF-8, e escreva em hexadecimal minúsculo. - Compare com
v1em tempo constante. Diferente → responda401e não lance nada.
Vetor de teste
Para conferir a implementação antes de ligar no painel (desligue a checagem dos 5 minutos ou fixe o "agora" em 1759261500):
segredo: whsec_exemplo0123456789abcdef0123456789abcdef01234567
t: 1759261500
corpo: {"id":"0199a3c4-5f10-7a2b-8c3d-4e5f60718295","tipo":"teste","criado_em":"2026-09-30T16:45:00-03:00"}
cabeçalho: t=1759261500,v1=7c088d8e8b8e9253568c05345d02d1110c91b1169ad13541859bdab7bffa5f83
O código, em quatro linguagens
A mesma função está dentro dos exemplos completos da seção 7.
Node.js / TypeScript
import { createHmac, timingSafeEqual } from "node:crypto";
export function assinaturaValida(cabecalho, corpoBruto, segredo, agora = Math.floor(Date.now() / 1000)) {
if (!cabecalho) return false;
const partes = Object.fromEntries(cabecalho.split(",").map((p) => p.trim().split("=", 2)));
const t = Number(partes.t);
if (!Number.isInteger(t) || !partes.v1 || Math.abs(agora - t) > 300) return false;
const esperado = createHmac("sha256", segredo).update(`${t}.${corpoBruto}`, "utf8").digest("hex");
const a = Buffer.from(esperado), b = Buffer.from(partes.v1);
return a.length === b.length && timingSafeEqual(a, b);
}
// Express: express.raw({ type: "application/json" }) e req.body.toString("utf8") — nunca express.json() antes.
Python
import hashlib, hmac, time
def assinatura_valida(cabecalho, corpo_bruto: bytes, segredo: str, agora=None) -> bool:
if not cabecalho:
return False
partes = dict(p.strip().split("=", 1) for p in cabecalho.split(",") if "=" in p)
try:
t = int(partes["t"])
except (KeyError, ValueError):
return False
agora = int(time.time()) if agora is None else agora
if abs(agora - t) > 300 or "v1" not in partes:
return False
esperado = hmac.new(segredo.encode(), f"{t}.".encode() + corpo_bruto, hashlib.sha256).hexdigest()
return hmac.compare_digest(esperado, partes["v1"])
# Flask: request.get_data() · Django: request.body · FastAPI: await request.body()
C# (.NET)
using System.Security.Cryptography;
using System.Text;
static bool AssinaturaValida(string? cabecalho, string corpoBruto, string segredo, long? agora = null)
{
if (string.IsNullOrEmpty(cabecalho)) return false;
var partes = new Dictionary<string, string>();
foreach (var parte in cabecalho.Split(','))
{
var kv = parte.Trim().Split('=', 2);
if (kv.Length == 2) partes[kv[0]] = kv[1];
}
if (!partes.TryGetValue("t", out var tTexto) || !long.TryParse(tTexto, out var t)) return false;
if (!partes.TryGetValue("v1", out var v1)) return false;
var now = agora ?? DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (Math.Abs(now - t) > 300) return false;
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(segredo));
var esperado = Convert.ToHexString(hmac.ComputeHash(Encoding.UTF8.GetBytes($"{t}.{corpoBruto}"))).ToLowerInvariant();
return CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(esperado), Encoding.UTF8.GetBytes(v1));
}
PHP
function assinatura_valida(?string $cabecalho, string $corpoBruto, string $segredo, ?int $agora = null): bool {
if (!$cabecalho) return false;
$partes = [];
foreach (explode(',', $cabecalho) as $p) {
[$k, $v] = array_pad(explode('=', trim($p), 2), 2, null);
$partes[$k] = $v;
}
if (!isset($partes['t'], $partes['v1']) || !ctype_digit($partes['t'])) return false;
$t = (int) $partes['t'];
if (abs(($agora ?? time()) - $t) > 300) return false;
$esperado = hash_hmac('sha256', $t . '.' . $corpoBruto, $segredo);
return hash_equals($esperado, $partes['v1']);
}
// $corpoBruto = file_get_contents('php://input'); · cabeçalho: $_SERVER['HTTP_SOFTEUM_ASSINATURA']
6. Responder ao aviso e não lançar duas vezes
| O ERP responde | O que acontece no Softeum |
|---|---|
2xx com corpo {"numero_erp": "584460"} |
Pedido confirmado com esse número. |
2xx sem corpo, ou sem numero_erp |
Pedido enviado (chegou ao ERP, ainda sem número). |
Qualquer outro código (3xx, 4xx, 5xx), nada em 10 s, erro de conexão ou de TLS |
Falha. Reenviamos depois de 1, 5, 30, 120 e 360 minutos (6 tentativas em cerca de 8h36). Esgotadas, o pedido fica com erro no painel e o time reenvia quando o ERP voltar. |
- Prazo: 10 segundos para a resposta inteira (cabeçalho e corpo). Se lançar demora mais, grave o aviso numa fila
do ERP, responda
2xxna hora e lance depois. numero_erpé texto de até 60 caracteres (um número JSON também é aceito). Lemos só os primeiros 4.000 bytes.- Pedido repetido também recebe
2xx, de preferência com o mesmonumero_erp. Responder erro para repetido faz o Softeum reenviar até esgotar. 401para assinatura inválida e400para corpo que o ERP não entende. Os dois contam como falha. O painel mostra cada tentativa com o código e o tempo; o começo do corpo da resposta (até 500 caracteres) fica no histórico.- Para recusar por regra de negócio (cliente bloqueado, produto inativo), responda
2xxao aviso e recuse pela API (POST /v1/pedidos/{id}/recusacom o motivo): o pedido volta na hora para o time, com o seu texto. Responder4xxtambém acaba em erro, mas só depois das 6 tentativas e sem o motivo à vista do time. - Se o lançamento demora, responda
2xxsem número e confirme depois pela API (POST /v1/pedidos/{id}/confirmacao).
Não lançar duas vezes
O mesmo pedido pode chegar de novo quando o ERP lançou mas respondeu depois dos 10 s (o Softeum conta como falha e reenvia), quando o time reenvia um pedido com erro que o ERP já tinha lançado, ou quando dois avisos chegam ao mesmo tempo. Por isso:
- Antes de lançar, procure
pedido.idno ERP. Já existe → não lance; responda2xxcom o número existente. - Grave
pedido.idcom uma restrição de unicidade no banco do ERP — procurar antes não basta quando dois avisos chegam ao mesmo tempo. Softeum-Entrega(oiddo aviso) também é estável por pedido e serve para registrar o recebimento.
Quantos avisos ao mesmo tempo
Os pedidos de uma empresa chegam ao ERP um de cada vez (o próximo sai quando o anterior teve resposta). Para quem recebe muitos pedidos por hora, a Softeum combina com a empresa até 32 avisos ao mesmo tempo: o ERP precisa aguentar as requisições em paralelo, e os pedidos podem chegar fora da ordem em que ficaram prontos. O mesmo pedido nunca vai em dois avisos ao mesmo tempo (nem quando o Softeum é atualizado no meio de uma entrega: a outra tentativa espera a primeira terminar). Vale também para a entrega por arquivo (seção 15): até esse número de conexões SFTP ao mesmo tempo.
7. Exemplos prontos e simulador
Para baixar no painel, ao lado deste guia (Integração com o ERP → Guia completo de integração → Arquivos de exemplo); a Softeum também os envia junto com o guia:
| Arquivo | O que é | Como rodar |
|---|---|---|
receptor.mjs |
Endereço de entrega em Node.js 18+, sem dependências | SOFTEUM_SEGREDO=whsec_... node receptor.mjs |
receptor.py |
O mesmo em Python 3.9+, só biblioteca padrão | SOFTEUM_SEGREDO=whsec_... python3 receptor.py |
receptor.php |
O mesmo em PHP 8, guardando os lançados em SQLite | SOFTEUM_SEGREDO=whsec_... php -S localhost:8080 receptor.php |
Program.cs |
O mesmo em C# (.NET 8, minimal API) | dotnet new web, troque o Program.cs, dotnet run |
simular-aviso.mjs |
Simulador: manda ao seu endereço um aviso assinado igual ao do Softeum | ver abaixo |
simular-aviso.sh |
Simulador do aviso de teste só com curl e openssl |
./simular-aviso.sh <endereço> <segredo> |
buscar-pedidos.mjs |
Busca pela API em Node.js 18+: lista os prontos, lança, confirma ou recusa | SOFTEUM_CHAVE=sp_live_... node buscar-pedidos.mjs |
buscar_pedidos.py |
O mesmo em Python 3.9+, só biblioteca padrão | SOFTEUM_CHAVE=sp_live_... python3 buscar_pedidos.py |
kit-para-o-ti.md |
O kit para a TI: o jeito de receber, o caminho de cada ERP, o teste, os acessos e quem testa o quê | Leia primeiro |
Os quatro receptores fazem a mesma coisa, na ordem certa:
- leem o corpo bruto e conferem a assinatura (
401se não bate); - respondem
2xxsem lançar nada ao aviso de teste e a tipos desconhecidos; - respondem com o mesmo número a um pedido já lançado;
- chamam
lancarNoErp— a única parte a escrever — e devolvem{"numero_erp": "..."}; se o lançamento falha, respondem503e o Softeum tenta de novo.
Eles escutam HTTP na porta 8080 (PORTA muda). Em produção, deixe o processo atrás do proxy ou balanceador que já faz
o HTTPS do ERP (nginx, IIS, load balancer da nuvem).
Os dois clientes de busca listam os prontos, chamam lancarNoErp e confirmam (ou recusam com o motivo). O pedido de
teste ("teste": true, seção 8) só é lançado com SOFTEUM_HOMOLOGACAO=1, que você liga só no ERP de homologação: sem
ela, ele é recusado com o motivo "Pedido de teste: não lançado no ERP de produção." e sai da fila. Mantenha essa
regra no cliente de busca que você escrever.
Testar sem o painel
Com o receptor rodando, num outro terminal:
# 1. aviso de teste: espere "teste aprovado"
node simular-aviso.mjs http://localhost:8080/softeum/pedidos whsec_SEU_SEGREDO
# 2. pedido de exemplo: espere "CONFIRMADO com o número ..."
node simular-aviso.mjs http://localhost:8080/softeum/pedidos whsec_SEU_SEGREDO --pedido
# 3. o mesmo pedido de novo: espere o MESMO número (o ERP não lançou duas vezes)
node simular-aviso.mjs http://localhost:8080/softeum/pedidos whsec_SEU_SEGREDO --pedido
# 4. outro pedido (id novo): espere um número novo
node simular-aviso.mjs http://localhost:8080/softeum/pedidos whsec_SEU_SEGREDO --pedido --novo
# 5. segredo errado: espere 401 e "falha"
node simular-aviso.mjs http://localhost:8080/softeum/pedidos whsec_errado --pedido
O simulador usa o mesmo prazo de 10 s e também não segue redirecionamento, e diz o que o Softeum faria com a resposta (confirmado, enviado ou falha com reenvio). Rode também contra o endereço HTTPS público, antes de cadastrá-lo.
8. Aviso de teste
O botão Enviar teste (Integração → Como o ERP recebe → Aviso no endereço do ERP) manda ao endereço:
POST <seu endereço>
Softeum-Evento: teste
Softeum-Entrega: <uuid novo a cada teste>
Softeum-Assinatura: t=...,v1=...
{"id": "...", "tipo": "teste", "criado_em": "2026-09-30T16:45:00-03:00"}
Confira a assinatura e responda 2xx sem lançar nada. O painel mostra o código e o tempo da resposta. Trate o
campo tipo (ou o cabeçalho Softeum-Evento) e responda 2xx para tipos que o ERP não conhece: novos tipos de aviso
podem ser criados no futuro.
Pedido de teste (busca pela API)
Para testar a busca sem esperar um pedido chegar por e-mail. O administrador clica em Criar pedido de teste (Integração → Como o ERP recebe → O ERP busca os pedidos) e o Softeum cria, na hora, um pedido pronto:
- o número
TESTE-0001,TESTE-0002... (nunca se repete) e"teste": trueno JSON, logo depois doid; - um comprador ativo do cadastro (de preferência com o
codigo_erp), até 3 produtos ativos (de preferência com ocodigo_erp), as quantidades 1, 2 e 3 e o preço da tabela de preço que vale para o comprador (R$ 10,00 no produto sem tabela). Com os códigos do ERP preenchidos, o ERP de homologação consegue lançar o pedido de verdade; - a data do pedido de hoje, a entrega para daqui a 7 dias e a observação "PEDIDO DE TESTE: não faturar. Criado no Softeum Pedidos para a TI testar a integração com o ERP." (o aviso vem no começo, e fica mesmo se o ERP cortar o campo);
- sem documento:
arquivo_originalvem com o endereço que não baixa nada (como no pedido sem arquivo, seção 4.5), eGET /v1/pedidos/{id}/arquivoresponde404.
Ele sai em GET /v1/pedidos?situacao=pronto como qualquer pronto. Lance e confirme
(POST /v1/pedidos/{id}/confirmacao), ou recuse com o motivo (POST /v1/pedidos/{id}/recusa), e confira no painel:
confirmado com o número do ERP, ou com erro e o motivo.
Só com o ERP de homologação, antes do primeiro pedido de verdade. A busca não separa a homologação da produção: o pedido de teste sai para todo ERP que busca os pedidos da empresa, com qualquer chave, e o ERP de produção o lançaria como um pedido de verdade. Por isso:
- o painel pede confirmação antes de criar;
- o Softeum não cria o pedido de teste quando o ERP já recebeu um pedido de verdade da empresa nos últimos 30 dias (entregue pelo aviso ou pelo arquivo, ou confirmado pela API, inclusive no ERP de homologação): o painel mostra a data e o contato do suporte. Faça o teste com o pedido de teste antes de mandar o primeiro pedido de verdade. Para testar de novo depois disso, fale com o suporte da Softeum (diiego.martins@softeum.com.br);
- no ERP de produção, recuse o pedido de teste com o motivo: ele sai da fila. Os clientes de busca da seção 7 já fazem
isso (sem
SOFTEUM_HOMOLOGACAO=1); - antes de apontar o ERP de produção, confirme ou recuse no ERP de homologação os pedidos de teste que ainda estão prontos. O que sobrar sem confirmar é apagado na primeira hora depois de um pedido de verdade chegar ao ERP.
O pedido de teste nunca vai pelo aviso nem pelo arquivo: com o endereço de entrega ou o SFTP ligados, o painel não cria (use o Enviar teste do aviso ou o Testar conexão do SFTP), e ligar um dos dois apaga os pedidos de teste que ainda não foram confirmados. Ele não entra na cobrança, aparece em Pedidos com o selo Teste e é apagado sozinho em até 7 dias depois de criado. No máximo 20 por dia por empresa. Sem nenhum comprador ou produto ativo no cadastro, o painel pede para cadastrar antes.
9. O que o painel mostra
No alto da tela Integração com o ERP, uma faixa diz numa frase como a entrega está agora (os pedidos chegando, a entrega parada, o ERP recusando, pedidos esperando o ERP há mais de 30 minutos) e leva direto ao que resolve; ao lado, quantos pedidos voltaram com erro do ERP.
Na aba Integração → Entregas, cada pedido enviado aparece com as tentativas (hora, código, tempo) e uma situação (os mesmos nomes da lista de Pedidos):
| Situação no painel | Quer dizer | O que fazer |
|---|---|---|
| Pronto para enviar | Pronto: esperando o aviso (primeira tentativa ou próximo reenvio) ou a busca do ERP pela API. | Pelo aviso, nada: o Softeum tenta sozinho. Pela busca, o pedido sai na próxima busca do ERP. |
| Enviado ao ERP | O ERP respondeu 2xx ao aviso sem numero_erp (no arquivo, o arquivo está na pasta do ERP). |
Confirme pela API (POST /v1/pedidos/{id}/confirmacao) ou devolva o número na resposta ao aviso. |
| Confirmado pelo ERP | O ERP lançou e devolveu o número (aparece na linha). | Nada. |
| Com erro | As 6 tentativas falharam, o ERP recusou pela API (o motivo aparece) ou o pedido não pôde sair (seção 10). | Corrija a causa e use Reenviar ao ERP. A agenda de reenvios recomeça. Só pedido com erro pode ser reenviado. |
| Em revisão | O pedido voltou para a revisão antes de sair (ex.: produto sem o código do ERP no arquivo que usa o código). | O time de faturamento confere e aprova de novo; aprovado, ele vai ao ERP. |
10. Problemas comuns
Mensagens que o painel mostra na entrega (ou no Enviar teste), com a causa e o que fazer:
| Mensagem no painel | Causa provável | O que fazer |
|---|---|---|
| O ERP respondeu com o código 401. | O ERP recusou a assinatura. | Use o corpo bruto (não o JSON reconvertido); use o segredo inteiro, com whsec_; confira se o segredo não foi trocado no painel; sincronize o relógio do servidor; compare em hexadecimal minúsculo. Teste com o vetor da seção 5. |
| O ERP respondeu com o código 404 / 405. | Endereço ou caminho errado, ou a rota não aceita POST. |
Confira o endereço cadastrado e a rota no ERP. |
| O ERP respondeu com o código 301 / 302 / 307 / 308. | O endereço redireciona (ex.: http→https, barra no fim, outro domínio). |
Cadastre o endereço final, para onde ele redireciona. |
| O ERP respondeu com o código 413. | O proxy do ERP limita o tamanho do corpo. | Aumente o limite: pedido com centenas de itens gera um corpo grande. |
| O ERP respondeu com o código 500 / 502 / 503 / 504. | Erro ou indisponibilidade do lado do ERP ou do proxy. | Veja o log do ERP. O Softeum reenvia sozinho. |
| O ERP não respondeu em 10 s. | O lançamento demora mais que o prazo, ou o servidor está travado. | Responda 2xx logo e lance depois (fila). Atenção: o ERP pode ter lançado mesmo assim — a regra de não duplicar (seção 6) resolve o reenvio. |
| Não deu para chegar ao endereço do ERP. Confira o endereço e se o ERP está no ar. | Nome que não resolve (DNS), porta fechada, firewall, certificado TLS inválido ou com a cadeia incompleta. | Teste de fora da rede da empresa (curl -v https://...) e com o simulador. Libere o acesso à porta pela internet. |
| O endereço do ERP aponta para uma rede interna. Use o endereço público do ERP. | O nome resolve para um IP privado (10.x, 172.16–31.x, 192.168.x etc.). | Publique o endereço com IP público (proxy reverso, balanceador). |
| O item da linha N está sem produto do catálogo. … O pedido voltou para a revisão. | Um item sem produto do catálogo, com o produto inativo ou, no arquivo por SFTP que usa o código do ERP, com o produto sem esse código. | Assunto do time de faturamento: o pedido volta para a revisão (não fica em erro), com a pendência de cada linha; o time troca ou reativa o produto (ou completa o código do ERP no cadastro) e aprova de novo. Nada chegou ao ERP. Por arquivo, depois de uma troca de nome sem resposta, o pedido vai para erro com "o arquivo pode já ter chegado ao ERP": confira no ERP antes de mandar para a revisão. |
| O comprador deste pedido não está cadastrado. O pedido voltou para a revisão. | O comprador não está no cadastro do Softeum. | O pedido volta para a revisão; cadastrar o comprador (de preferência com o codigo_erp) e aprovar de novo. Nada chegou ao ERP. |
| O ERP recusou: motivo | O ERP recusou pela API, com esse motivo. | Assunto do time de faturamento: corrigir o que o ERP apontou e reenviar. |
Respostas da API REST e o que fazer:
| Resposta | Causa provável | O que fazer |
|---|---|---|
401 Chave de acesso inválida |
Sem o cabeçalho Authorization: Bearer, chave incompleta, revogada ou de outro ambiente. |
Copie a chave inteira (sp_live_ + 48 caracteres). Revogada não volta: gere outra no painel. |
403 Acesso suspenso |
O acesso da empresa ao Softeum está suspenso. | Fale com a Softeum. |
404 Pedido não encontrado |
O id é de outra empresa, o pedido ainda está na leitura ou na revisão, ou foi recusado ou cancelado pelo time (o detail diz qual). |
Use os ids da lista. Pedido que ainda não ficou pronto aparece quando o time aprovar. |
409 em confirmação |
Já confirmado com outro número, ou o pedido não está pronto nem enviado (com erro, cancelado). | Não confirme duas vezes com números diferentes; o detail traz o número que vale. |
409 em recusa |
O pedido já foi confirmado (não se recusa) ou não está pronto nem enviado. | Para desfazer um pedido confirmado, fale com o time de faturamento. |
409 ao buscar um pedido |
O pedido não pode ir ao ERP (item sem produto do catálogo, produto inativo, comprador sem cadastro) e voltou para a revisão do time. | Assunto do time de faturamento; o pedido volta a aparecer quando o time aprovar de novo. |
422 Dados inválidos |
Campo fora do formato: erros[] traz cada campo e o motivo. |
Corrija e mande de novo. |
429 Muitas chamadas |
Mais de 120 chamadas por minuto com a mesma chave (ou o limite combinado para a sua empresa), ou mais de 30 tentativas por minuto com chave inválida (ou de empresa suspensa) vindas do mesmo endereço. | Espere os segundos do cabeçalho Retry-After; busque a cada 1 a 5 minutos, não em laço. Com muitos pedidos por hora, confirme com Prefer: return=minimal (13.3) e peça à Softeum um limite maior. Se o detail fala de chave inválida, confira a chave antes de tentar de novo. |
5xx |
Instabilidade do lado do Softeum. | Tente de novo em instantes; as operações podem ser repetidas sem efeito duplicado. |
11. Antes de ir para produção
Pelo aviso
Pela busca
Nos dois casos
12. Perguntas frequentes
Qual campo identifica o pedido? pedido.id. O numero_comprador se repete entre compradores e pode vir vazio.
Como acho o cliente no ERP? Por comprador.codigo_erp; se vier null, pelo comprador.cnpj.
Como acho o produto? Por itens[].produto.codigo_erp; se vier null, pelo sku. Mantenha o código do ERP
preenchido no catálogo do Softeum.
A condição de pagamento vem no formato do meu ERP? Não: vem como o comprador escreveu. Mantenha uma tabela de
tradução no ERP (28/42/56 dias → condição 023); sem tradução, use a condição do cadastro do cliente ou recuse.
E os impostos? Os preços são líquidos, sem impostos. O ERP calcula IPI, ICMS-ST e os demais pelas regras dele.
O time cancelou um pedido que já chegou ao ERP. O ERP fica sabendo? Hoje não: não existe aviso de cancelamento,
e o pedido cancelado some da API (GET responde 404 dizendo que foi cancelado). O time precisa avisar quem cuida do ERP.
E se o ERP ficar fora do ar por um dia? As 6 tentativas cobrem cerca de 8h36. Depois disso o pedido fica com erro no painel e o time usa Reenviar ao ERP quando o ERP voltar; a agenda de reenvios recomeça a cada reenvio.
De que IPs vêm os avisos? Do serviço do Softeum na nuvem, sem IP fixo garantido. Proteja o endereço pela assinatura, não por lista de IPs.
Posso ter dois endereços (produção e homologação)? Hoje é um endereço ativo por empresa. Homologue no endereço de homologação e troque para o de produção depois; o segredo continua o mesmo.
O ERP pode confirmar o pedido depois, sem ser na resposta? Sim: POST /v1/pedidos/{id}/confirmacao com o número,
quando quiser (seção 13.3). Vale para o pedido entregue pelo aviso e para o buscado pela API.
Como testo a busca sem esperar um pedido chegar por e-mail? Com o pedido de teste: o administrador clica em
Criar pedido de teste no painel (Integração → Como o ERP recebe → O ERP busca os pedidos) e um pedido pronto, com os
compradores e os produtos do cadastro, aparece na busca com "teste": true. Ele não entra na cobrança, nunca vai pelo
aviso nem pelo arquivo e é apagado em até 7 dias (seção 8). É só para antes do primeiro pedido de verdade: depois que o
ERP recebe um, o painel não cria mais o pedido de teste e mostra o contato do suporte.
O ERP de produção pode receber o pedido de teste? Pode, se ele busca enquanto há um pedido de teste pronto: a
busca não separa a homologação da produção. Recuse no ERP de produção todo pedido com "teste": true, como os
exemplos da seção 7 fazem; ele chega com o número TESTE- e a observação "PEDIDO DE TESTE: não faturar.".
Busquei um pedido e não confirmei. Ele some? Não: continua pronto e volta em toda busca de situacao=pronto até
ser confirmado ou recusado. Por isso a regra de não lançar duas vezes (seção 6) vale também na busca.
Posso usar o aviso e a busca juntos? Sim. O aviso entrega na hora; a API confirma depois, recusa com o motivo e busca o que ficou para trás (por exemplo, com o endereço fora do ar).
13. API REST: referência
13.1 O básico
- Endereço:
https://api.softeum.com.br/v1. - Chave: toda chamada leva
Authorization: Bearer sp_live_...(seção 2). A chave identifica a empresa: a API só mostra e altera os dados dela. - Formato: JSON em UTF-8. Em
POSTePUT, mandeContent-Type: application/json. - Repetir não duplica: toda operação pode ser repetida sem efeito em dobro (confirmar com o mesmo número, a mesma
recusa, o mesmo lote de cadastro). Diante de timeout ou erro de rede, repita. O cabeçalho
Idempotency-Keyé aceito, mas não é necessário.
| Limite | Valor |
|---|---|
| Chamadas por minuto, por chave | 120 (acima disso, 429 com Retry-After). Para quem confirma milhares de pedidos por hora, a Softeum combina um limite maior para a empresa (até 6.000). O minuto conta a partir da primeira chamada da chave. |
| Tentativas com chave inválida ou de empresa suspensa, por minuto, por endereço | 30 (acima disso, 429 com Retry-After até o minuto virar; a chave que já entrou continua passando) |
| Tamanho do corpo | 5 MB |
| Registros por lote de cadastro | 1.000 |
| Preços por tabela | 20.000 (acima disso, a tabela inteira é recusada com 422, sem conferir os preços) |
| Pedidos por página | até 500 (padrão 100) |
Erros vêm em application/problem+json (RFC 9457): title diz o tipo, detail explica em português e erros[]
traz cada campo com problema, até 100: quando passam disso, vão os 100 primeiros e o detail diz quantos são ao todo.
{
"type": "https://softeum.com.br/erros/validacao",
"title": "Dados inválidos",
"status": 422,
"detail": "Confira o corpo da confirmação.",
"erros": [{ "campo": "numero_erp", "mensagem": "Pequeno demais: esperava que o texto tivesse >= 1 caracteres" }]
}
| Código | Quando |
|---|---|
200 |
Deu certo. |
302 |
/arquivo: o Location é o link do documento. |
400 |
O corpo não é um JSON válido ou veio vazio. |
401 |
Sem chave, chave incompleta, inválida ou revogada (vem com WWW-Authenticate: Bearer). |
403 |
O acesso da empresa ao Softeum está suspenso. |
404 |
Rota ou pedido que não existe para a sua empresa (o detail diz o porquê). |
409 |
A situação do pedido não permite (já confirmado com outro número, recusar um confirmado...). |
415 |
Faltou o Content-Type: application/json (corpo com outro tipo vira 422). |
422 |
Dados fora do formato; erros[] diz o quê. |
429 |
Muitas chamadas (ou tentativas demais com chave inválida do mesmo endereço): espere o Retry-After (segundos). |
5xx |
Instabilidade do Softeum: tente de novo em instantes. |
13.2 Buscar os pedidos
GET /v1/pedidos devolve os pedidos do mais antigo para o mais novo (pelo momento em que ficaram prontos).
| Parâmetro | O que é |
|---|---|
situacao |
pronto (esperando o ERP), enviado, confirmado ou erro. Sem ele, todas essas. |
desde |
Só os que ficaram prontos a partir deste momento, em ISO 8601 com fuso (2026-10-01T00:00:00-03:00). |
limite |
De 1 a 500 por página (padrão 100). |
cursor |
O proximo_cursor da página anterior, como veio. |
curl -s "https://api.softeum.com.br/v1/pedidos?situacao=pronto&limite=100" \
-H "Authorization: Bearer $SOFTEUM_CHAVE"
{
"dados": [ { "id": "0199a3c4-5f10-7a2b-8c3d-4e5f60718293", "numero_comprador": "45882", "situacao": "pronto", "...": "o pedido inteiro, igual ao do aviso (seção 3.2)" } ],
"proximo_cursor": "eyJlIjoiMjAyNi0xMC0yMSAxMDowMDowMCswMCIsImkiOiIwMTk5Li4uIn0"
}
proximo_cursor nulo = acabou. Cada pedido vem no mesmo formato do aviso (seção 3.2 e campos na seção 4), com
situacao e numero_erp como estão agora.
O laço de busca (é o que os exemplos buscar-pedidos.mjs e buscar_pedidos.py fazem):
- Peça
GET /v1/pedidos?situacao=prontosem cursor. - Para cada pedido: lance no ERP (sem duplicar: seção 6) e confirme com
POST /v1/pedidos/{id}/confirmacao. O que o ERP não aceita, recuse comPOST /v1/pedidos/{id}/recusae o motivo. - Se veio
proximo_cursor, peça a página seguinte com ele e repita o passo 2. - Na próxima rodada (1 a 5 minutos depois), comece de novo do passo 1, sem cursor.
Não guarde o cursor de uma rodada para a outra. O pedido sai de
prontoquando é confirmado ou recusado, então a fila de prontos sempre recomeça do que falta. Um pedido que volta para pronto depois de um reenvio do time mantém o lugar dele na fila (pelo momento em que ficou pronto da primeira vez): com cursor guardado, ele ficaria para trás e não seria visto. O cursor serve só para ler as páginas da mesma rodada.
O pedido buscado e não confirmado continua pronto e volta na próxima busca — é o ERP que tira o pedido da fila, confirmando ou recusando.
O pedido de teste (seção 8) vem na mesma lista, com "teste": true. No ERP de produção, recuse com o motivo (os
exemplos da seção 7 recusam sozinhos, sem SOFTEUM_HOMOLOGACAO=1).
13.3 Um pedido, confirmar, recusar, documento
GET /v1/pedidos/{id} — um pedido, no formato do aviso.
404: o pedido não é da sua empresa, ainda está na leitura ou na revisão, ou foi recusado ou cancelado pelo time.409: o pedido não pode ir ao ERP (item sem produto do catálogo, produto inativo, comprador sem cadastro) e voltou para a revisão do time, com a pendência de cada linha (antes ia para erro, e o reenvio só o punha em erro de novo). A mesma régua vale para a lista: esse pedido não aparece até o time aprovar de novo.
POST /v1/pedidos/{id}/confirmacao — o ERP lançou o pedido.
curl -s -X POST "https://api.softeum.com.br/v1/pedidos/0199a3c4-5f10-7a2b-8c3d-4e5f60718293/confirmacao" \
-H "Authorization: Bearer $SOFTEUM_CHAVE" -H "Content-Type: application/json" \
-d '{"numero_erp": "584460", "lancado_em": "2026-10-25T09:30:00-03:00"}'
| Campo | Obrigatório | O que é |
|---|---|---|
numero_erp |
sim | Número do pedido no ERP, de 1 a 60 caracteres. |
lancado_em |
não | Quando o ERP lançou (ISO 8601 com fuso). Sem ele, vale a hora da confirmação. |
Responde 200 com o pedido (situacao: "confirmado", numero_erp preenchido). Com o cabeçalho
Prefer: return=minimal, responde só o resumo, mais rápido (o pedido não é montado de novo), e o cabeçalho
Preference-Applied: return=minimal:
{ "id": "0199a3c4-5f10-7a2b-8c3d-4e5f60718293", "situacao": "confirmado", "numero_erp": "584460" }
Use a resposta curta quando confirmar muitos pedidos por hora: o ERP já tem o pedido. Repetir com o mesmo número não muda
nada; com outro número, 409. Só pedido pronto ou enviado pode ser confirmado.
POST /v1/pedidos/{id}/recusa — o ERP não aceita o pedido; ele volta para o time com o motivo.
curl -s -X POST "https://api.softeum.com.br/v1/pedidos/0199a3c4-5f10-7a2b-8c3d-4e5f60718293/recusa" \
-H "Authorization: Bearer $SOFTEUM_CHAVE" -H "Content-Type: application/json" \
-d '{"motivo": "Condição de pagamento 28/56 não existe no ERP."}'
motivo tem de 3 a 500 caracteres e aparece no painel como "O ERP recusou: motivo". Com Prefer: return=minimal,
responde só o resumo (situacao: "erro"), como na confirmação. Sem ele, responde 200 com o pedido
(situacao: "erro"). A mesma recusa de novo não muda nada; recusar um pedido confirmado é 409. Depois que o time
corrige e reenvia, o pedido volta a pronto (e aparece de novo na busca ou chega por novo aviso).
GET /v1/pedidos/{id}/arquivo — o documento original do comprador (PDF, planilha, XML ou o e-mail .eml).
Responde 302 com o link no Location, válido por 5 minutos; 404 se o pedido não tem documento guardado. Os
arquivos ficam guardados por 12 meses a partir do recebimento: depois disso, a resposta é 404.
Os pedidos (com os itens) ficam disponíveis na API por 5 anos a partir do recebimento; depois disso saem e
GET /v1/pedidos/{id} responde 404. Guarde no ERP o que precisar por mais tempo.
curl -s -L -o pedido.pdf "https://api.softeum.com.br/v1/pedidos/0199a3c4-5f10-7a2b-8c3d-4e5f60718293/arquivo" \
-H "Authorization: Bearer $SOFTEUM_CHAVE"
13.4 Enviar os cadastros
O ERP é a fonte da verdade de produtos, compradores, vendedores e tabelas de preço. Mande o que mudou (ou tudo, de tempos em tempos).
As regras
- Cada registro é criado ou atualizado pelo
codigo_erp. Registro que ainda não tem código no Softeum (veio da planilha ou foi feito no painel) é reconhecido pelo SKU (produto), pelo CNPJ (comprador) ou pelo e-mail (vendedor) e passa a ter o código. - Campo opcional que não veio fica como está;
nullapaga. Os obrigatórios vão sempre. ativoque não vem valetrue. Para desativar (produto fora de linha, comprador bloqueado, vendedor que saiu), mande"ativo": false. Nada é apagado. Atenção: um registro desativado no painel volta a ativo se o ERP mandá-lo semativo: false.- Registro com problema não impede os outros: ele volta em
erroscom oindice(a posição no lote, começando em 0), ocampoe amensagem; o resto é gravado. - A ordem importa, porque um cadastro aponta para outro pelo código do ERP: 1. vendedores → 2. produtos →
- tabelas de preço
padrao,regiaoecomprador→ 4. compradores → 5. tabelasespecifico.
- tabelas de preço
A resposta de todo envio:
{
"criados": 2,
"atualizados": 1,
"erros": [
{ "indice": 1, "campo": "ncm", "mensagem": "NCM com 8 números." },
{ "indice": 3, "campo": "codigo_vendedor", "mensagem": "O vendedor V99 não está cadastrado: envie os vendedores antes dos compradores." }
]
}
O lote inteiro só é recusado (422) quando falta a lista, ela vem vazia ou passa de 1.000 registros.
PUT /v1/vendedores — {"vendedores": [...]}
| Campo | Obrigatório | O que é |
|---|---|---|
codigo_erp |
sim | Código do vendedor no ERP. |
nome |
sim | Nome. |
email |
não | O e-mail que encaminha os pedidos: vira o endereço principal do vendedor. Outros endereços do mesmo vendedor se cadastram no painel. Só e-mails de vendedores ativos cadastrados são lidos pelo Softeum (pelo remetente, ou por quem encaminhou ou redirecionou). |
ativo |
não (padrão true) |
false desativa: os e-mails dele ficam retidos no painel. |
PUT /v1/produtos — {"produtos": [...]}
| Campo | Obrigatório | O que é |
|---|---|---|
codigo_erp |
sim | Código do produto no ERP (é ele que volta em itens[].produto.codigo_erp). |
sku |
sim | SKU do catálogo, único na empresa. |
descricao |
sim | Descrição. |
embalagem |
sim | Embalagem de venda (CX, FD, UN). |
unidades_por_embalagem |
sim | Inteiro maior que zero. |
ean |
não | EAN/GTIN com 8, 12, 13 ou 14 números. |
ncm |
não | NCM com 8 números. |
ipi_pct |
não (padrão 0) | IPI em %, de 0 a 100. |
linha |
não | Linha de produto. |
ativo |
não (padrão true) |
false = fora de linha. |
curl -s -X PUT "https://api.softeum.com.br/v1/produtos" \
-H "Authorization: Bearer $SOFTEUM_CHAVE" -H "Content-Type: application/json" \
-d '{"produtos": [{"codigo_erp": "4359", "sku": "VA-LVD-500R", "descricao": "Limpa-vidros refil 500 ml",
"ean": "7896543000415", "embalagem": "CX", "unidades_por_embalagem": 12, "ncm": "34022000", "ipi_pct": 0}]}'
PUT /v1/tabelas-de-preco/{codigo} — uma tabela inteira; {codigo} é o código da tabela no ERP.
| Campo | Obrigatório | O que é |
|---|---|---|
nome |
sim | Nome da tabela. |
nivel |
sim | especifico, comprador, regiao ou padrao (veja abaixo). |
codigo_comprador |
no nível especifico |
codigo_erp do comprador dono dos preços. |
regiao |
no nível regiao |
A região de preço (a mesma do campo regiao dos compradores). Gravada em maiúsculas e sem acento: Sudeste, sudeste e SUDESTE são a mesma região. A tabela de região que vem do ERP é sempre pela região escrita: a tabela pelos estados (as UFs do cadastro do comprador) nasce só pelo painel e pela planilha geral de preços. |
vigencia_inicio |
sim | aaaa-mm-dd. |
vigencia_fim |
não | aaaa-mm-dd; sem ela, vale sem prazo. |
precos |
sim | [{"codigo_produto": "4359", "preco": 76.04}]: preço líquido, sem impostos, por embalagem de venda. |
A tabela é substituída inteira: preço que não vier sai dela. Produto que não está no catálogo (ou repetido na
lista) volta em erros e os outros preços entram. Em cada item do pedido vale o primeiro nível que tiver preço:
especifico (de um comprador) → comprador (a tabela atribuída ao comprador em codigo_tabela_preco) → regiao (a da
região escrita do comprador; a tabela pelos estados, criada no painel, vale pelo uf dele, depois da região escrita) →
padrao. Duas tabelas do mesmo alcance não podem valer no mesmo dia: a segunda é recusada inteira (422, campo
vigencia_inicio). Esta API não mudou com a tabela pelos estados.
curl -s -X PUT "https://api.softeum.com.br/v1/tabelas-de-preco/T-SUDESTE" \
-H "Authorization: Bearer $SOFTEUM_CHAVE" -H "Content-Type: application/json" \
-d '{"nome": "Sudeste 2026", "nivel": "regiao", "regiao": "Sudeste", "vigencia_inicio": "2026-01-01",
"precos": [{"codigo_produto": "4359", "preco": 76.04}, {"codigo_produto": "4360", "preco": 52.80}]}'
PUT /v1/compradores — {"compradores": [...]}
| Campo | Obrigatório | O que é |
|---|---|---|
codigo_erp |
sim | Código do cliente no ERP (é ele que volta em comprador.codigo_erp). |
cnpj |
sim | Só os 14 números, com dígitos verificadores válidos. Único na empresa. |
razao_social |
sim | Razão social. |
nome_fantasia, cidade |
não | |
uf |
não | Duas letras maiúsculas (SP). Casa com a tabela de preço pelos estados, criada no painel. |
regiao |
não | Região de preço (casa com as tabelas de nível regiao). Gravada em maiúsculas e sem acento, como a da tabela. |
codigo_vendedor |
não | codigo_erp do vendedor responsável (envie os vendedores antes). null tira. |
codigo_tabela_preco |
não | codigo_erp de uma tabela de nível comprador (envie a tabela antes). null tira. Sem o campo, fica a tabela escolhida no painel; com ele, vale a do ERP, também por cima da escolhida no painel. |
ativo |
não (padrão true) |
false = bloqueado: os pedidos dele vão para a revisão do time. |
14. Por ERP: o caminho de cada um
A integração é a mesma para qualquer ERP: pegar o pedido no Softeum (busca ou aviso), traduzir os códigos e lançar pela porta de entrada de pedidos do ERP. O que muda de um ERP para outro é só essa porta de entrada e os dados internos que ela exige (4.6).
Os nomes abaixo são os mais comuns. Versão, módulos e personalizações mudam nomes e regras: confirme os detalhes com o time ou a consultoria do seu ERP antes de começar.
14.1 O roteiro, para qualquer ERP
- Escolha busca ou aviso (seção 0). ERP dentro da rede da empresa, sem endereço público: busca.
- Ache a porta de entrada de pedidos de venda do ERP: a API do ERP, a rotina automática de pedido, uma tabela de integração (pré-pedido) ou a importação de arquivo. Use a porta oficial: gravar direto nas tabelas do banco pula as regras do ERP (crédito, impostos, estoque), e alguns ERPs nem reconhecem o registro.
- Ponha os códigos do ERP no Softeum:
codigo_erpde compradores, produtos e vendedores, pela API (13.4), pela planilha ou na tela. Com eles, o lançamento não precisa procurar nada. - Monte a configuração fixa (4.6) e a tradução da condição de pagamento (seção 12).
- Escreva o lançamento: um pedido do Softeum vira um pedido de venda no ERP, com o
pedido.idgravado com restrição de unicidade (seção 6) enumero_compradorelinhaguardados para a nota (4.9). - Feche o ciclo: confirme com o número do ERP; o que o ERP não aceitar, recuse com o motivo.
- Teste com o simulador (seção 7) e um pedido real (seção 11) antes de ligar em produção.
14.2 A porta de entrada em cada ERP
| ERP | Porta de entrada do pedido de venda | O que o lançamento precisa ter em mãos |
|---|---|---|
| TOTVS Protheus | Rotina automática do pedido de venda (MATA410, por ExecAuto, em ADVPL ou TLPP). Pronto: o modelo pronto do arquivo em CSV (15.6). Também a Mensagem Única de pedido (EAI). |
Cliente (código + loja: 4.8), TES, condição de pagamento, tabela de preço, filial. |
| TOTVS Winthor | Serviço de pedido de venda do Winthor Anywhere (winthor-pedido-venda) ou a integradora de pedidos que a empresa já usa. |
RCA (vendedor: vendedor.codigo_erp), plano de pagamento, cobrança, região de preço, filial. |
| TOTVS Datasul | API ou Mensagem Única de pedido de venda, ou o TOTVS Conector. | Estabelecimento, natureza de operação, condição de pagamento, tabela de preço. |
| TOTVS RM | Serviços do RM para movimentos (o pedido de venda é um movimento). | Coligada e filial, tipo de movimento, condição de pagamento. |
| SAP S/4HANA e ECC | API de ordem de venda (OData API_SALES_ORDER_SRV), BAPI ou IDoc de pedido, normalmente pela ferramenta de integração da empresa (SAP Integration Suite, PI/PO). Pronto: o IDoc ORDERS05 e o odv-mapping por arquivo (15.6). |
Tipo de ordem, organização de vendas, canal de distribuição, setor de atividade, emissor da ordem (cliente com 10 dígitos: 4.8), pedido do cliente (numero_comprador). |
| SAP Business One | Service Layer: POST /b1s/v2/Orders. |
CardCode (cliente), DocumentLines com ItemCode e Quantity, data de entrega, referência do pedido do cliente (NumAtCard). |
| Sankhya | API de serviços pelo Gateway da Sankhya, com AppKey; o pedido entra como nota de venda. Gravar direto no banco não é reconhecido. | Empresa, parceiro (cliente), TOP, tipo de negociação, vendedor, natureza e centro de custo; o usuário de integração precisa ter acesso às rotinas. |
| Senior (Gestão Empresarial, Senior X) | API de pedido de venda do Senior X (autenticação por chave; o pedido é criado e a situação volta depois) ou os serviços do ERP. | Empresa e filial, cliente, condição de pagamento, representante, tabela de preço. |
| Omie | API do Omie (IncluirPedido). |
codigo_cliente_omie e codigo_produto (os ids do Omie: 4.8), etapa do pedido, parcelas. |
| Bling | API v3 de pedidos de venda (/pedidos/vendas). |
Contato e produtos pelo id do Bling, natureza de operação, parcelas. |
| Tiny / Olist ERP | API v3 de pedidos (POST /pedidos). |
Contato, lista de preço, natureza de operação, vendedor, endereço de entrega. |
| Microsoft Dynamics 365 Business Central | API v2.0: salesOrders e salesOrderLines. |
customerNumber (cliente), externalDocumentNumber (= numero_comprador), condição de pagamento. |
| Oracle NetSuite | REST, registro salesOrder. |
Cliente (entity), itens, referência do pedido do cliente (otherRefNum). |
| Odoo | API externa (XML-RPC ou JSON-RPC), modelo sale.order. |
Cliente (partner_id), linhas com produto, quantidade e preço, referência do pedido do cliente. |
| Outros (Mega, Benner, Viasoft, Nomus, Consinco, CISS, Linx, Alterdata, ERP próprio) | A API, a rotina de importação ou a tabela de integração de pedidos do próprio ERP. | O roteiro de 14.1 vale igual. |
14.3 ERP sem API, sem time de TI ou que só recebe arquivo
- ERP dentro da rede da empresa e sem ninguém para programar: fale com a Softeum sobre um conector ao lado do ERP. Ele busca os pedidos prontos, lança na porta de entrada do ERP, confirma ou recusa, e traz os cadastros do ERP para o Softeum. Nada precisa ser aberto para a internet.
- ERP que só recebe pedido por arquivo (XML, CSV, texto de layout fixo): use a entrega por arquivo (seção 15). O Softeum grava cada pedido, no layout do ERP, na pasta do servidor SFTP que o ERP lê.
15. Entrega por arquivo (SFTP)
Para o ERP que importa os pedidos de uma pasta e não recebe aviso nem busca pela API. Cada pedido aprovado vira um arquivo, no layout que o ERP importa, gravado na pasta de um servidor SFTP. Ninguém precisa programar: basta o ERP (ou a rotina de importação dele) ler a pasta.
15.1 Como funciona
- O pedido fica pronto (direto ou aprovado na revisão).
- O Softeum monta o arquivo do pedido pelo modelo cadastrado no painel (15.3) e conecta ao servidor SFTP.
- Grava o arquivo como
<nome>.tmpe, no fim, troca para o nome final: o ERP nunca lê um arquivo pela metade. Servidor que não deixa trocar o nome: escolha a gravação direta, em que o arquivo já vai com o nome final (se a conexão cair no meio, o Softeum apaga o pedaço quando consegue, mas o ERP pode ler o arquivo pela metade antes). - O pedido fica enviado no painel. O ERP importa o arquivo no ritmo dele.
O ERP importa cada pedido uma vez só, pelo pedido.id. Como no aviso (seção 6), o mesmo pedido pode chegar de
novo em casos raros: a conexão cai logo depois da troca de nome e a resposta do servidor se perde, ou o time reenvia
pelo painel um pedido com erro que o ERP já tinha importado. A rotina de importação guarda os pedido.id já
importados e ignora o arquivo de um pedido que já entrou. Por isso o nome do arquivo e o conteúdo levam o pedido.id.
O arquivo que já está na pasta com o mesmo nome, antes de gravar:
- igual ao de agora: a entrega vale, sem gravar de novo;
- de uma tentativa anterior deste pedido (ela gravou e caiu antes de registrar), mesmo com outro conteúdo (o modelo mudou entre as tentativas): a entrega vale, sem gravar de novo. Vale só a tentativa feita depois da última aprovação do pedido: o que voltou para a revisão e foi aprovado de novo pode estar com outro conteúdo (outro produto, outra quantidade), e o arquivo de antes que ficou na pasta conta como outro arquivo deste pedido (abaixo);
- o começo do arquivo de agora (sobra de uma gravação interrompida): é apagado e o arquivo é gravado inteiro; a entrega pede para conferir no ERP se o pedido não entrou pela metade;
- outro arquivo: não é substituído (o ERP pode estar lendo o de outro pedido). A entrega falha, diz o motivo e
tenta de novo nos intervalos abaixo. Com o nome do pedido (
{{pedido.id}}), a mensagem diz que é um arquivo deste pedido gravado antes com outro conteúdo: confira no ERP se o pedido entrou; se não entrou, apague o arquivo da pasta para ele ser gravado de novo.
A troca de nome sem resposta: a conexão que cai depois de pedir a troca do .tmp para o nome final deixa a dúvida
(a troca pode ter acontecido). A tentativa fica marcada e a seguinte confere a pasta: sem o arquivo e sem o .tmp, o
ERP já levou o arquivo, e a entrega vale sem gravar de novo (e pede para conferir no ERP); com o .tmp lá, a troca
não aconteceu, e o arquivo é gravado e trocado de novo.
O que tenta de novo e o que para as entregas:
Tenta de novo depois de 1, 5, 30, 120 e 360 minutos (os intervalos do aviso): servidor fora do ar ou que não responde, conexão recusada ou que cai no meio, endereço que não resolve ou de rede interna, pasta que não existe, usuário sem permissão na pasta e outro arquivo com o mesmo nome. Depois da última tentativa, o pedido fica com erro, com o motivo, para reenviar no painel. Problema da configuração (o endereço, a pasta, a permissão, o nome) termina em "Corrija na tela de Integração e reenvie o pedido."; o servidor fora do ar, em "reenvie pelo painel quando o servidor SFTP voltar.".
Para as entregas por arquivo, na hora e sem tentar de novo: o servidor recusou o acesso (usuário e senha ou chave), a chave privada não pôde ser lida ou a identificação do servidor mudou. Insistir só tentaria a senha errada no servidor do cliente. Os pedidos aprovados ficam prontos, esperando, e o painel mostra a parada na tela de Integração; não há prazo para eles irem para erro. As entregas voltam quando o teste da conexão passa, depois de corrigir o acesso ou aceitar a identificação nova. A conexão nova, o acesso trocado (outro servidor, usuário, senha ou chave) e a identificação nova aceita também esperam o teste passar antes da primeira entrega.
Vai para erro na hora, sem conectar: o modelo que não gera o arquivo do pedido (uma variável que ele não tem, um código que não cabe nas posições), com a linha do modelo; o arquivo que sai inválido (o JSON que não fecha, o XML mal formado), com a linha e a coluna do arquivo; e o arquivo do pedido maior que 5 MB.
Volta para a revisão, sem conectar: o pedido com item sem produto do catálogo, com produto inativo, com o comprador sem cadastro ou, quando o modelo usa
item.produto.codigo_erp, com produto sem o código do ERP no cadastro (antes o arquivo saía com o código em branco). A pendência de cada linha diz o motivo, e o time resolve na revisão. Para o arquivo sair mesmo sem o código (o ERP acha o produto pelo SKU), escreva o código dentro de{{#se item.produto.codigo_erp}}…{{/se}}ou com o filtropadraoantes dos de largura ({{item.produto.codigo_erp | padrao:0 | esquerda:6}}): depois deesquerdaoudireitao vazio já virou espaços, opadraonão troca nada, e o código conta como necessário. A tela de Integração avisa quando o modelo precisa do código e quando opadraovem depois deesquerda,direitaoujson. A exceção é a tentativa anterior com a troca de nome sem resposta: o arquivo dela pode já estar com o ERP, e o pedido vai para erro (e não para a revisão), com o motivo e o pedido de conferir no ERP se ele entrou; se não entrou, o time manda de volta para a revisão.A rodada de reenvios recomeça quando o pedido é reenviado pelo painel ou aprovado de novo na revisão: as falhas de antes não contam para os intervalos nem levam o pedido aprovado de novo para erro na primeira falha.
O nome do arquivo nos reenvios: cada tentativa guarda a versão do layout (15.2) que gerou o arquivo, e os reenvios automáticos usam o nome da versão da primeira tentativa: se uma versão nova for publicada no meio, o mesmo pedido não vai em dois arquivos com nomes diferentes. O conteúdo é o da versão de agora.
Confirmação: o arquivo não traz resposta. Se o ERP puder, confirme o pedido com o número dele pela API (
POST /v1/pedidos/{id}/confirmacao, seção 13.3): opedido.idvai no arquivo.Com o SFTP ligado, os pedidos vão por arquivo, mesmo com endereço de entrega (aviso) cadastrado.
15.2 Configurar no painel
Em Integração → Como o ERP recebe → Arquivo na pasta do ERP, o administrador informa:
| Campo | O que é |
|---|---|
| Endereço do servidor e porta | Ex.: sftp.suaempresa.com.br, porta 22. |
| Usuário e acesso | Senha ou chave privada (as aceitas estão abaixo), com a frase-senha se tiver. Ficam guardadas cifradas, não aparecem de novo e só valem para o servidor e o usuário de quando foram digitadas: trocar o endereço, a porta ou o usuário pede a senha (ou a chave e a frase-senha dela) de novo. |
| Pasta dos pedidos | Onde o ERP lê os pedidos (ex.: /entrada/pedidos). Vazia: a pasta inicial do usuário. |
| Formato | Um modelo (XML, CSV, TXT, JSON com os nomes de campo do ERP) ou o JSON oficial do pedido (o mesmo da API). |
| Tipo de conteúdo | XML, JSON ou texto (CSV, TXT): como os valores entram no modelo (15.4). |
| Nome do arquivo | Também um modelo, ex.: PEDIDO_{{pedido.numero_comprador}}_{{pedido.id}}.xml. |
| Codificação e quebra de linha | UTF-8 ou ISO-8859-1; LF ou CRLF (Windows). |
| Marca BOM | Em UTF-8, o arquivo pode começar com a marca BOM (EF BB BF), que alguns ERPs no Windows pedem para ler os acentos. |
Chaves aceitas: as que a entrega lê. OpenSSH (-----BEGIN OPENSSH PRIVATE KEY-----, o padrão do ssh-keygen),
PEM de RSA, DSA e EC (-----BEGIN RSA PRIVATE KEY----- e as outras duas) e o .ppk do PuTTY na versão 2 com RSA ou
DSA. O painel recusa as outras ao salvar, com o caminho para converter:
- PKCS#8 (
-----BEGIN PRIVATE KEY-----ou-----BEGIN ENCRYPTED PRIVATE KEY-----, o que o OpenSSL grava): regrave no formato OpenSSH comssh-keygen -p -f <arquivo da chave>e cole o arquivo regravado; .ppkda versão 3 (o padrão do PuTTYgen desde 2021) e.ppkde Ed25519 ou ECDSA: no PuTTYgen, abra a chave, use Conversions > Export OpenSSH key e cole o arquivo exportado.
Chave protegida por frase-senha: o painel pede a frase antes de salvar. Sem ela, a entrega não lê a chave e as entregas param.
A entrega começa desligada. Cadastre o acesso, teste a conexão e ligue a entrega quando o modelo do arquivo estiver pronto: o suporte da Softeum (diiego.martins@softeum.com.br) ajusta o modelo ao layout do seu ERP. Ao ligar, o painel diz qual arquivo vai ao ERP (o modelo pronto, sem ajuste, ele avisa).
O arquivo do pedido tem versões. A equipe Softeum monta o modelo no layout do seu ERP, a partir do arquivo que o ERP importa (de preferência o de um pedido que já passou pelo Softeum) e da especificação do layout, confere com os seus últimos pedidos e publica uma versão. Ela pode publicar antes de a TI cadastrar o servidor: o cadastro já sai com a versão em uso, mesmo antes do primeiro pedido e mesmo que o pedido de exemplo do painel não caiba nela (um código de 4 posições num ERP de 3): o pedido que ela não gerar vai para erro, com o motivo. A aba mostra a versão em uso (quem montou e quando) e o histórico das versões.
- Mudar o arquivo nesta tela cria uma versão nova, feita no painel, e os próximos pedidos já saem com ela. Quando a versão em uso é da equipe Softeum, o painel pede a confirmação antes; a equipe vê a mudança no histórico e pode voltar a versão dela.
- Se a equipe Softeum publicar outra versão enquanto a tela está aberta com mudança não salva (também no cadastro), salvar é recusado e a tela pede para recarregar: o arquivo da tela não grava por cima do novo nem é trocado sem aviso.
- Excluir a conexão apaga a senha e a chave, não as versões: cadastrando de novo, a conexão volta com a versão em uso.
A prévia com os seus pedidos. Além do pedido de exemplo, Usar os pedidos de verdade traz os últimos 20 pedidos que vão ao ERP, montados como a entrega monta: a prévia gera com o escolhido e diz quantos geram o arquivo e quais não (com o motivo). O modelo novo que o pedido de exemplo não gera (um código de 4 posições num ERP de 3) salva quando todos os pedidos de verdade geram.
Depois de salvar, use Testar conexão: o serviço que entrega os pedidos conecta ao servidor, confere a pasta e
grava, renomeia e apaga um arquivo de teste (.softeum-teste-*.tmp); com a gravação direta, grava e apaga, sem
renomear. Nenhum pedido é enviado. No máximo um teste a cada 30 segundos e 10 por hora, por empresa. O teste que passa
libera as entregas: a conexão nova e o acesso trocado esperam por ele (15.1).
- Identificação do servidor: no primeiro contato, o Softeum grava a impressão digital da chave do servidor
(
SHA256:...) e confere em todas as conexões seguintes. Se ela mudar, nada é gravado até o administrador conferir com a TI, aceitar a nova no painel e testar a conexão. - Servidor que só aceita endereços liberados: a entrega ainda não sai por um endereço fixo, então o servidor SFTP que só aceita conexões de endereços liberados não recebe os arquivos. Fale com o suporte da Softeum. O e-mail do suporte é diiego.martins@softeum.com.br.
15.3 O modelo do arquivo
O modelo é o arquivo como o ERP espera, com marcações entre chaves duplas. O painel traz modelos prontos (XML do
pedido, CSV com um item por linha, TXT de largura fixa e o pedido de venda do SAP pela interface odv-mapping), a
lista completa das variáveis e filtros e a prévia do arquivo com um pedido de exemplo, que já acusa erro com a linha.
A prévia é o arquivo que o Softeum grava, byte a byte: a mesma montagem, a mesma codificação e a mesma quebra de linha.
| Marcação | O que faz |
|---|---|
{{pedido.numero_comprador}} |
O valor do pedido, com o escape do tipo de conteúdo (15.4). |
{{#cada pedido.itens}} ... {{/cada}} |
Repete o trecho para cada item; o item da vez é item (item.posicao é 1, 2, 3...). |
{{#se pedido.data_entrega}} ... {{senao}} ... {{/se}} |
O trecho só entra quando o valor não está vazio. |
{{! comentário }} |
Não vai para o arquivo. |
A linha que só tem uma marcação de bloco sai inteira do arquivo. Para o {{#se}}, vazio é: sem valor, texto vazio,
zero (preço 0 é item sem preço) e item.ultimo em todo item menos o último.
Variáveis. Seguem os campos do pedido (seção 4): pedido.*, pedido.comprador.*, pedido.vendedor.*, os itens
(item.produto.ean, item.quantidade_erp...), pedido.quantidade_itens, empresa.cnpj (a sua empresa) e agora
(quando o arquivo é gerado; não vale no nome do arquivo, que precisa ser o mesmo em todas as tentativas). E também:
| Variável | O que é |
|---|---|
arquivo.linha |
O número desta linha no arquivo (1, 2, 3...). No rodapé, é o total de linhas, com o cabeçalho e o próprio rodapé. Também serve de sequencial do registro. |
item.ultimo |
Sim só no último item. No JSON, a vírgula entre os itens: {{#se item.ultimo}}{{senao}},{{/se}}. |
item.preco_unitario_erp |
O preço na unidade do ERP, para ir com item.quantidade_erp (76,04 a CX de 12 → 6,336667 a UN). O item.preco_unitario é o preço na unidade do comprador, para ir com item.quantidade. Com 6 casas, a conta quantidade × preço bate com o valor do item. |
item.desconto_percentual, item.desconto_tipo |
Só na indústria que lê o desconto de cada item (a equipe Softeum liga): o desconto escrito no item (10,50% → 10.5, com numero:2) e o tipo (comercial, especial ou promocional); vazios no item sem desconto. O item.preco_unitario já vem com ele. O arquivo que usa essas variáveis não sai do pedido lido sem o desconto de cada item (o pedido fica com erro, sem conectar). |
Filtros. Vêm depois da variável, com |, um depois do outro:
| Filtro | Exemplo | Resultado |
|---|---|---|
data |
{{pedido.data_entrega | data:DD/MM/AAAA}} |
07/10/2026 (também DDMMAAAA, AAAAMMDD, HH, mm, ss; com dois-pontos, entre aspas: data:"HH:mm") |
numero |
{{item.preco_unitario | numero:2:,}} |
76,04 (casas, separador decimal e de milhar; a metade arredonda para cima) |
numero sem separador |
{{pedido.valor_total | numero:2:}} |
325920 (casas implícitas, para TXT) |
digitos |
{{pedido.comprador.cnpj | digitos}} |
só os números |
mascara |
{{pedido.comprador.cnpj | mascara:##.###.###/####-##}} |
12.345.678/0001-95 (cada # recebe um caractere do valor) |
trecho |
{{pedido.comprador.codigo_erp | trecho:7:2}} |
2 caracteres a partir do 7º; trecho:-2 pega os 2 últimos |
esquerda / direita |
{{item.quantidade_erp | numero:4: | direita:12:0}} |
000003600000 (largura fixa, 15.5) |
corta, padrao, troca |
{{pedido.observacoes | corta:35}} |
no máximo 35 caracteres |
maiusculas, minusculas, sem_acento |
{{item.produto.descricao | sem_acento | maiusculas}} |
LIMPA-VIDROS REFIL 500 ML |
csv |
{{item.produto.descricao | csv}} |
entre aspas quando tem ; ou aspas (csv:, para o separador vírgula) |
json |
{{pedido.observacoes | json}} |
o valor em JSON: texto entre aspas, número como número e vazio como null (também o vazio que outro filtro devolveu: sem a data, {{pedido.data_entrega | data:DD/MM/AAAA | json}} dá null) |
esquerda e direita cortam só texto livre: razão social, nome, descrição, endereço, observações e condição de
pagamento. Código, CNPJ, EAN, unidade, número e data que não cabem nas posições não são cortados: o arquivo não é
gerado e o pedido fica com erro, dizendo a linha do modelo, porque cortado o valor vira outro (o código de outro
produto; 1.440,000 que vira 14,400). Se o ERP aceita o código cortado, ponha corta antes:
{{pedido.numero_comprador | corta:15 | esquerda:15}}.
Valor vazio em cada filtro: data, numero, mascara e os de texto devolvem vazio; esquerda completa o campo com
espaços e direita:N:0 com zeros; padrao põe o texto dado; json devolve null, também depois de outro filtro. O
padrao só troca o vazio onde aparece na cadeia: ponha antes de esquerda, direita e json
(| padrao:0 | esquerda:6 sai 0 ; | esquerda:6 | padrao:0 sai em branco).
A conferência avisa do que costuma sair errado sem quebrar o arquivo (o modelo já salvo continua gerando):
datacom uma letra que não é código (YYYY,hh,SSsaem como estão: useAAAA,HH,ss) eMMlogo depois da hora (é o mês: para os minutos,mm);digitosnum número (3.259,20 vira 32592; para as casas sem separador,numero:2:) e o separador de milhar sem o decimal, ou igual a ele (numero:2:,:.é o certo);esquerdaoudireitaantes de outro filtro (o outro pode mudar o tamanho do campo: deixe a largura no fim) e o campo com mais de 2.000 posições;- no nome do arquivo,
pedido.arquivo_original(o link muda a cada tentativa) e um pedaço do começo depedido.id(o começo é a hora: dois pedidos do mesmo minuto ganhariam o mesmo nome; use o id inteiro outrecho:-12); - no CSV, o campo de texto sem
| csv(a unidadeCX;99999que o comprador escreveu abriria uma coluna a mais) e, no JSON, o texto, a data ou o valor vazio fora das aspas e sem| json; - o arquivo do pedido de exemplo maior que 100 KB (um pedido com muitos itens passaria de 5 MB e não seria gerado).
15.4 Tipo de conteúdo e codificação
O tipo de conteúdo diz como os valores entram no modelo. O texto fixo do modelo vai como está escrito (em ISO-8859-1, com as mesmas trocas dos valores, abaixo).
| Tipo | Os valores entram |
|---|---|
| XML | Com & < > " ' trocados por & e os outros. Dentro de <![CDATA[ ... ]]> do modelo, como estão. |
| JSON | Com o escape de texto do JSON (\", \\, \n): o modelo põe as aspas, como em "descricao": "{{item.produto.descricao}}". Número vai sem aspas: "quantidade": {{item.quantidade_erp}}. |
| Texto (CSV, TXT) | Sem quebra de linha nem tabulação (viram espaço), para o valor nunca quebrar o registro. |
Cada valor entra já como vai ficar no arquivo, antes dos filtros: com os acentos compostos (o acento separado da letra,
comum em texto copiado de PDF, vira a letra acentuada) e, em ISO-8859-1, com o parecido no lugar do que a codificação
não tem: ™ vira TM, … vira ..., € vira EUR, — vira -, aspas curvas viram retas e o resto vira ?.
Assim esquerda, direita e corta contam as posições que o ERP lê.
Exemplo de ERP que importa JSON com os nomes de campo dele:
{
"pedido": "{{pedido.numero_comprador}}",
"cliente": "{{pedido.comprador.codigo_erp}}",
"observacao": {{pedido.observacoes | json}},
"itens": [
{{#cada pedido.itens}}
{ "produto": "{{item.produto.codigo_erp}}", "descricao": "{{item.produto.descricao}}", "quantidade": {{item.quantidade_erp}} }{{#se item.ultimo}}{{senao}},{{/se}}
{{/cada}}
]
}
O arquivo do pedido de exemplo:
{
"pedido": "45882",
"cliente": "C001",
"observacao": null,
"itens": [
{ "produto": "4359", "descricao": "Limpa-vidros refil 500 ml", "quantidade": 360 },
{ "produto": "4360", "descricao": "Desengordurante ação rápida 500 ml", "quantidade": 144 }
]
}
O arquivo de cada pedido é conferido antes de ir ao servidor: o JSON tem de ser válido e o XML bem formado (as marcas
abrem e fecham na ordem, o & escapado). O que não passa vai para erro, sem conectar, com a linha e a coluna onde
quebrou. A prévia mostra o mesmo, com o pedido de exemplo e com os pedidos de verdade (o caso comum é a vírgula depois
do último item, ou um EAN vazio fora das aspas). O modelo novo ou mudado que, com o pedido de exemplo, gera um JSON
inválido ou um XML mal formado não é salvo; o modelo que já estava salvo assim continua, com o aviso.
O XML em ISO-8859-1 precisa da declaração na primeira linha (<?xml version="1.0" encoding="ISO-8859-1"?>): sem
ela, o leitor de XML entende UTF-8 e recusa o arquivo ou estraga os acentos. A prévia avisa e oferece pôr.
15.5 TXT de largura fixa
- Codificação: use ISO-8859-1, uma posição por letra. Em UTF-8 a largura conta letras, mas a letra com acento
ocupa 2 bytes, e o ERP que conta bytes lê os campos fora do lugar (a prévia avisa, com
esquerdaoudireita). - Todo campo leva
esquerdaoudireita, até a data opcional (data:AAAAMMDD | esquerda:8): vazia, ela vira 8 espaços e os campos seguintes ficam no lugar. - Número: zeros à esquerda e as casas implícitas,
numero:2: | direita:15:0(12,34 vira000000000001234); com vírgula,numero:2:, | direita:15:0. - Quantidade: com 4 casas (
numero:4:), as que o pedido pode ter: com 3, o item vendido a peso com 12,5625 KG vai como 12,563. - Contagens e totais:
pedido.quantidade_itens,pedido.valor_totalearquivo.linhano rodapé (o total de linhas, que já conta o registro opcional quando ele entra). - Sequência dos itens:
item.posicao(1, 2, 3), com zerosdireita:3:0(001) ou de 10 em 10, como o SAP:{{item.posicao | direita:5:0}}0(000010, 000020).
Exemplo: cabeçalho (1), um registro por item (2) e o rodapé (9) com a quantidade de itens, o total e o número de linhas do arquivo.
{{! 1 = cabeçalho, 2 = um por item, 9 = rodapé }}
1{{pedido.numero_comprador | esquerda:15}}{{pedido.comprador.cnpj | direita:14:0}}{{pedido.recebido_em | data:DDMMAAAA}}{{pedido.data_entrega | data:AAAAMMDD | esquerda:8}}
{{#cada pedido.itens}}
2{{item.posicao | direita:3:0}}{{item.produto.codigo_erp | esquerda:6}}{{item.produto.descricao | sem_acento | maiusculas | esquerda:30}}{{item.quantidade_erp | numero:4: | direita:11:0}}{{item.preco_unitario_erp | numero:6: | direita:12:0}}
{{/cada}}
9{{pedido.quantidade_itens | direita:3:0}}{{pedido.valor_total | numero:2: | direita:12:0}}{{arquivo.linha | direita:4:0}}
O arquivo do pedido de exemplo (a descrição do item 2 passou de 30 posições e foi cortada):
145882 123456780001953009202620261007
20014359 LIMPA-VIDROS REFIL 500 ML 00003600000000006336667
20024360 DESENGORDURANTE ACAO RAPIDA 5000001440000000006791667
90020000003259200004
15.6 Layouts comuns
- SAP (
odv-mapping): o modelo pronto, com o cliente pelo CNPJ e o produto pelo EAN. Troque os valores fixos pelos do seu SAP. Produto sem EAN vai com oEANCodevazio; se o seu SAP aceita o código do material, use<MaterialCode>{{item.produto.codigo_erp}}</MaterialCode>. - SAP (IDoc ORDERS05): o modelo pronto SAP: IDoc de pedido (ORDERS05), um IDoc em XML por pedido para a porta
de arquivo do SAP ou para o PI/PO, com os segmentos na ordem do tipo: o controle (
EDI_DC40), a organização de vendas (E1EDK14), a data do pedido (E1EDK03022) e a de entrega (002), o emissor da ordem (E1EDKA1AG, comcomprador.codigo_erp), o pedido do comprador (E1EDK02001) e, em cada item, a quantidade do ERP (E1EDP01), a entrega do item (E1EDP20) e o material (E1EDP19002, comproduto.codigo_erp). Troque as portas, os parceiros e a organização de vendas pelos do seu SAP; a equipe SAP prepara o perfil de parceiro (ORDERS, código de processoORDE). - TOTVS Protheus: não tem importação de pedido por arquivo pronta; a TI do cliente faz a rotina (ExecAuto do
MATA410) que lê o arquivo. O modelo pronto TOTVS Protheus: pedido de venda (CSV) traz um item por linha, com as
colunas no nome do campo do Protheus: o cliente e a loja saem do código do comprador no ERP
(
{{pedido.comprador.codigo_erp | troca:-: | trecho:1:6}}etrecho:7:2, com ou sem o hífen), o CNPJ para achar o cliente sem o código, o produto e o código de barras, a quantidade e o preço na unidade do ERP com ponto decimal (numero:4enumero:6), a entrega do item, o pedido e o item do comprador para a nota (C6_NUMPCOMeC6_ITEMPC) e oSOFTEUM_ID, que a rotina guarda noC5_XIDSOFTpara o mesmo pedido não entrar duas vezes. OC6_ITEMa rotina numera (Soma1, que passa do 99). - CSV: o modelo pronto leva
| csvem todo campo de texto (o valor com;ou aspas vai entre aspas) eitem.preco_unitario_erpao lado deitem.quantidade_erp(o preço na mesma unidade da quantidade). - CSV lido por rotina que só corta no
;: aspas não protegem. Tire o;do texto:{{item.produto.descricao | troca:";":","}}. - EDI de pedido (registros 01, 04, 09): quantidade e preço na unidade do comprador (
item.quantidade,item.unidade,item.preco_unitario), EAN com 14 posições (direita:14:0) e datasdata:AAAAMMDDHHmm.
O que o modelo não faz: um arquivo leva um pedido só (não junta vários pedidos num arquivo); não faz contas além das variáveis prontas (a soma das quantidades, o total de linhas no cabeçalho); e o que o pedido não traz (o código da condição de pagamento, a TES, a tabela de preço, a transportadora) vai fixo no modelo ou fica com o padrão do cadastro no ERP.
Exemplo: o pedido de venda do SAP pela interface de arquivos (odv-mapping), com o cliente pelo CNPJ e o produto
pelo EAN. Troque os valores fixos pelos do seu SAP.
<odv-mapping xmlns="urn:odv-mapping-1.0">
<Order>
<OrderType>ZD30</OrderType>
<SalesOrg>BR10</SalesOrg>
<DistrChannel>30</DistrChannel>
<Division>99</Division>
<CNPJSoldTo>{{pedido.comprador.cnpj | digitos}}</CNPJSoldTo>
<PurchaseOrder>{{pedido.numero_comprador | corta:35}}</PurchaseOrder>
<PurchaseOrderDate>{{#se pedido.data_pedido}}{{pedido.data_pedido | data:AAAAMMDD}}{{senao}}{{pedido.recebido_em | data:AAAAMMDD}}{{/se}}</PurchaseOrderDate>
<RequestDeliveryDate>{{pedido.data_entrega | data:AAAAMMDD}}</RequestDeliveryDate>
{{#cada pedido.itens}}
<Item>
<ItemNumber>{{item.posicao}}</ItemNumber>
<EANCode>{{item.produto.ean}}</EANCode>
<Quantity>{{item.quantidade_erp}}</Quantity>
</Item>
{{/cada}}
</Order>
</odv-mapping>
A data do pedido (PurchaseOrderDate) é a que o documento do comprador traz (pedido.data_pedido, a emissão); sem
ela, a de quando o e-mail chegou (pedido.recebido_em). A data de entrega de cada item, quando o documento traz uma
por item, está em item.data_entrega.
Na indústria que lê o desconto de cada item (4.7), o desconto vai no <Condition> do item, logo depois de
<ItemCategory> no modelo pronto do SAP, com os três tipos trocados pelos códigos da condição no SAP (os do exemplo;
use os do seu SAP). O item sem desconto não leva o <Condition>, e o pedido sem desconto sai igual ao do modelo
pronto:
{{#se item.desconto_percentual}}
<Condition>
<ConditionType>{{item.desconto_tipo | troca:comercial:ZCBR | troca:especial:ZESP | troca:promocional:ZPBR}}</ConditionType>
<Value>{{item.desconto_percentual | numero:2}}</Value>
<Currency>%</Currency>
</Condition>
{{/se}}
O arquivo que usa o desconto de cada item não sai do pedido lido sem ele (o lido antes de ligar): o pedido fica com erro, sem conectar, para lançar à mão.