Skip to content

[1.8] Validate required fields per e-invoice standard before XML generation #21

Description

@nielsdrost7

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

  • validate() implemented and called by every XML generator before output
  • Each missing required field returns a separate, human-readable error message in plain English — no raw field names in the error text
  • Missing recommended fields return warnings that are shown in the UI but do not block generation
  • Validation passes and returns [] when all required fields are present
  • UI shows the full list of errors/warnings on the invoice page
  • No XML file is written when validation returns required errors
  • $client_meta and $invoice_meta passed in so FatturaPA SdI/PEC fields can be validated

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions