Integrações Contábeis (ERP)
Visão Geral
O módulo de contabilidade conecta o PopinaFlow a quatro sistemas ERP/contábeis brasileiros. A cada lançamento gerado (vendas, pagamentos, folha, etc.) o AccountingQueueProcessor enfileira o envio assíncrono ao ERP ativo do tenant via BullMQ.
| Provedor | Tipo | Autenticação | Módulo cliente |
|---|---|---|---|
| Omie | ERP cloud BR | app_key + app_secret no body | OmieClient |
| Conta Azul | ERP cloud BR | OAuth2 Bearer + auto-refresh | ContaAzulClient |
| SAP Business One | ERP on-premise | Session cookie B1SESSION (Basic Auth login) | SapClient |
| SPED ECD | Arquivo fiscal | N/A — geração local/S3 | SpedClient |
Localização
| Camada | Arquivo |
|---|---|
| Clientes HTTP | backend/src/accounting/providers/{omie,contaazul,sap,sped}.client.ts |
| Retry util | backend/src/accounting/providers/retry.util.ts |
| Schema de credenciais | backend/src/accounting/schemas/accounting-integration.schema.ts |
| Módulo de fila | backend/src/accounting-queue/ |
| View Admin | frontend-react/src/views/admin/accounting/AccountingIntegrationsView.tsx |
Schema: AccountingIntegration
Um documento por (tenant, provider) — índice único composto.
| Campo | Tipo | Descrição |
|---|---|---|
provider | enum | omie | contaazul | sap | sped |
enabled | boolean | Integração ativa. Default false até configurar. |
testMode | boolean | Quando true, usa stub CSV em vez de chamada real. Default true. |
apiKeyEncrypted | EncryptedField | Credencial primária (app_key / access_token / username SAP) — AES-256-GCM |
apiSecretEncrypted | EncryptedField | Credencial secundária (app_secret / refresh_token / password SAP) |
baseUrlOverride | string | null | URL customizada. Obrigatório para SAP. Para SPED: caminho S3 ou filesystem. |
lastSyncAt | Date | Timestamp do último envio com sucesso. |
lastError | string | Mensagem do último erro (limpo na próxima sincronização com sucesso). |
OmieClient
Endpoint: POST https://app.omie.com.br/api/v1/financas/lancamentos/ (call: IncluirLancamento)
Idempotência: campo cCodIntLanc com formato popinaflow-{tenantSlug}-{entryId}-{pairIndex}. O Omie de-dupes por esse campo dentro da mesma conta — sem suporte a idempotency header.
Mapeamento de lançamentos compostos: entradas N:M (N débitos × M créditos) são cross-pareadas — cada par é enviado como um lancamento separado com sufixo _D1C1, _D1C2, etc.
Mapeamento de tipo:
origin do JournalEntry | cTipo Omie |
|---|---|
sale, payment_received, card_receivable | LCR (crédito) |
expense, supplier_payment, payroll | LCP (débito) |
| outros | ADI |
Limitação conhecida: o campo nCodCC (conta bancária Omie) é enviado como 0 — o contador precisa remapear na interface do Omie.
ContaAzulClient
Endpoint: POST https://api.contaazul.com/v1/financial-entries
Idempotência: header Idempotency-Key com popinaflow-{tenantSlug}-{entryId}-{pair} (truncado para 36 chars).
Refresh automático de token: se a chamada retorna 401, o cliente troca o refresh_token por um novo access_token via POST /auth/token e retenta uma vez. Se o refresh falhar, o erro é surfaceado para o operador reautorizar.
Mapeamento: cada par de linhas gera um financial-entry com date, description, reference (número do lançamento), amount, debit_account, credit_account.
SapClient
Endpoint: POST {baseUrl}/JournalEntries (SAP B1 Service Layer)
Sessão: o cliente faz POST {baseUrl}/Login com UserName, Password, CompanyDB → extrai B1SESSION do header Set-Cookie. A cookie é reutilizada por até 30 minutos. Em 401 o cliente re-autentica uma vez automaticamente.
Campo obrigatório: baseUrlOverride deve ser configurado (ex: https://192.168.1.100:50000/b1s/v1). Sem esse campo a chamada falha imediatamente.
SSL self-signed: para servidores SAP on-premise com certificado auto-assinado, defina ACCOUNTING_SAP_REJECT_UNAUTHORIZED=false no .env do backend.
Mapeamento de campos:
| JournalEntry | SAP JournalEntries |
|---|---|
competenceDate | ReferenceDate, DueDate, TaxDate |
description (100 chars) | Memo |
entryNumber | Reference1 |
lines[].accountCode | JournalEntryLines[].AccountCode |
lines[].debit | Debit |
lines[].credit | Credit |
lines[].memo | LineMemo |
SpedClient
O SPED ECD não é uma API HTTP — gera um arquivo texto no formato da Receita Federal (Blocos 0, I, Z) para transmissão manual via PVA SPED Contábil.
Destinos de upload configurados via baseUrlOverride:
| Prefixo | Comportamento |
|---|---|
file:///var/sped | Salva no filesystem do servidor |
s3://meu-bucket/sped | Upload para AWS S3 (via @aws-sdk/client-s3) |
null / não configurado | Retorna o conteúdo em memória (para download manual) |
Blocos gerados automaticamente: 0 (abertura + I050 plano de contas + 0990), I (I200 cabeçalho + I250 linhas + I990), Z (fechamento).
Blocos fora do escopo: Block J (balanço), assinatura digital ICP-Brasil, transmissão via PGE-SPED. O arquivo gerado é para revisão do contador antes da transmissão oficial.
retry.util.ts — Política de Retentativa Compartilhada
Todos os clientes usam retryWithBackoff():
| Parâmetro | Valor |
|---|---|
maxAttempts | 3 |
| Delays | 1s → 2s → 4s (base-2 exponencial) |
429 Too Many Requests | Respeita header Retry-After; se ausente usa backoff normal |
4xx (exceto 429) | Não-retryável — lança imediatamente |
5xx / erros de rede | Retryável até maxAttempts |
Segurança de Credenciais
apiKeyEncrypted e apiSecretEncrypted são armazenados como EncryptedField (AES-256-GCM via TenantKmsService). Nunca são retornados em texto claro pela API — a interface exibe apenas **** para confirmar que foram configurados.
Testar a Conexão
Cada cliente expõe um método ping() que realiza uma chamada leve de validação:
| Cliente | Chamada de ping |
|---|---|
OmieClient | POST /geral/categorias/ com nPagina=1, nRegPorPagina=1 |
ContaAzulClient | GET /v1/company |
SapClient | POST /Login + POST /Logout imediato |
SpedClient | N/A — validação via generateAndSave() em modo dry-run |
Resposta de sucesso: { ok: true, providerVersion: "..." }. Resposta de falha: { ok: false, error: "..." }.