Cross Industry Invoice (CII) XML — build and parse (pure Elixir, via Saxy).
Maps between Facturx.Invoice and the EN 16931 CII vocabulary
(rsm:CrossIndustryInvoice, ram:*, udt:*). Element order follows the CII
schema so the output validates.
Summary
Functions
@spec build( Facturx.Invoice.t(), keyword() ) :: {:ok, binary()} | {:error, term()}
Build the CII XML for invoice.
Options
:profile— overridesinvoice.profile.:validate_business_process— check BT-23 (:business_process) againstFacturx.business_processes/0, returning{:error, {:invalid_business_process, code}}. Defaults tofalse, because that list is the French one whereas BT-23 is an EN 16931 term whose values are not restricted (Peppol usesurn:fdc:peppol.eu:…, Chorus Pro usedA1/A2).Enabling it also enforces rule G1.60: a
B4/S4/M4framework means "final invoice after a down payment", so it cannot be paired with a down-payment:type_code—386,500or503. That returns{:error, {:final_invoice_type_conflict, %{business_process: …, type_code: …}}}. Being a cross-field rule, neither the XSD nor the EN 16931 schematron sees it.:validate_vat_point_date— check BT-8 (:tax_due_date_type_code, or a per-entry:due_date_type_code) againstFacturx.vat_point_date_codes/0, returning{:error, {:invalid_vat_point_date_code, code}}. Defaults totrue, the other way round, because this list is imposed by EN 16931 itself (rule BR-CL-06): a value outside it is invalid in CII whatever the jurisdiction.
Both accept a per-call value or a default from the application environment, the option winning:
config :facturx, Facturx.CII, validate_business_process: trueRebuilding third-party documents
parse/1 validates nothing, so a document you received may carry codes these
checks reject — a BT-8 of 3/35/432 (the UBL values) is common in
UBL→CII conversions. build/2 on such a struct fails by design; pass
validate_vat_point_date: false to reproduce it as-is.
Notes
nil and "" are both treated as absent and emit no element. A
:tax_due_date_type_code with an empty :tax_breakdown has nowhere to go and
returns {:error, {:vat_point_date_unemittable, code}} rather than being
dropped silently.
What a leaner profile drops
That error is about a struct that contradicts itself — BT-8 with no breakdown
to attach it to. Asking for a leaner profile is a different thing: it is an
explicit instruction to emit less, and build/2 obeys it silently, because
dropping data is the whole point.
:minimum is the extreme case. Its schema has no ram:ApplicableTradeTax, so
the VAT breakdown goes, and BT-8 with it; the lines, the notes, the payment
means, the contacts and the buyer's address go too. What comes back from
parse/1 is therefore much less than what went in — see Facturx.profiles/0
and the profile table in the README for what each one carries.
@spec parse(binary()) :: {:ok, Facturx.Invoice.t()} | {:error, term()}
Parse a CII XML binary into an Facturx.Invoice.