Development target: ivpl-exprmt · branch v180
Context
Each e-invoice standard requires a different combination of client and invoice fields. Without upfront validation, a missing field currently causes XML generation to fail silently, produce malformed output, or throw a PHP error. The user has no way to know which field caused the problem.
A dedicated validation layer should check all required fields before generation begins and return a structured list of what is missing — one entry per missing field, in plain language.
Validation matrix
| Field |
Peppol |
XRechnung |
ISDOC |
FatturaPA |
Facturae |
client_name |
Required |
Required |
Required |
Required |
Required |
client_address_1 |
Required |
Required |
Required |
Required |
Required |
client_city |
Required |
Required |
Required |
Required |
Required |
client_zip |
Required |
Required |
Required |
Required |
Required |
client_country |
Required |
Required |
Required |
Required |
Required |
client_vat_id |
Required |
Required |
Required |
Required (B2B/B2G) |
Required |
client_company_number |
— |
— |
Recommended |
Required (B2C only, as fiscal code) |
— |
client_peppol_id |
Required |
— |
— |
— |
— |
client_einvoice_sdi_code or client_einvoice_pec_email |
— |
— |
— |
Required |
— |
invoice_routing_code_1 |
— |
Required (blocks generation) |
— |
Recommended (B2G) |
Recommended (B2G) |
invoice_routing_code_2 |
— |
— |
— |
Recommended (B2G) |
Recommended (B2G) |
invoice_routing_code_3 |
— |
— |
— |
— |
Recommended (B2G) |
Required = generation blocked, error returned.
Recommended = warning returned, generation still proceeds.
client_peppol_id is a column on ip_clients. The FatturaPA routing fields (client_einvoice_sdi_code, client_einvoice_pec_email) are stored in ip_client_meta — see #13 and #41.
Deliverable
A validation method — either in Mdl_einvoice or a dedicated EInvoice_validator class:
public function validate(string $standard, array $client, array $invoice, array $client_meta = [], array $invoice_meta = []): array
Returns [] on success. On failure, returns an associative array of field_key => human_readable_message:
[
'client_peppol_id' => 'Electronic invoice address is required for Peppol.',
'invoice_routing_code_1' => 'Routing code 1 (Leitweg-ID) is required for XRechnung.',
]
The XML generator calls this before producing any output. If the returned array contains any required failures, generation is aborted and the errors are displayed in the UI.
Acceptance criteria
AAA tests
Test 1 — missing Peppol endpoint blocks generation
Arrange: Peppol client with all required address fields set, client_vat_id = 'BE0123456789', but client_peppol_id = NULL
Act: Call validate('peppol', $client, $invoice)
Assert: Returns array containing entry for client_peppol_id; generation does not proceed
Test 2 — missing Leitweg-ID blocks XRechnung generation
Arrange: German government client; invoice with invoice_routing_code_1 = NULL
Act: Call validate('xrechnung', $client, $invoice)
Assert: Returns array containing entry for invoice_routing_code_1; generation does not proceed
Test 3 — all required fields present
Arrange: Peppol client with all required fields populated
Act: Call validate('peppol', $client, $invoice)
Assert: Returns []; XML generation proceeds normally
Test 4 — FatturaPA B2C: VAT not required for individuals
Arrange: Italian individual client with client_vat_id = NULL and client_company_number = 'RSSMRA80A01H501Z'
Act: Call validate('fatturaPA', $client, $invoice, $client_meta)
Assert: No error for missing VAT number; validation passes
Test 5 — FatturaPA B2G: missing routing codes return warnings, not errors
Arrange: Italian government client; invoice with all three routing codes NULL
Act: Call validate('fatturaPA', $client, $invoice, $client_meta, $invoice_meta)
Assert: Returns warnings for missing routing codes; generation is not blocked; warning shown in UI
Test 6 — multiple missing fields returns all errors at once
Arrange: Client with client_city = NULL, client_zip = NULL, client_peppol_id = NULL for Peppol
Act: Call validate('peppol', $client, $invoice)
Assert: Returns three separate entries — all three missing fields reported in a single call, not just the first one found
Context
Each e-invoice standard requires a different combination of client and invoice fields. Without upfront validation, a missing field currently causes XML generation to fail silently, produce malformed output, or throw a PHP error. The user has no way to know which field caused the problem.
A dedicated validation layer should check all required fields before generation begins and return a structured list of what is missing — one entry per missing field, in plain language.
Validation matrix
client_nameclient_address_1client_cityclient_zipclient_countryclient_vat_idclient_company_numberclient_peppol_idclient_einvoice_sdi_codeorclient_einvoice_pec_emailinvoice_routing_code_1invoice_routing_code_2invoice_routing_code_3Required = generation blocked, error returned.
Recommended = warning returned, generation still proceeds.
Deliverable
A validation method — either in
Mdl_einvoiceor a dedicatedEInvoice_validatorclass:Returns
[]on success. On failure, returns an associative array offield_key => human_readable_message:[ 'client_peppol_id' => 'Electronic invoice address is required for Peppol.', 'invoice_routing_code_1' => 'Routing code 1 (Leitweg-ID) is required for XRechnung.', ]The XML generator calls this before producing any output. If the returned array contains any required failures, generation is aborted and the errors are displayed in the UI.
Acceptance criteria
validate()implemented and called by every XML generator before output[]when all required fields are present$client_metaand$invoice_metapassed in so FatturaPA SdI/PEC fields can be validatedAAA tests
Test 1 — missing Peppol endpoint blocks generation
Arrange: Peppol client with all required address fields set,
client_vat_id = 'BE0123456789', butclient_peppol_id = NULLAct: Call
validate('peppol', $client, $invoice)Assert: Returns array containing entry for
client_peppol_id; generation does not proceedTest 2 — missing Leitweg-ID blocks XRechnung generation
Arrange: German government client; invoice with
invoice_routing_code_1 = NULLAct: Call
validate('xrechnung', $client, $invoice)Assert: Returns array containing entry for
invoice_routing_code_1; generation does not proceedTest 3 — all required fields present
Arrange: Peppol client with all required fields populated
Act: Call
validate('peppol', $client, $invoice)Assert: Returns
[]; XML generation proceeds normallyTest 4 — FatturaPA B2C: VAT not required for individuals
Arrange: Italian individual client with
client_vat_id = NULLandclient_company_number = 'RSSMRA80A01H501Z'Act: Call
validate('fatturaPA', $client, $invoice, $client_meta)Assert: No error for missing VAT number; validation passes
Test 5 — FatturaPA B2G: missing routing codes return warnings, not errors
Arrange: Italian government client; invoice with all three routing codes NULL
Act: Call
validate('fatturaPA', $client, $invoice, $client_meta, $invoice_meta)Assert: Returns warnings for missing routing codes; generation is not blocked; warning shown in UI
Test 6 — multiple missing fields returns all errors at once
Arrange: Client with
client_city = NULL,client_zip = NULL,client_peppol_id = NULLfor PeppolAct: Call
validate('peppol', $client, $invoice)Assert: Returns three separate entries — all three missing fields reported in a single call, not just the first one found