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 3const xr = toUbl(invoice, { profile: 'xrechnung' }); // or 'peppol-bis3' | 'en16931': stamps BT-24 and BT-23const cii = toCii(invoice);einvoice write --to ubl invoice.xml # any invoice file: converted first, then writteneinvoice 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"stays100.0000); a value that is not anxsd:decimalthrowsWriterError. The only arithmetic is the exact BigInt sum fromvalues.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 intest/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
sourceand 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
profileoverrides 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 asBICID. - 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.