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

Boa, vamos fechar com a parte de dashboard no Grafana pra NFS-e 😄

Vou assumir:

A ideia é ter 1 dashboard NFSe com:


1. Variáveis do dashboard (filtros)

1.1. Variável tenant

Tipo: Query

SELECT DISTINCT tenant_id AS "__value", tenant_id::text AS "__text"
FROM public.nfse
ORDER BY tenant_id;

Assim você filtra o dashboard inteiro por um ou mais tenants.


2. Layout sugerido do dashboard

Sugestão de organização:

  1. Linha 1 – KPI geral (últimos 30 dias)

    • Total de NFS-e

    • Total autorizado

    • Taxa de erro (%)

  2. Linha 2 – Gráficos por dia

    • NFS-e emitidas por dia

    • Taxa de erro por dia

  3. Linha 3 – Erros por substatus técnico

    • Barras por substatus_tecnico

    • Tabela de detalhe

  4. Linha 4 – Operação

    • Tabela de NFS-e pendentes de transmissão

    • Tabela de últimas rejeitadas/erros internos

Vou te dar exemplos de queries para cada painel.


3. Painéis e queries (usando vw_nfse_dashboard_30d)

3.1. KPI: Total de NFS-e (últimos 30 dias)

Tipo de painel: Stat

Query:

SELECT
  SUM(total_notas) AS total_nfse
FROM public.vw_nfse_dashboard_30d
WHERE (${tenant:raw} IS NULL OR tenant_id IN (${tenant:raw}));

No Grafana, configure:


3.2. KPI: Total Autorizadas (últimos 30 dias)

Tipo: Stat

SELECT
  SUM(qtd_autorizadas) AS total_autorizadas
FROM public.vw_nfse_dashboard_30d
WHERE (${tenant:raw} IS NULL OR tenant_id IN (${tenant:raw}));

3.3. KPI: Taxa de erro total (últimos 30 dias)

Tipo: Stat

