Multi-Tenant - 12 - Exemplo de NFSe para o Ambiente Nacional em Golang

Show, vamos fechar a “esteira” com:

  1. Estados da NFS-e (state machine simples, mas robusta).
  2. Campos extras recomendados na tabela nfse.
  3. Diagrama de estados (Mermaid).
  4. Como isso conversa com o fluxo Go + Delphi + ACBr.

1. Estados recomendados para nfse.situacao

Sugestão de enum lógico (armazenado como TEXT):

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;

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)

  1. Usuário salva rascunho

    • POST /nfse/draft (opcional)

    • Persiste nfse com situacao = 'rascunho'.

    • Pode permitir edição, inclusão/remoção de itens, etc.

  2. Usuário manda emitir

    • POST /tenants/{tenantID}/nfse (ou POST /nfse/{id}/emitir):

      • Valida o rascunho.

      • Persiste/atualiza nfse:

        • situacao = 'pendente_transmissao'.
      • Retorna algo como:

        {
          "id": 123,
          "situacao": "pendente_transmissao"
        }
        
  3. Consultas

    • GET /tenants/{tenantID}/nfse/{id}:

      • Retorna o estado atual: rascunho, pendente_transmissao, autorizada, etc.

4.2. No serviço Delphi (emissão)

  1. Busca pendentes

    • SELECT ... FROM nfse WHERE situacao = 'pendente_transmissao' LIMIT 10.
  2. Marca como “em_transmissao”

    • Ao pegar a NFSe pra processar:

      UPDATE nfse 
         SET situacao = 'em_transmissao',
             updated_at = NOW()
       WHERE id = :id;
      
  3. 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:


4.3. Cancelamento

Na API Go:

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

  1. API Go

    • rascunhopendente_transmissao.
  2. Serviço Delphi

    • pendente_transmissaoem_transmissaoautorizada / rejeitada / erro_interno.
  3. API Go (consultas)

    • Mostra sempre o estado atual (e o histórico de mensagens/códigos, se quiser guardar).
  4. Cancelamento

    • autorizadacancelada (ou erro_interno caso falhe).