Facturx.CII (Facturx v0.7.0)

Copy Markdown View Source

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

Build the CII XML for invoice.

Parse a CII XML binary into an Facturx.Invoice.

Functions

build(invoice, opts \\ [])

@spec build(
  Facturx.Invoice.t(),
  keyword()
) :: {:ok, binary()} | {:error, term()}

Build the CII XML for invoice.

Options

  • :profile — overrides invoice.profile.

  • :validate_business_process — check BT-23 (:business_process) against Facturx.business_processes/0, returning {:error, {:invalid_business_process, code}}. Defaults to false, because that list is the French one whereas BT-23 is an EN 16931 term whose values are not restricted (Peppol uses urn:fdc:peppol.eu:…, Chorus Pro used A1/A2).

    Enabling it also enforces rule G1.60: a B4/S4/M4 framework means "final invoice after a down payment", so it cannot be paired with a down-payment :type_code386, 500 or 503. 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) against Facturx.vat_point_date_codes/0, returning {:error, {:invalid_vat_point_date_code, code}}. Defaults to true, 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: true

Rebuilding 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.

parse(xml)

@spec parse(binary()) :: {:ok, Facturx.Invoice.t()} | {:error, term()}

Parse a CII XML binary into an Facturx.Invoice.