-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/danfse local generation #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
a648a7d
feat(danfse): generate DANFSe PDF locally instead of via ADN API
YvesCesar 3b54e52
fix: improve local DANFSe validation and sandbox output
YvesCesar 1c01be4
refactor(danfse): extract shared logo resolver and enum label trait
YvesCesar e87b54a
refactor(danfse): introduce Ambiente enum and percentOrDash helper
YvesCesar 87dfebb
test(danfse): use explicit property name for Formatter in test
YvesCesar 408f4cc
test(danfse): rename remaining Formatter test property usages
YvesCesar 6e1c94d
refactor(danfse): rename template.php to HTMLTemplate.php
YvesCesar adadb1a
refactor(danfse): expand enum names and split label tests per enum
YvesCesar 801d43a
test(danfse): drive Formatter and Municipios tests with data providers
YvesCesar 2c0eede
chore(reuse): inline SPDX in XML fixture and drop redundant .gitkeep
YvesCesar 5dec9c9
refactor(danfse): use explicit variable names
YvesCesar 998b31f
test(danfse): drive enum and generator tests with data providers
YvesCesar 2f80c5d
test(config): drive EnvironmentConfig URL test with a data provider
YvesCesar File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| <?php | ||
|
|
||
| // SPDX-FileCopyrightText: 2026 LibreCode coop and contributors | ||
| // SPDX-License-Identifier: AGPL-3.0-or-later | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace LibreCodeCoop\NfsePHP\Danfse\Config; | ||
|
|
||
| /** | ||
| * Immutable presentation options for the DANFSe. | ||
| * | ||
| * The provider logo is optional: pass a ready data URI via $logoDataUri, or a | ||
| * file path via $logoPath (a data URI takes precedence). When neither is given | ||
| * the header logo area stays empty. | ||
| */ | ||
| final readonly class DanfseConfig | ||
| { | ||
| public ?string $logoDataUri; | ||
|
|
||
| public function __construct( | ||
| ?string $logoDataUri = null, | ||
| ?string $logoPath = null, | ||
| public ?MunicipalityBranding $municipality = null, | ||
| ) { | ||
| $this->logoDataUri = LogoLoader::resolve($logoDataUri, $logoPath); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| <?php | ||
|
|
||
| // SPDX-FileCopyrightText: 2026 LibreCode coop and contributors | ||
| // SPDX-License-Identifier: AGPL-3.0-or-later | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace LibreCodeCoop\NfsePHP\Danfse\Config; | ||
|
|
||
| /** | ||
| * Reads an image file from disk and builds a base64 data URI for embedding in | ||
| * the DANFSe HTML (dompdf has remote loading disabled). | ||
| */ | ||
| final class LogoLoader | ||
| { | ||
| /** | ||
| * Resolve a logo to a data URI: a ready data URI takes precedence, otherwise | ||
| * a file path is loaded from disk, otherwise null (no logo). | ||
| */ | ||
| public static function resolve(?string $dataUri, ?string $path): ?string | ||
| { | ||
| return $dataUri ?? ($path !== null ? self::pathToDataUri($path) : null); | ||
| } | ||
|
|
||
| public static function pathToDataUri(string $path): string | ||
| { | ||
| if (!is_readable($path)) { | ||
| throw new \InvalidArgumentException("Logo file not found or unreadable: {$path}"); | ||
| } | ||
|
|
||
| $contents = file_get_contents($path); | ||
|
|
||
| if ($contents === false) { | ||
| throw new \RuntimeException("Could not read logo file: {$path}"); | ||
| } | ||
|
|
||
| $mime = mime_content_type($path) ?: 'image/png'; | ||
|
|
||
| return 'data:' . $mime . ';base64,' . base64_encode($contents); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| <?php | ||
|
|
||
| // SPDX-FileCopyrightText: 2026 LibreCode coop and contributors | ||
| // SPDX-License-Identifier: AGPL-3.0-or-later | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace LibreCodeCoop\NfsePHP\Danfse\Config; | ||
|
|
||
| /** | ||
| * Optional municipal issuer branding shown on the DANFSe header. | ||
| * | ||
| * The logo accepts either a file path or a ready data URI; a data URI takes | ||
| * precedence when both are supplied. | ||
| */ | ||
| final readonly class MunicipalityBranding | ||
| { | ||
| public ?string $logoDataUri; | ||
|
|
||
| public function __construct( | ||
| public string $name, | ||
| public string $department = '', | ||
| public string $email = '', | ||
| ?string $logoDataUri = null, | ||
| ?string $logoPath = null, | ||
| ) { | ||
| $this->logoDataUri = LogoLoader::resolve($logoDataUri, $logoPath); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| <?php | ||
|
|
||
| // SPDX-FileCopyrightText: 2026 LibreCode coop and contributors | ||
| // SPDX-License-Identifier: AGPL-3.0-or-later | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace LibreCodeCoop\NfsePHP\Danfse; | ||
|
|
||
| use Dompdf\Dompdf; | ||
| use Dompdf\Options; | ||
| use LibreCodeCoop\NfsePHP\Danfse\Config\DanfseConfig; | ||
| use LibreCodeCoop\NfsePHP\Exception\ArtifactException; | ||
| use LibreCodeCoop\NfsePHP\Exception\NfseErrorCode; | ||
|
|
||
| /** | ||
| * Generates the DANFSe (PDF auxiliary document) locally from an authorized | ||
| * NFS-e Nacional XML, replacing the (sunset) ADN generation API. | ||
| * | ||
| * Usage: | ||
| * $pdf = (new DanfseGenerator())->generateFromXml($nfseXml); | ||
| */ | ||
| final class DanfseGenerator | ||
| { | ||
| public function __construct( | ||
| private readonly DanfseConfig $config = new DanfseConfig(), | ||
| ) { | ||
| } | ||
|
|
||
| /** | ||
| * Render the DANFSe PDF from the NFS-e XML and return its raw bytes. | ||
| */ | ||
| public function generateFromXml(string $xml): string | ||
| { | ||
| $html = $this->generateHtml($xml); | ||
|
|
||
| try { | ||
| $options = new Options(); | ||
| $options->set('isHtml5ParserEnabled', true); | ||
| $options->set('isRemoteEnabled', false); | ||
| $options->set('defaultFont', 'Helvetica'); | ||
|
|
||
| $dompdf = new Dompdf($options); | ||
| $dompdf->loadHtml($html, 'UTF-8'); | ||
| $dompdf->setPaper('A4', 'portrait'); | ||
| $dompdf->render(); | ||
|
|
||
| return (string) $dompdf->output(); | ||
| } catch (\Throwable $e) { | ||
| throw new ArtifactException( | ||
| 'Failed to render DANFSe PDF: ' . $e->getMessage(), | ||
| NfseErrorCode::ArtifactRetrievalFailed, | ||
| previous: $e, | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Render the intermediate HTML (useful for inspection and testing). | ||
| */ | ||
| public function generateHtml(string $xml): string | ||
| { | ||
| try { | ||
| $data = (new XmlToArray())->convert($xml); | ||
| } catch (\Throwable $e) { | ||
| throw new ArtifactException( | ||
| 'Failed to parse NFS-e XML for DANFSe generation: ' . $e->getMessage(), | ||
| NfseErrorCode::ArtifactRetrievalFailed, | ||
| previous: $e, | ||
| ); | ||
| } | ||
|
|
||
| $this->assertAuthorizedNfse($data); | ||
|
|
||
| return (new DanfseTemplate())->render($data, $this->config); | ||
| } | ||
|
|
||
| /** | ||
| * @param array<string, mixed> $data | ||
| */ | ||
| private function assertAuthorizedNfse(array $data): void | ||
| { | ||
| $infNfse = $data['infNFSe'] ?? null; | ||
| if (!is_array($infNfse)) { | ||
| throw $this->invalidNfseXml(); | ||
| } | ||
|
|
||
| $id = $infNfse['Id'] ?? null; | ||
| if (!is_string($id) || trim($id) === '' || !str_starts_with(trim($id), 'NFS')) { | ||
| throw $this->invalidNfseXml(); | ||
| } | ||
|
|
||
| if (!is_array($infNfse['DPS']['infDPS'] ?? null)) { | ||
| throw $this->invalidNfseXml(); | ||
| } | ||
| } | ||
|
|
||
| private function invalidNfseXml(): ArtifactException | ||
| { | ||
| return new ArtifactException( | ||
| 'Failed to generate DANFSe: XML does not contain an authorized NFS-e.', | ||
| NfseErrorCode::ArtifactRetrievalFailed, | ||
| ); | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.