SELECT
  ROUND(
    (SUM(qtd_rejeitadas + qtd_erro_interno)::numeric
     / NULLIFnumeric * 100
  , 2) AS taxa_erro_percent
FROM public.vw_nfse_dashboard_30d
WHERE (${tenant:raw} IS NULL OR tenant_id IN (${tenant:raw}));

Configura a unidade como percent (%).


4. Gráficos de linha por dia

4.1. NFS-e emitidas por dia (últimos 30 dias)

Tipo: Time series

Query:

SELECT
  dia::timestamp AS time,
  SUM(total_notas) AS value
FROM public.vw_nfse_dashboard_30d
WHERE (${tenant:raw} IS NULL OR tenant_id IN (${tenant:raw}))
GROUP BY dia
ORDER BY dia;

4.2. Taxa de erro por dia

Tipo: Time series

SELECT
  dia::timestamp AS time,
  ROUND(
    (SUM(qtd_rejeitadas + qtd_erro_interno)::numeric
     / NULLIFnumeric * 100
  , 2) AS value
FROM public.vw_nfse_dashboard_30d
WHERE (${tenant:raw} IS NULL OR tenant_id IN (${tenant:raw}))
GROUP BY dia
ORDER BY dia;

5. Erros por substatus_tecnico

5.1. Gráfico de barras: erros internos por substatus

Tipo: Bar chart

Query:

SELECT
  substatus_tecnico AS label,
  SUM(qtd_erro_interno) AS value
FROM public.vw_nfse_dashboard_30d
WHERE situacao = 'erro_interno'
  AND (${tenant:raw} IS NULL OR tenant_id IN (${tenant:raw}))
GROUP BY substatus_tecnico
ORDER BY value DESC;

Isso te mostra rapidamente se está explodindo timeout_ws, ssl_error, config_invalida, etc.


5.2. Tabela de detalhe por substatus técnico

Tipo: Table

SELECT
  dia,
  tenant_id,
  situacao,
  substatus_tecnico,
  total_notas,
  qtd_erro_interno
FROM public.vw_nfse_dashboard_30d
WHERE situacao = 'erro_interno'
  AND (${tenant:raw} IS NULL OR tenant_id IN (${tenant:raw}))
ORDER BY dia DESC, qtd_erro_interno DESC;

Você pode:


6. Painéis operacionais (pegando direto da nfse)

6.1. Tabela: NFS-e pendentes de transmissão

Tipo: Table

Query:

SELECT
  id,
  tenant_id,
  numero_nfse,
  serie,
  data_emissao,
  situacao,
  substatus_tecnico,
  detalhe_tecnico,
  valor_servicos
FROM public.nfse
WHERE situacao = 'pendente_transmissao'
  AND (${tenant:raw} IS NULL OR tenant_id IN (${tenant:raw}))
ORDER BY data_emissao ASC
LIMIT 200;

6.2. Tabela: últimas rejeitadas / erro_interno

Tipo: Table

SELECT
  id,
  tenant_id,
  numero_nfse,
  serie,
  data_emissao,
  situacao,
  substatus_tecnico,
  codigo_mensagem_retorno,
  mensagem_retorno,
  detalhe_tecnico,
  valor_servicos
FROM public.nfse
WHERE situacao IN ('rejeitada', 'erro_interno')
  AND (${tenant:raw} IS NULL OR tenant_id IN (${tenant:raw}))
ORDER BY data_emissao DESC
LIMIT 200;

Essa tabela é ouro para suporte: você enxerga o que falhou, como o provedor descreveu (mensagem/código) e ainda vê o substatus_tecnico da integração.


7. Extra: separando por multi-schema (se quiser no futuro)

Se lá na frente você tiver NFS-e em vários schemas (ex: erp_nfse, saas_nfse, etc.), você pode:

Algo dessa linha:

CREATE OR REPLACE VIEW public.vw_nfse_all AS
SELECT 'erp' AS origem, * FROM erp_nfse.nfse
UNION ALL
SELECT 'saas' AS origem, * FROM saas_nfse.nfse;

E aí todas as views de dashboard passam a apontar para vw_nfse_all em vez de nfse direto.


Segue um modelo de dashboard JSON do Grafana já com:

⚠️ IMPORTANTE:
Antes de importar no Grafana, você precisa:

  1. Criar/identificar o data source PostgreSQL que aponta para seu banco.

  2. Trocar em cada painel o campo
    "uid": "YOUR_POSTGRES_UID"
    pelo uid real do seu data source (pega em Configuration → Data sources).


{
  "id": null,
  "uid": "nfse-dashboard-001",
  "title": "NFSe - Visão Geral",
  "tags": ["nfse", "fiscal", "multi-tenant"],
  "timezone": "browser",
  "schemaVersion": 39,
  "version": 1,
  "refresh": "30s",
  "style": "dark",
  "editable": true,
  "panels": [
    {
      "type": "row",
      "title": "KPIs - Últimos 30 dias",
      "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 },
      "collapsed": false,
      "id": 1,
      "panels": []
    },
    {
      "id": 2,
      "type": "stat",
      "title": "Total de NFSe (30d)",
      "gridPos": { "h": 4, "w": 8, "x": 0, "y": 1 },
      "datasource": {
        "type": "postgres",
        "uid": "YOUR_POSTGRES_UID"
      },
      "targets": [
        {
          "refId": "A",
          "format": "table",
          "rawSql": "SELECT\n  SUM(total_notas) AS total_nfse\nFROM public.vw_nfse_dashboard_30d\nWHERE (${tenant:raw} IS NULL OR tenant_id IN (${tenant:raw}));",
          "hide": false
        }
      ],
      "options": {
        "reduceOptions": {
          "calcs": ["lastNotNull"],
          "fields": "",
          "values": false
        },
        "orientation": "auto",
        "colorMode": "value",
        "graphMode": "none",
        "justifyMode": "auto",
        "textMode": "auto"
      },
      "fieldConfig": {
        "defaults": {
          "unit": "none",
          "decimals": 0
        },
        "overrides": []
      }
    },
    {
      "id": 3,
      "type": "stat",
      "title": "NFSe autorizadas (30d)",
      "gridPos": { "h": 4, "w": 8, "x": 8, "y": 1 },
      "datasource": {
        "type": "postgres",
        "uid": "YOUR_POSTGRES_UID"
      },
      "targets": [
        {
          "refId": "A",
          "format": "table",
          "rawSql": "SELECT\n  SUM(qtd_autorizadas) AS total_autorizadas\nFROM public.vw_nfse_dashboard_30d\nWHERE (${tenant:raw} IS NULL OR tenant_id IN (${tenant:raw}));",
          "hide": false
        }
      ],
      "options": {
        "reduceOptions": {
          "calcs": ["lastNotNull"],
          "fields": "",
          "values": false
        },
        "orientation": "auto",
        "colorMode": "value",
        "graphMode": "none",
        "justifyMode": "auto",
        "textMode": "auto"
      },
      "fieldConfig": {
        "defaults": {
          "unit": "none",
          "decimals": 0
        },
        "overrides": []
      }
    },
    {
      "id": 4,
      "type": "stat",
      "title": "Taxa de erro (30d)",
      "gridPos": { "h": 4, "w": 8, "x": 16, "y": 1 },
      "datasource": {
        "type": "postgres",
        "uid": "YOUR_POSTGRES_UID"
      },
      "targets": [
        {
          "refId": "A",
          "format": "table",
          "rawSql": "SELECT\n  ROUND(\n    (SUM(qtd_rejeitadas + qtd_erro_interno)::numeric / NULLIFnumeric) * 100\n  , 2) AS taxa_erro_percent\nFROM public.vw_nfse_dashboard_30d\nWHERE (${tenant:raw} IS NULL OR tenant_id IN (${tenant:raw});",
          "hide": false
        }
      ],
      "options": {
        "reduceOptions": {
          "calcs": ["lastNotNull"],
          "fields": "",
          "values": false
        },
        "orientation": "auto",
        "colorMode": "value",
        "graphMode": "none",
        "justifyMode": "auto",
        "textMode": "auto"
      },
      "fieldConfig": {
        "defaults": {
          "unit": "percent",
          "decimals": 2
        },
        "overrides": []
      }
    },
    {
      "type": "row",
      "title": "Séries temporais",
      "gridPos": { "h": 1, "w": 24, "x": 0, "y": 5 },
      "collapsed": false,
      "id": 5,
      "panels": []
    },
    {
      "id": 6,
      "type": "timeseries",
      "title": "NFSe emitidas por dia (30d)",
      "gridPos": { "h": 8, "w": 12, "x": 0, "y": 6 },
      "datasource": {
        "type": "postgres",
        "uid": "YOUR_POSTGRES_UID"
      },
      "fieldConfig": {
        "defaults": {
          "unit": "none",
          "decimals": 0
        },
        "overrides": []
      },
      "options": {
        "legend": {
          "displayMode": "list",
          "placement": "bottom"
        },
        "tooltip": {
          "mode": "single"
        }
      },
      "targets": [
        {
          "refId": "A",
          "format": "time_series",
          "rawSql": "SELECT\n  dia::timestamp AS \"time\",\n  SUM(total_notas) AS value\nFROM public.vw_nfse_dashboard_30d\nWHERE (${tenant:raw} IS NULL OR tenant_id IN (${tenant:raw}))\nGROUP BY dia\nORDER BY dia;",
          "hide": false
        }
      ]
    },
    {
      "id": 7,
      "type": "timeseries",
      "title": "Taxa de erro por dia (30d)",
      "gridPos": { "h": 8, "w": 12, "x": 12, "y": 6 },
      "datasource": {
        "type": "postgres",
        "uid": "YOUR_POSTGRES_UID"
      },
      "fieldConfig": {
        "defaults": {
          "unit": "percent",
          "decimals": 2
        },
        "overrides": []
      },
      "options": {
        "legend": {
          "displayMode": "list",
          "placement": "bottom"
        },
        "tooltip": {
          "mode": "single"
        }
      },
      "targets": [
        {
          "refId": "A",
          "format": "time_series",
          "rawSql": "SELECT\n  dia::timestamp AS \"time\",\n  ROUND(\n    (SUM(qtd_rejeitadas + qtd_erro_interno)::numeric / NULLIFnumeric) * 100\n  , 2) AS value\nFROM public.vw_nfse_dashboard_30d\nWHERE (${tenant:raw} IS NULL OR tenant_id IN (${tenant:raw})\nGROUP BY dia\nORDER BY dia;",
          "hide": false
        }
      ]
    },
    {
      "type": "row",
      "title": "Erros técnicos",
      "gridPos": { "h": 1, "w": 24, "x": 0, "y": 14 },
      "collapsed": false,
      "id": 8,
      "panels": []
    },
    {
      "id": 9,
      "type": "barchart",
      "title": "Erros internos por substatus (30d)",
      "gridPos": { "h": 8, "w": 12, "x": 0, "y": 15 },
      "datasource": {
        "type": "postgres",
        "uid": "YOUR_POSTGRES_UID"
      },
      "fieldConfig": {
        "defaults": {
          "unit": "none",
          "decimals": 0
        },
        "overrides": []
      },
      "options": {
        "orientation": "horizontal",
        "legend": {
          "displayMode": "list",
          "placement": "bottom"
        },
        "tooltip": {
          "mode": "single"
        }
      },
      "targets": [
        {
          "refId": "A",
          "format": "table",
          "rawSql": "SELECT\n  substatus_tecnico AS label,\n  SUM(qtd_erro_interno) AS value\nFROM public.vw_nfse_dashboard_30d\nWHERE situacao = 'erro_interno'\n  AND (${tenant:raw} IS NULL OR tenant_id IN (${tenant:raw}))\nGROUP BY substatus_tecnico\nORDER BY value DESC;",
          "hide": false
        }
      ]
    },
    {
      "id": 10,
      "type": "table",
      "title": "Detalhe de erros internos (30d)",
      "gridPos": { "h": 8, "w": 12, "x": 12, "y": 15 },
      "datasource": {
        "type": "postgres",
        "uid": "YOUR_POSTGRES_UID"
      },
      "fieldConfig": {
        "defaults": {
          "unit": "none",
          "decimals": 0
        },
        "overrides": []
      },
      "options": {
        "showHeader": true
      },
      "targets": [
        {
          "refId": "A",
          "format": "table",
          "rawSql": "SELECT\n  dia,\n  tenant_id,\n  situacao,\n  substatus_tecnico,\n  total_notas,\n  qtd_erro_interno\nFROM public.vw_nfse_dashboard_30d\nWHERE situacao = 'erro_interno'\n  AND (${tenant:raw} IS NULL OR tenant_id IN (${tenant:raw}))\nORDER BY dia DESC, qtd_erro_interno DESC;",
          "hide": false
        }
      ]
    },
    {
      "type": "row",
      "title": "Operacional",
      "gridPos": { "h": 1, "w": 24, "x": 0, "y": 23 },
      "collapsed": false,
      "id": 11,
      "panels": []
    },
    {
      "id": 12,
      "type": "table",
      "title": "NFSe pendentes de transmissão",
      "gridPos": { "h": 9, "w": 12, "x": 0, "y": 24 },
      "datasource": {
        "type": "postgres",
        "uid": "YOUR_POSTGRES_UID"
      },
      "fieldConfig": {
        "defaults": {
          "unit": "none",
          "decimals": 0
        },
        "overrides": []
      },
      "options": {
        "showHeader": true
      },
      "targets": [
        {
          "refId": "A",
          "format": "table",
          "rawSql": "SELECT\n  id,\n  tenant_id,\n  numero_nfse,\n  serie,\n  data_emissao,\n  situacao,\n  substatus_tecnico,\n  detalhe_tecnico,\n  valor_servicos\nFROM public.nfse\nWHERE situacao = 'pendente_transmissao'\n  AND (${tenant:raw} IS NULL OR tenant_id IN (${tenant:raw}))\nORDER BY data_emissao ASC\nLIMIT 200;",
          "hide": false
        }
      ]
    },
    {
      "id": 13,
      "type": "table",
      "title": "Últimas rejeitadas / erro interno",
      "gridPos": { "h": 9, "w": 12, "x": 12, "y": 24 },
      "datasource": {
        "type": "postgres",
        "uid": "YOUR_POSTGRES_UID"
      },
      "fieldConfig": {
        "defaults": {
          "unit": "none",
          "decimals": 0
        },
        "overrides": []
      },
      "options": {
        "showHeader": true
      },
      "targets": [
        {
          "refId": "A",
          "format": "table",
          "rawSql": "SELECT\n  id,\n  tenant_id,\n  numero_nfse,\n  serie,\n  data_emissao,\n  situacao,\n  substatus_tecnico,\n  codigo_mensagem_retorno,\n  mensagem_retorno,\n  detalhe_tecnico,\n  valor_servicos\nFROM public.nfse\nWHERE situacao IN ('rejeitada', 'erro_interno')\n  AND (${tenant:raw} IS NULL OR tenant_id IN (${tenant:raw}))\nORDER BY data_emissao DESC\nLIMIT 200;",
          "hide": false
        }
      ]
    }
  ],
  "templating": {
    "list": [
      {
        "name": "tenant",
        "label": "Tenant",
        "type": "query",
        "datasource": {
          "type": "postgres",
          "uid": "YOUR_POSTGRES_UID"
        },
        "query": "SELECT DISTINCT tenant_id AS \"__value\", tenant_id::text AS \"__text\" FROM public.nfse ORDER BY tenant_id;",
        "refresh": 1,
        "includeAll": true,
        "multi": true,
        "allValue": null
      }
    ]
  },
  "time": {
    "from": "now-30d",
    "to": "now"
  },
  "timepicker": {
    "refresh_intervals": ["10s", "30s", "1m", "5m", "15m", "30m", "1h"]
  },
  "annotations": {
    "list": []
  }
}