Skip to content
GitHub

Writing UBL and CII

The inverse of the parsers: a canonical invoice becomes a Peppol BIS Billing 3.0 UBL 2.1 document (Invoice, or CreditNote for kind: 'creditNote') or an EN 16931 CII D16B CrossIndustryInvoice.

import { toCii, toUbl } from 'eu-einvoice-canonical';
const ubl = toUbl(invoice); // string; keeps an EN 16931 based specification id, else Peppol BIS 3
const xr = toUbl(invoice, { profile: 'xrechnung' }); // or 'peppol-bis3' | 'en16931': stamps BT-24 and BT-23
const cii = toCii(invoice);
einvoice write --to ubl invoice.xml # any invoice file: converted first, then written
einvoice write --to cii canonical.json # the JSON `einvoice convert` printed (one invoice or an array)
einvoice write --to ubl --out out/ lot.xml # several invoices: one <invoice-id>.xml each
  • Deterministic and well-formed. The same invoice gives the same bytes: two-space indentation, elements in the order of the UBL 2.1 and CII D16B schemas (not the parser’s), a declaration and a trailing newline. Text and attributes are escaped, and characters XML 1.0 forbids (control characters, lone surrogates) are dropped.
  • Decimals stay strings. Amounts, quantities and rates are written exactly as the model holds them ("100.0000" stays 100.0000); a value that is not an xsd:decimal throws WriterError. The only arithmetic is the exact BigInt sum from values.ts, used once: when the model has a VAT breakdown but no VAT total (BT-110), the total is the sum of the breakdown, because both syntaxes require one.
  • Only EN 16931 is written. Country extensions, clearance data, withholdings, other taxes, payment schedules, extra parties and national type codes have no place in either binding and are left out. A document from a national format therefore comes back as an EN 16931 invoice, not as the original.
  • Where the syntax cannot hold a value it is dropped, never invented: UBL has no credit note due date outside PaymentMeans, no sales order reference without an order reference, and no gross price without a discount; CII has no issue time, card network or sub-lines, one preceding invoice, and keeps BT-7 and BT-8 on the VAT lines. A payment with several credit transfers becomes several payment means in both. The full list, with reasons, is the allow-list in test/writers.test.ts.
  • Round trip. The tests parse every fixture, write it as UBL and as CII and parse the result again. For EN 16931 based fixtures the canonical invoices must be equal apart from source and the allow-listed differences; for national formats the EN 16931 core (parties, totals, VAT breakdown, line ids and amounts) must be equal. Fixtures that are valid as received are also validated as written with the XSD and EN 16931 Schematron layers (and the Peppol / XRechnung layer when the syntax is unchanged).
  • Specification identifiers stay inside their syntax. An EN 16931 based identifier is kept when it exists in the target syntax (Peppol BIS for UBL, XRechnung and NLCIUS for both); a Peppol identifier on a CII document or a Factur-X one on a UBL document is replaced by the EN 16931 base (CII) or Peppol BIS 3 (UBL), and profile overrides it.
  • CII has one remittance reference, one direct debit mandate and one creditor identifier per document, so they apply to every payment means when the document is read back; a bank identifier that is not a BIC (SE:BANKGIRO) is not written as BICID.
  • The VAT point date code (BT-8) is UNTDID 2005 in UBL and UNTDID 2475 in CII, so the writers translate it (5, 29, 72 and 3, 35, 432).
  • A credit note written to CII whose type code is not a credit code is written as 381, since CII reads the direction from the type code.