From 44f240a33e9b8ca138ac707a31c1753765a62e4b Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Mon, 12 Oct 2026 13:32:50 +0000
Subject: [PATCH] fix(danfse): render NT008 municipality header from authorized
XML (#117)
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
docs/danfse-v2-rendering.md | 1 +
src/Danfse/DanfseTemplate.php | 19 ++++++++-
src/Danfse/HTMLTemplate.php | 12 ++++--
tests/Unit/Danfse/DanfseTemplateTest.php | 52 ++++++++++++++++++++++++
4 files changed, 79 insertions(+), 5 deletions(-)
diff --git a/docs/danfse-v2-rendering.md b/docs/danfse-v2-rendering.md
index bc2fbd1..f163d91 100644
--- a/docs/danfse-v2-rendering.md
+++ b/docs/danfse-v2-rendering.md
@@ -12,6 +12,7 @@ uses synthetic/public-schema data rather than copyrighted screenshots.
The stable contract covered by tests is:
- authorized NFS-e identity, access key and QR-code presence;
+- header municipality and UF only from `infNFSe/xLocEmi` and `emit/enderNac/UF`, suppressed for national taxation item 99; environment identification remains visible;
- provider, taker and intermediary identity fields;
- retained ISS labels from the DPS and municipal base/rate/assessed tax from the authorized `infNFSe/valores` (not the DPS `tribMun`);
- foreign taker NIF without inventing a Brazilian CPF/CNPJ;
diff --git a/src/Danfse/DanfseTemplate.php b/src/Danfse/DanfseTemplate.php
index d3f49bd..01edd7c 100644
--- a/src/Danfse/DanfseTemplate.php
+++ b/src/Danfse/DanfseTemplate.php
@@ -106,7 +106,11 @@ public function buildData(array $nfse): array
'emissao_dps' => $this->formatter->dateTime($this->val($infDps, 'dhEmi')),
'ambiente' => Ambiente::fromValue($this->val($infDps, 'tpAmb'))->value,
'ambiente_gerador' => $this->val($infNfse, 'ambGer') ?: '-',
- 'municipio_emissao' => $this->val($infNfse, 'xLocEmi') ?: '-',
+ 'municipio_emissao' => $this->headerMunicipality(
+ $this->val($infNfse, 'xLocEmi'),
+ $this->val($enderEmit, 'UF'),
+ $this->val($cServ, 'cTribNac'),
+ ),
'emitente_nfse' => $this->emitterLabel($this->val($infDps, 'tpEmit')),
'situacao_nfse' => $this->statusLabel($this->val($infNfse, 'cStat')),
'finalidade_nfse' => $this->purposeLabel($this->val($infDps, 'IBSCBS', 'finNFSe')),
@@ -353,6 +357,19 @@ private function formattedNbs(string $code): string
. substr($code, 5, 2) . '.' . substr($code, 7, 2);
}
+ /**
+ * The NT 008 municipal heading is not the service location. Its city and UF
+ * come only from the authorized NFS-e, and item 99 suppresses the line.
+ */
+ private function headerMunicipality(string $city, string $uf, string $nationalTaxCode): string
+ {
+ if (str_starts_with($nationalTaxCode, '99') || ($city === '' && $uf === '')) {
+ return '';
+ }
+
+ return ($city !== '' ? $city : '-') . ' / ' . ($uf !== '' ? $uf : '-');
+ }
+
private function cityWithUf(string $city, string $uf): string
{
return ($city !== '' && $uf !== '') ? $city . ' - ' . $uf : '-';
diff --git a/src/Danfse/HTMLTemplate.php b/src/Danfse/HTMLTemplate.php
index 266ef91..f9ee91c 100644
--- a/src/Danfse/HTMLTemplate.php
+++ b/src/Danfse/HTMLTemplate.php
@@ -46,7 +46,9 @@
.header-table td { padding: 2pt 3pt 3pt; vertical-align: middle; }
.logo-cell { width: 32%; text-align: left; }
.title-cell { width: 42%; text-align: center; }
- .municipality-cell { width: 26%; font-size: 5.7pt; text-align: left; }
+ .municipality-cell { width: 26%; font-family: "Microsoft Sans Serif", Arial, sans-serif; text-align: left; }
+ .municipality-name { font-size: 8pt; font-weight: normal; }
+ .municipality-environment { font-size: 6pt; font-weight: normal; }
.qr-container { text-align: center; }
.qr-notice { font-size: 5.6pt; line-height: 1.15; margin-top: 2pt; text-align: left; }
.access-key { white-space: nowrap; font-size: 6.6pt; }
@@ -97,9 +99,11 @@
logoDataUri): ?>
- Município: = $data['municipio_emissao'] ?>
- Ambiente Gerador: = $data['ambiente_gerador'] ?>
- Tipo de Ambiente: = $data['ambiente'] ?>
+
+