Multi-Tenant - 12 - Exemplo de NFSe para o Ambiente Nacional em Golang
Show, vamos fechar a “esteira” com:
- Estados da NFS-e (state machine simples, mas robusta).
- Campos extras recomendados na tabela
nfse. - Diagrama de estados (Mermaid).
- Como isso conversa com o fluxo Go + Delphi + ACBr.
1. Estados recomendados para nfse.situacao
Sugestão de enum lógico (armazenado como TEXT):
-
rascunho- Criada internamente, mas ainda não enviada para transmissão.
-
pendente_transmissao- Gravada no banco, já “fechada” para envio, aguardando serviço Delphi/ACBr.
-
em_transmissao- Serviço Delphi pegou a nota e está no meio do processo de envio.
-
autorizada- NFSe gerada/autorizada pelo Ambiente Nacional.
-
rejeitada- Provedor retornou erro definitivo (precisa corrigir dados e emitir nova).
-
cancelada- NFSe já autorizada e depois cancelada com sucesso.
-
erro_interno- Erro na integração interna (banco, ACBr, config, etc.), não chegou nem a bater na prefeitura ou resultado não confiável.
Você pode começar com um subconjunto (pendente_transmissao, autorizada, rejeitada, cancelada) e depois evoluir.
2. Campos extras recomendados em nfse
Além dos que já temos, sugeriria:
ALTER TABLE public.nfse
ADD COLUMN numero_nfse_provedor TEXT NULL, -- número gerado pela prefeitura, se diferente
ADD COLUMN codigo_verificacao TEXT NULL, -- código verificador da NFS-e
ADD COLUMN link_nfse TEXT NULL, -- URL para consulta visual da NFSe (se o provedor oferecer)
ADD COLUMN data_autorizacao TIMESTAMPTZ NULL,
ADD COLUMN data_cancelamento TIMESTAMPTZ NULL,
ADD COLUMN motivo_cancelamento TEXT NULL;
-
numero_nfse_provedor: às vezes o número final da NFSe não é igual ao número do RPS. -
codigo_verificacao: padrão em muitos municípios/portais. -
link_nfse: facilita abrir direto o DANFSe/espelho. -
data_autorizacao: carimbada na autorização. -
data_cancelamento/motivo_cancelamento: eventos de cancelamento.
3. Máquina de estados (Mermaid)
3.1. Emissão
stateDiagram-v2
[*] --> rascunho
rascunho --> pendente_transmissao: enviar_pra_fila_transmissao()
pendente_transmissao --> em_transmissao: worker_delphi_inicia()
em_transmissao --> autorizada: retorno_sucesso()
em_transmissao --> rejeitada: retorno_erro_fiscal()
em_transmissao --> erro_interno: falha_integracao/timeout
rejeitada --> pendente_transmissao: corrigir_dados_e_reenviar()
erro_interno --> pendente_transmissao: corrigir_infra_e_reprocessar()
autorizada --> cancelada: cancelar_nfse()
autorizada --> [*]
cancelada --> [*]3.2. Cancelamento (evento separado)
stateDiagram-v2
autorizada --> cancelada: cancelamento_autorizado()
autorizada --> erro_interno: falha_cancelamento()
erro_interno --> autorizada: retentar_cancelamento()4. Como isso encaixa no fluxo Go + Delphi + ACBr
4.1. Na API Go (emissão)
-
Usuário salva rascunho
-
POST /nfse/draft(opcional) -
Persiste
nfsecomsituacao = 'rascunho'. -
Pode permitir edição, inclusão/remoção de itens, etc.
-
-
Usuário manda emitir
-
POST /tenants/{tenantID}/nfse(ouPOST /nfse/{id}/emitir):-
Valida o rascunho.
-
Persiste/atualiza
nfse:situacao = 'pendente_transmissao'.
-
Retorna algo como:
{ "id": 123, "situacao": "pendente_transmissao" }
-
-
-
Consultas
-
GET /tenants/{tenantID}/nfse/{id}:- Retorna o estado atual:
rascunho,pendente_transmissao,autorizada, etc.
- Retorna o estado atual:
-
4.2. No serviço Delphi (emissão)
-
Busca pendentes
SELECT ... FROM nfse WHERE situacao = 'pendente_transmissao' LIMIT 10.
-
Marca como “em_transmissao”
-
Ao pegar a NFSe pra processar:
UPDATE nfse SET situacao = 'em_transmissao', updated_at = NOW() WHERE id = :id;
-
-
Tenta enviar via ACBr
-
Se tudo ok, prefeitura autorizou:
UPDATE nfse SET situacao = 'autorizada', numero_nfse_provedor = :num, codigo_verificacao = :cod_verif, link_nfse = :link, data_autorizacao = :dt_aut, xml_envio = :xml_envio, xml_retorno = :xml_retorno, protocolo_envio = :protocolo, codigo_mensagem_retorno = NULL, mensagem_retorno = NULL, updated_at = NOW() WHERE id = :id; -
Se prefeitura rejeitou (erro fiscal, dado inválido, etc.):
UPDATE nfse SET situacao = 'rejeitada', codigo_mensagem_retorno = :cod_erro, mensagem_retorno = :msg_erro, xml_envio = :xml_envio, xml_retorno = :xml_retorno, updated_at = NOW() WHERE id = :id; -
Se deu algum erro interno (config errada, certificação, conexão, exception Delphi):
UPDATE nfse SET situacao = 'erro_interno', codigo_mensagem_retorno = 'ERR_INT', mensagem_retorno = :msg_erro, updated_at = NOW() WHERE id = :id;
-
Isso garante que o usuário na API consiga ver claramente:
“Deu erro meu (dados da nota)” →
rejeitada.“Deu erro deles (infra, integração, ACBr, cert)” →
erro_interno.
4.3. Cancelamento
Na API Go:
-
Endpoint:
POST /tenants/{tenantID}/nfse/{id}/cancelar-
Valida se
situacao = 'autorizada'. -
Cria um registro de “pedido de cancelamento” ou já muda
nfse.situacaopara algo tipopendente_cancelamento(se quiser granular). -
Serviço Delphi lê esses pedidos e chama o método de cancelamento do ACBr (
CancelarNFSe, etc.). -
Atualiza
nfsepara:-
canceladacomdata_cancelamento,motivo_cancelamento. -
ou
erro_interno/rejeitada(se provedor não aceitar o cancelamento).
-
-
Se quiser granularidade extra, pode ter uma coluna situacao_cancelamento ou usar um pequeno enum de evento, mas para começo de conversa, só situacao = 'cancelada' resolve.
5. Visão final da “linha do tempo” da NFS-e
-
API Go
rascunho→pendente_transmissao.
-
Serviço Delphi
pendente_transmissao→em_transmissao→autorizada/rejeitada/erro_interno.
-
API Go (consultas)
- Mostra sempre o estado atual (e o histórico de mensagens/códigos, se quiser guardar).
-
Cancelamento
autorizada→cancelada(ouerro_internocaso falhe).