Skip to content
Closed
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
3 changes: 3 additions & 0 deletions docs/danfse-v2-rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ 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`);
- municipal special regime codes `0` (Nenhum), `9` (Outros) and missing values retain distinct presentation; the two optional municipal rows are omitted only if every field of that row is absent, and a present numeric zero is displayed;
- foreign taker NIF without inventing a Brazilian CPF/CNPJ;
- 60-character taxation-description content with an ellipsis suffix when truncated;
- IBS/CBS block rendered only when the authorized XML contains it;
- IBS/CBS values displayed from authorized totals without recalculation;
- legacy authorized XML without IBS/CBS remains renderable;
- homologation documents have a visible environment marker;
- cancelled and substituted documents may show the required 50pt diagonal status watermark when the consuming application supplies an **explicit, externally verified event state** through `DanfseConfig::fiscalState`; the original authorized XML alone does not establish that a subsequent cancel/substitution occurred and is not mutated;
- local generation returns a valid PDF artifact;
- line breaks in the authorized service description are preserved as escaped HTML line breaks;
- optional contact complement and IBGE/CEP follow the authorized XML without inventing missing data;
Expand Down
285 changes: 246 additions & 39 deletions src/Danfse/DanfseTemplate.php

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/Danfse/Enum/RegimeEspecialTributacao.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ enum RegimeEspecialTributacao: int
case NOTARIO_REGISTRADOR = 4;
case PROFISSIONAL_AUTONOMO = 5;
case SOCIEDADE_PROFISSIONAIS = 6;
case OUTROS = 9;

public function label(): string
{
Expand All @@ -29,6 +30,7 @@ public function label(): string
self::NOTARIO_REGISTRADOR => 'Notário ou Registrador',
self::PROFISSIONAL_AUTONOMO => 'Profissional Autônomo',
self::SOCIEDADE_PROFISSIONAIS => 'Sociedade de Profissionais',
self::OUTROS => 'Outros',
};
}
}
165 changes: 98 additions & 67 deletions src/Danfse/HTMLTemplate.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
/** @var ?string $logo */
/** @var string $qrCode */
/** @var bool $isHomologacao */
/** @var ?\LibreCodeCoop\NfsePHP\Danfse\Config\DanfseFiscalState $fiscalState */
/** @var ?\LibreCodeCoop\NfsePHP\Danfse\Config\MunicipalityBranding $municipality */
?>
<!DOCTYPE html>
Expand Down Expand Up @@ -46,7 +47,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 All @@ -60,6 +63,18 @@
.footer-table td { border: 0.8pt solid #000; padding: 2pt 3pt; }
.footer-table .label { font-size: 5.8pt; }
.footer-table .value { font-size: 6pt; }
.fiscal-state-watermark {
position: fixed;
top: 46%;
left: 50%;
transform: translate(-50%, -50%) rotate(-45deg);
font-family: Arial, Helvetica, sans-serif;
font-size: 50pt;
font-weight: normal;
color: #a6a6a6;
z-index: -1;
white-space: nowrap;
}
.watermark {
position: fixed;
top: 50%;
Expand All @@ -74,6 +89,9 @@
</style>
</head>
<body>
<?php if ($fiscalState !== null): ?>
<div class="fiscal-state-watermark"><?= htmlspecialchars($fiscalState->value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></div>
<?php endif; ?>
<?php if ($isHomologacao): ?>
<div class="watermark">HOMOLOGAÇÃO</div>
<?php endif; ?>
Expand All @@ -97,9 +115,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 Expand Up @@ -167,7 +187,7 @@
</div>

<!-- Emitente -->
<div class="bordered-section">
<div class="bordered-section party-prestador">
<table>
<tr>
<td class="section-header">
Expand All @@ -187,28 +207,13 @@
</td>
</tr>
<tr>
<td colspan="2">
<span class="label">Nome / Nome Empresarial</span>
<span class="value"><?= $data['emitente']['nome'] ?></span>
</td>
<td colspan="2">
<span class="label">E-mail</span>
<span class="value"><?= $data['emitente']['email'] ?></span>
</td>
<td colspan="2"><span class="label">Nome / Nome Empresarial</span><span class="value"><?= $data['emitente']['nome'] ?></span></td>
<td><span class="label">Município / Sigla UF</span><span class="value"><?= $data['emitente']['municipio'] ?></span></td>
<td><span class="label">Código IBGE / CEP</span><span class="value"><?= $data['emitente']['ibge_cep'] ?></span></td>
</tr>
<tr>
<td colspan="2">
<span class="label">Endereço</span>
<span class="value"><?= $data['emitente']['endereco'] ?></span>
</td>
<td>
<span class="label">Município</span>
<span class="value"><?= $data['emitente']['municipio'] ?></span>
</td>
<td>
<span class="label">Código IBGE / CEP</span>
<span class="value"><?= $data['emitente']['ibge_cep'] ?></span>
</td>
<td colspan="2"><span class="label">Endereço</span><span class="value"><?= $data['emitente']['endereco'] ?></span></td>
<td colspan="2"><span class="label">E-mail</span><span class="value"><?= $data['emitente']['email'] ?></span></td>
</tr>
<tr>
<td colspan="2">
Expand All @@ -224,7 +229,10 @@
</div>

<!-- Tomador -->
<div class="bordered-section">
<?php if ($data['tomador'] === null): ?>
<div class="no-party">TOMADOR/ADQUIRENTE DA OPERAÇÃO NÃO IDENTIFICADO NA NFS-e</div>
<?php else: ?>
<div class="bordered-section party-tomador">
<table>
<tr>
<td class="section-header">
Expand All @@ -248,38 +256,54 @@
<span class="label">Nome / Nome Empresarial</span>
<span class="value"><?= $data['tomador']['nome'] ?></span>
</td>
<td colspan="2" style="width: 50%;">
<span class="label">E-mail</span>
<span class="value"><?= $data['tomador']['email'] ?></span>
</td>
<td><span class="label">Município / Sigla UF</span><span class="value"><?= $data['tomador']['municipio'] ?></span></td>
<td><span class="label">Código IBGE / CEP</span><span class="value"><?= $data['tomador']['ibge_cep'] ?></span></td>
</tr>
<tr>
<td colspan="2" style="width: 50%;">
<span class="label">Endereço</span>
<span class="value"><?= $data['tomador']['endereco'] ?></span>
</td>
<td style="width: 25%;">
<span class="label">Município</span>
<span class="value"><?= $data['tomador']['municipio'] ?></span>
</td>
<td style="width: 25%;">
<span class="label">Código IBGE / CEP</span>
<span class="value"><?= $data['tomador']['ibge_cep'] ?></span>
</td>
<td colspan="2"><span class="label">Endereço</span><span class="value"><?= $data['tomador']['endereco'] ?></span></td>
<td colspan="2"><span class="label">E-mail</span><span class="value"><?= $data['tomador']['email'] ?></span></td>
</tr>
</table>
</div>
<?php endif; ?>

<!-- Destinatário: independent of the intermediary, as required by NT008 -->
<?php if ($data['destinatario_mesmo_tomador']): ?>
<div class="no-party">O DESTINATÁRIO É O PRÓPRIO TOMADOR/ADQUIRENTE DA OPERAÇÃO</div>
<?php elseif ($data['destinatario'] === null): ?>
<div class="no-party">DESTINATÁRIO DA OPERAÇÃO NÃO IDENTIFICADO NA NFS-e</div>
<?php else: ?>
<div class="bordered-section party-destinatario">
<table>
<tr>
<td class="section-header"><span class="section-title">DESTINATÁRIO DA OPERAÇÃO</span></td>
<td><span class="label">CNPJ / CPF / NIF</span><span class="value"><?= $data['destinatario']['cnpj_cpf'] ?></span></td>
<td></td>
<td><span class="label">Telefone</span><span class="value"><?= $data['destinatario']['telefone'] ?></span></td>
</tr>
<tr>
<td colspan="2"><span class="label">Nome / Nome Empresarial</span><span class="value"><?= $data['destinatario']['nome'] ?></span></td>
<td><span class="label">Município / Sigla UF</span><span class="value"><?= $data['destinatario']['municipio'] ?></span></td>
<td><span class="label">Código IBGE / CEP</span><span class="value"><?= $data['destinatario']['ibge_cep'] ?></span></td>
</tr>
<tr>
<td colspan="2"><span class="label">Endereço</span><span class="value"><?= $data['destinatario']['endereco'] ?></span></td>
<td colspan="2"><span class="label">E-mail</span><span class="value"><?= $data['destinatario']['email'] ?></span></td>
</tr>
</table>
</div>
<?php endif; ?>

<!-- Intermediário -->
<?php if ($data['intermediario'] !== null): ?>
<div class="bordered-section">
<div class="bordered-section party-intermediario">
<table>
<tr>
<td class="section-header">
<span class="section-title">INTERMEDIÁRIO DO SERVIÇO</span>
</td>
<td style="width: 25%;">
<span class="label">CNPJ / CPF</span>
<span class="label">CNPJ / CPF / NIF</span>
<span class="value"><?= $data['intermediario']['cnpj_cpf'] ?></span>
</td>
<td style="width: 25%;">
Expand All @@ -296,31 +320,17 @@
<span class="label">Nome / Nome Empresarial</span>
<span class="value"><?= $data['intermediario']['nome'] ?></span>
</td>
<td colspan="2" style="width: 50%;">
<span class="label">E-mail</span>
<span class="value"><?= $data['intermediario']['email'] ?></span>
</td>
<td><span class="label">Município / Sigla UF</span><span class="value"><?= $data['intermediario']['municipio'] ?></span></td>
<td><span class="label">Código IBGE / CEP</span><span class="value"><?= $data['intermediario']['ibge_cep'] ?></span></td>
</tr>
<tr>
<td colspan="2" style="width: 50%;">
<span class="label">Endereço</span>
<span class="value"><?= $data['intermediario']['endereco'] ?></span>
</td>
<td style="width: 25%;">
<span class="label">Município</span>
<span class="value"><?= $data['intermediario']['municipio'] ?></span>
</td>
<td style="width: 25%;">
<span class="label">CEP</span>
<span class="value"><?= $data['intermediario']['cep'] ?></span>
</td>
<td colspan="2"><span class="label">Endereço</span><span class="value"><?= $data['intermediario']['endereco'] ?></span></td>
<td colspan="2"><span class="label">E-mail</span><span class="value"><?= $data['intermediario']['email'] ?></span></td>
</tr>
</table>
</div>
<?php else: ?>
<div class="no-party">DESTINATÁRIO DA OPERAÇÃO NÃO IDENTIFICADO NA NFS-e
<div>INTERMEDIÁRIO DA OPERAÇÃO NÃO IDENTIFICADO NA NFS-e</div>
</div>
<div class="no-party">INTERMEDIÁRIO DA OPERAÇÃO NÃO IDENTIFICADO NA NFS-e</div>
<?php endif; ?>

<!-- Serviço Prestado -->
Expand All @@ -339,22 +349,41 @@
</table>
</div>

<!-- Tributação Municipal -->
<!-- Tributação Municipal: NT 008 note 4 and optional rows from note 5 -->
<div class="bordered-section">
<?php if ($data['tributacao_municipal']['nao_sujeito_issqn']): ?>
<div class="no-party">TRIBUTAÇÃO MUNICIPAL (ISSQN) - OPERAÇÃO NÃO SUJEITA AO ISSQN</div>
<?php else: ?>
<table>
<tr>
<td class="section-header"><span class="section-title">TRIBUTAÇÃO MUNICIPAL (ISSQN)</span></td>
<td><span class="label">Tipo de Tributação do ISSQN</span><span class="value"><?= $data['tributacao_municipal']['tributacao_issqn'] ?></span></td>
<td><span class="label">Município / Sigla UF / País de Incidência do ISSQN</span><span class="value"><?= $data['tributacao_municipal']['municipio_incidencia'] ?></span></td>
<td><span class="label">Regime Especial de Tributação</span><span class="value"><?= $data['tributacao_municipal']['regime_especial'] ?></span></td>
<td colspan="2"><span class="label">Município / Sigla UF / País de Incidência do ISSQN</span><span class="value"><?= $data['tributacao_municipal']['municipio_incidencia'] ?></span></td>
</tr>
<?php if ($data['tributacao_municipal']['mostrar_linha_regime']): ?>
<tr class="municipal-conditional-regime">
<td><span class="label">Regime Especial de Tributação do ISSQN</span><span class="value"><?= $data['tributacao_municipal']['regime_especial'] ?></span></td>
<td><span class="label">Tipo de Imunidade do ISSQN</span><span class="value"><?= $data['tributacao_municipal']['tipo_imunidade'] ?></span></td>
<td><span class="label">Suspensão da Exigibilidade do ISSQN</span><span class="value"><?= $data['tributacao_municipal']['suspensao_issqn'] ?></span></td>
<td><span class="label">Número Processo Suspensão</span><span class="value"><?= $data['tributacao_municipal']['numero_processo'] ?></span></td>
</tr>
<?php endif; ?>
<?php if ($data['tributacao_municipal']['mostrar_linha_beneficio']): ?>
<tr class="municipal-conditional-benefit">
<td><span class="label">Benefício Municipal</span><span class="value"><?= $data['tributacao_municipal']['beneficio_municipal'] ?></span></td>
<td><span class="label">Cálculo do BM</span><span class="value"><?= $data['tributacao_municipal']['calculo_bm'] ?></span></td>
<td><span class="label">Total Deduções/Reduções</span><span class="value"><?= $data['tributacao_municipal']['total_deducoes'] ?></span></td>
<td><span class="label">Desconto Incondicionado</span><span class="value"><?= $data['tributacao_municipal']['desconto_incondicionado'] ?></span></td>
</tr>
<?php endif; ?>
<tr>
<td><span class="label">BC ISSQN</span><span class="value"><?= $data['tributacao_municipal']['bc_issqn'] ?></span></td>
<td><span class="label">Alíquota Aplicada</span><span class="value"><?= $data['tributacao_municipal']['aliquota'] ?></span></td>
<td><span class="label">Retenção do ISSQN</span><span class="value"><?= $data['tributacao_municipal']['retencao_issqn'] ?></span></td>
<td><span class="label">ISSQN Apurado</span><span class="value"><?= $data['tributacao_municipal']['issqn_apurado'] ?></span></td>
</tr>
</table>
<?php endif; ?>
</div>

<!-- Tributação Federal -->
Expand All @@ -366,11 +395,13 @@
<td><span class="label">Contribuição Previdenciária - Retida</span><span class="value"><?= $data['tributacao_federal']['cp'] ?></span></td>
<td><span class="label">Contribuições Sociais - Retidas</span><span class="value"><?= $data['tributacao_federal']['csll'] ?></span></td>
</tr>
<tr>
<?php if ($data['tributacao_federal']['mostrar_linha_2026']): ?>
<tr class="federal-competence-2026">
<td><span class="label">PIS - Débito Apuração Própria</span><span class="value"><?= $data['tributacao_federal']['pis'] ?></span></td>
<td><span class="label">COFINS - Débito Apuração Própria</span><span class="value"><?= $data['tributacao_federal']['cofins'] ?></span></td>
<td colspan="2"><span class="label">Tipo de retenção PIS/COFINS/CSLL</span><span class="value"><?= $data['tributacao_federal']['descricao_retencao'] ?></span></td>
</tr>
<?php endif; ?>
</table>
</div>

Expand Down
Loading