Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/danfse-v2-rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
19 changes: 18 additions & 1 deletion src/Danfse/DanfseTemplate.php
Original file line number Diff line number Diff line change
Expand Up @@ -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')),
Expand Down Expand Up @@ -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 : '-';
Expand Down
12 changes: 8 additions & 4 deletions src/Danfse/HTMLTemplate.php
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down Expand Up @@ -97,9 +99,11 @@
<?php if ($municipality && $municipality->logoDataUri): ?>
<img style="height: 22pt; width: auto" src="<?= htmlspecialchars($municipality->logoDataUri) ?>" alt="Prefeitura" /><br>
<?php endif; ?>
Município: <?= $data['municipio_emissao'] ?><br>
Ambiente Gerador: <?= $data['ambiente_gerador'] ?><br>
Tipo de Ambiente: <?= $data['ambiente'] ?>
<?php if ($data['municipio_emissao'] !== ''): ?>
<div class="municipality-name">Município: <?= $data['municipio_emissao'] ?></div>
<?php endif; ?>
<div class="municipality-environment">Ambiente Gerador: <?= $data['ambiente_gerador'] ?></div>
<div class="municipality-environment">Tipo de Ambiente: <?= $data['ambiente'] ?></div>
</td>
</tr>
</table>
Expand Down
52 changes: 52 additions & 0 deletions tests/Unit/Danfse/DanfseTemplateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,58 @@ public function testTomadorNifIsPreserved(): void
self::assertSame('AB-123', $data['tomador']['cnpj_cpf']);
}


public function testHeaderMunicipalityFollowsAuthorizedXmlAndItem99Rule(): void
{
$nfse = $this->fixtureNfseData();
$nfse['infNFSe']['xLocEmi'] = 'Município de Teste';
$nfse['infNFSe']['emit']['enderNac']['UF'] = 'MG';

$template = new DanfseTemplate();
$data = $template->buildData($nfse);
self::assertSame('Município de Teste / MG', $data['municipio_emissao']);

$html = $template->render($nfse, new DanfseConfig());
self::assertStringContainsString('Município: Município de Teste / MG', $html);
self::assertStringContainsString('.municipality-name { font-size: 8pt;', $html);
self::assertStringContainsString('.municipality-environment { font-size: 6pt;', $html);

$nfse['infNFSe']['DPS']['infDPS']['serv']['cServ']['cTribNac'] = '990000';
self::assertSame('', $template->buildData($nfse)['municipio_emissao']);
$html = $template->render($nfse, new DanfseConfig());
self::assertStringNotContainsString('Município: Município de Teste', $html);
self::assertStringContainsString('Ambiente Gerador:', $html);
self::assertStringContainsString('Tipo de Ambiente:', $html);
}

public function testHeaderDoesNotInventAbsentMunicipalityOrUf(): void
{
$nfse = $this->fixtureNfseData();
unset($nfse['infNFSe']['xLocEmi'], $nfse['infNFSe']['emit']['enderNac']['UF']);
$template = new DanfseTemplate();
self::assertSame('', $template->buildData($nfse)['municipio_emissao']);
self::assertStringNotContainsString('Município:', $template->render($nfse, new DanfseConfig()));

$nfse['infNFSe']['xLocEmi'] = 'Cidade Sintética';
self::assertSame('Cidade Sintética / -', $template->buildData($nfse)['municipio_emissao']);

unset($nfse['infNFSe']['xLocEmi']);
$nfse['infNFSe']['emit']['enderNac']['UF'] = 'SP';
self::assertSame('- / SP', $template->buildData($nfse)['municipio_emissao']);
}

public function testHomologationHeaderRetainsWarningAndEnvironmentWhenMunicipalitySuppressed(): void
{
$nfse = $this->fixtureNfseData();
$nfse['infNFSe']['DPS']['infDPS']['tpAmb'] = '2';
$nfse['infNFSe']['DPS']['infDPS']['serv']['cServ']['cTribNac'] = '990000';

$html = (new DanfseTemplate())->render($nfse, new DanfseConfig());
self::assertStringContainsString('NFS-e SEM VALIDADE JURÍDICA', $html);
self::assertStringContainsString('Tipo de Ambiente:', $html);
self::assertStringNotContainsString('Município: Niterói / RJ', $html);
}

public function testRenderProducesHtmlWithQrCodeAndAccessKey(): void
{
$html = (new DanfseTemplate())->render($this->fixtureNfseData(), new DanfseConfig());
Expand Down
Loading