Improve Factur-X compliance for French PDP validation#12
Conversation
- Use EN16931 profile identifier compatible with French validators - Sanitize invoice references according to BR-FR-02 requirements - Add mandatory French legal notes (PMT, PMD, AAB) - Export seller SIREN from user_siren - Export buyer routing identifier from client_company - Add BT-34 and BT-49 electronic addresses using SIREN scheme (0002) - Generate valid BT-30 seller identification - Prevent invalid payment means generation when no IBAN/account identifier is available - Fix BR-50, BR-61 and BR-CO-27 validation issues - Validate generated invoices with SuperPDP and FactPulse
📝 WalkthroughWalkthroughThis PR updates Factur-X v1.0 invoice generation to enforce French PDP compliance. Configuration changes remap Factur-X extended URNs to base EN16931. New helper methods sanitize SIREN identifiers and normalize invoice references. XML generation routes seller parties through SIREN with schemeID 0002, injects mandatory French legal notes, and conditionalizes payment means output based on available account data. ChangesFrench PDP Compliance for Factur-X Invoicing
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@application/libraries/XMLtemplates/Facturxv10Xml.php`:
- Around line 209-224: Ensure we don't emit empty/invalid SIREN-derived
identifiers: before calling $this->cleanedSiren($this->invoice->user_siren ??
'') and appending ram:ID / ram:URIID nodes (the code that creates
$node->appendChild(... 'ram:ID'), $idNode = $this->doc->createElement('ram:ID',
...), and any ram:URIID creation), validate the result is a strict 9-digit SIREN
(digits only, length == 9); if validation fails, either throw an explicit
exception (fail fast) or use a clear fallback behavior (do not create the
ram:ID/ram:URIID node and/or log/error) so invalid empty nodes are never
emitted; apply this check wherever cleanedSiren() is used for seller SIREN
(including the SpecifiedLegalOrganization block that creates $sloNode and
$idNode and the code path using $this->options[$prop[9]]).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7c202fa2-88ab-4776-ae3e-cd812a61d5cb
📒 Files selected for processing (2)
application/helpers/XMLconfigs/Facturxv10extended.phpapplication/libraries/XMLtemplates/Facturxv10Xml.php
| if (empty($this->minimum) && $who == 'user') { | ||
| $node->appendChild($this->doc->createElement('ram:ID', $this->invoice->{$prop[0]})); // *_id zugferd 2 : SELLER123 | ||
| $sellerSiren = $this->cleanedSiren($this->invoice->user_siren ?? ''); | ||
| $node->appendChild($this->doc->createElement('ram:ID', $sellerSiren)); | ||
| } | ||
|
|
||
| $node->appendChild($this->doc->createElement('ram:Name', htmlsc($this->invoice->{$prop[1]}))); // *_name | ||
|
|
||
| // SpecifiedLegalOrganization XRechnung-CII-validation + (minimum profile : need for user if $this->notax) | ||
| if ( ! empty($this->options[$prop[9]])) { // *_eas_code | ||
| // Required when "No subject to VAT" for minimum profile (Factur-X/Zugferd2.3). | ||
| // Note: is valid with VAT but not required | ||
| // Only for MINIMUM profile : ram:SpecifiedLegalOrganization is expected for seller (user) only | ||
| // SpecifiedLegalOrganization | ||
| // For France, FactPulse / XP Z12-012 expects seller SIREN in BT-30: exactly 9 digits. | ||
| if ($who == 'user') { | ||
| $sloNode = $this->doc->createElement('ram:SpecifiedLegalOrganization'); | ||
| $idNode = $this->doc->createElement('ram:ID', $this->cleanedSiren($this->invoice->user_siren ?? '')); | ||
| $idNode->setAttribute('schemeID', '0002'); | ||
| $sloNode->appendChild($idNode); | ||
| $node->appendChild($sloNode); | ||
| } elseif ( ! empty($this->options[$prop[9]])) { // *_eas_code |
There was a problem hiding this comment.
Prevent empty SIREN-derived identifiers from being emitted.
Line 210, Line 220, and Line 282 can write empty values after cleanedSiren() (e.g., missing/non-numeric source fields), which generates invalid ram:ID / ram:URIID nodes and breaks PDP validation contracts (BT-30/BT-34/BT-49). Validate a strict 9-digit SIREN before emitting these nodes, and fail fast (or apply explicit fallback) when unavailable.
Suggested patch
+ $sellerSiren = $this->cleanedSiren($this->invoice->user_siren ?? '');
+ $hasSellerSiren = preg_match('/^\d{9}$/', $sellerSiren) === 1;
+
- if (empty($this->minimum) && $who == 'user') {
- $sellerSiren = $this->cleanedSiren($this->invoice->user_siren ?? '');
+ if (empty($this->minimum) && $who === 'user' && $hasSellerSiren) {
$node->appendChild($this->doc->createElement('ram:ID', $sellerSiren));
}
- if ($who == 'user') {
+ if ($who === 'user' && $hasSellerSiren) {
$sloNode = $this->doc->createElement('ram:SpecifiedLegalOrganization');
- $idNode = $this->doc->createElement('ram:ID', $this->cleanedSiren($this->invoice->user_siren ?? ''));
+ $idNode = $this->doc->createElement('ram:ID', $sellerSiren);
$idNode->setAttribute('schemeID', '0002');
$sloNode->appendChild($idNode);
$node->appendChild($sloNode);
} elseif ( ! empty($this->options[$prop[9]])) { // *_eas_code
@@
- $uriNode = $this->doc->createElement('ram:URIUniversalCommunication');
- $idNode = $this->doc->createElement('ram:URIID', $electronicAddress);
- $idNode->setAttribute('schemeID', '0002');
- $uriNode->appendChild($idNode);
- $node->appendChild($uriNode);
+ if ($electronicAddress !== '') {
+ $uriNode = $this->doc->createElement('ram:URIUniversalCommunication');
+ $idNode = $this->doc->createElement('ram:URIID', $electronicAddress);
+ $idNode->setAttribute('schemeID', '0002');
+ $uriNode->appendChild($idNode);
+ $node->appendChild($uriNode);
+ }Also applies to: 271-285
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@application/libraries/XMLtemplates/Facturxv10Xml.php` around lines 209 - 224,
Ensure we don't emit empty/invalid SIREN-derived identifiers: before calling
$this->cleanedSiren($this->invoice->user_siren ?? '') and appending ram:ID /
ram:URIID nodes (the code that creates $node->appendChild(... 'ram:ID'), $idNode
= $this->doc->createElement('ram:ID', ...), and any ram:URIID creation),
validate the result is a strict 9-digit SIREN (digits only, length == 9); if
validation fails, either throw an explicit exception (fail fast) or use a clear
fallback behavior (do not create the ram:ID/ram:URIID node and/or log/error) so
invalid empty nodes are never emitted; apply this check wherever cleanedSiren()
is used for seller SIREN (including the SpecifiedLegalOrganization block that
creates $sloNode and $idNode and the code path using $this->options[$prop[9]]).
Pull Request Checklist
Please check the following steps before submitting your PR. If any items are incomplete, consider marking it as
[WIP](Work in Progress).Checklist
Description
Provide a brief description of the changes made in this pull request.
Related Issue(s)
List any related issues or discussions. If applicable, link to an accompanying thread on the forums.
Fixes # (example)
Motivation and Context
See #11
Issue Type (Check one or more)
Screenshots (If Applicable)
Attach relevant screenshots that demonstrate your changes.
Thank you for your contribution to InvoicePlane! We appreciate your time and effort.