This page documents important data transformations that occur during bidirectional conversion between GOBL and CII formats. Understanding these transformations is critical for maintaining data integrity and handling edge cases in invoice processing.
For information about the conversion functions themselves, see Convert Function and Parse Function. For context-specific behavior, see Context System.
The gobl.cii conversion process involves several automatic transformations to accommodate differences between GOBL's flexible data model and CII's structured XML requirements. These transformations fall into three categories:
| Transformation Type | Direction | Impact |
|---|---|---|
| Included Tax Removal | GOBL → CII | Required - CII does not support tax-inclusive pricing |
| Amount Precision Adjustments | Both | Automatic rounding to match currency precision |
| Field Mapping Limitations | CII → GOBL | Some CII fields are lost or converted to notes |
Sources: cii.go159-185 README.md149-156
The CII XML format does not support tax-inclusive pricing. All prices must be expressed as net amounts (tax-exclusive), with taxes calculated and added separately. This is a fundamental constraint of the EN 16931 semantic model.
When a GOBL invoice has tax.prices_include set to any tax category (typically "VAT"), the conversion process must remove the included taxes before generating CII XML.
The Convert function automatically calls RemoveIncludedTaxes() on the invoice before processing:
This transformation:
Consider an invoice with 19% VAT included in prices:
| Field | Before (Tax Included) | After (Tax Excluded) | Calculation |
|---|---|---|---|
| Line Item Price | 25.00 EUR | 21.0084 EUR | 25.00 ÷ 1.19 |
| Line Total (×1) | 25.00 EUR | 21.01 EUR | Rounded to 2 decimals |
| Sum of All Lines (×11) | 275.00 EUR | 231.11 EUR | Sum of rounded line totals |
| Tax Amount | 43.91 EUR | 43.91 EUR | 231.11 × 0.19 |
| Grand Total | 275.00 EUR | 275.02 EUR | Before rounding adjustment |
| Payable Amount | 275.00 EUR | 275.00 EUR | After -0.02 rounding adjustment |
Sources: test/data/convert/en16931/invoice-prices-include.json test/data/convert/en16931/out/invoice-prices-include.xml cii.go176-179
CII XML uses different precision levels for different amount types:
| Amount Type | Decimal Places | XML Element | Example |
|---|---|---|---|
| Unit Price | Up to 4 | ChargeAmount | 21.0084 |
| Line Total | 2 (currency) | LineTotalAmount | 21.01 |
| Tax Basis | 2 (currency) | TaxBasisTotalAmount | 231.11 |
| Tax Amount | 2 (currency) | CalculatedAmount | 43.91 |
| Grand Total | 2 (currency) | GrandTotalAmount | 275.02 |
Due to rounding at each calculation stage, the sum of rounded values may differ from the original total. CII provides a RoundingAmount field to capture this difference:
The formula is:
DuePayableAmount = GrandTotalAmount + RoundingAmount
275.00 = 275.02 + (-0.02)
Sources: test/data/convert/en16931/out/invoice-prices-include.xml470-477 test/data/convert/en16931/invoice-prices-include.json471-498
When parsing CII XML to GOBL, certain fields cannot be preserved due to model differences:
| CII Field | BT Code | GOBL Equivalent | Handling |
|---|---|---|---|
AdditionalReferencedDocument | BG-24 | Not supported | Lost - Additional document attachments are discarded |
ReceivableSpecifiedTradeAccountingAccount | BT-133 | No direct mapping | Converted to Note - Added as line note with key from type code |
DesignatedProductClassification | BT-158 | No direct mapping | Converted to Note - Added as line note with classification code as key |
SpecifiedTradeAllowanceCharge (prepaid) | BG-20 | Payment.Advances | Partial - Tax rate not preserved, uses global rate |
When converting GOBL to CII, certain GOBL features are not supported:
| GOBL Feature | CII Limitation | Impact |
|---|---|---|
| Line-level discounts with different tax rates | CII line charges lack tax info | Discounts may not calculate correctly if tax-variant |
Rich metadata in meta fields | Only extension keys map to CII | Custom metadata is lost unless using recognized extensions |
| Multiple delivery parties per line | CII has single ShipTo | Only document-level delivery preserved |
GOBL payment advances do not include their own tax rate. During conversion, they inherit the invoice's global tax configuration:
Sources: README.md149-156
Certain CII fields that lack direct GOBL equivalents are preserved as line-level notes during parsing:
The ReceivableSpecifiedTradeAccountingAccount field is added as a note with the type code as the key:
The DesignatedProductClassification field follows a similar pattern:
GOBL uses extension keys to preserve CII-specific codes. During conversion, these extensions are bidirectionally mapped:
| GOBL Extension Key | CII Field | Purpose |
|---|---|---|
untdid-document-type | TypeCode | Document type (380, 381, 384, 389) |
untdid-tax-category | CategoryCode | Tax category (S, Z, E, AE, etc.) |
untdid-payment-means | PaymentMeans/TypeCode | Payment method code |
cef-vatex | ExemptionReason | Tax exemption reason code |
iso-scheme-id | schemeID attribute | Party identifier scheme |
Sources: README.md152-156 cii.go1-233
The rescaleAmount function ensures amounts match expected decimal precision for CII output. This is critical for XML validation against CII schemas.
Different CII versions and contexts may have different precision requirements:
| Context | Version | Typical Precision | Notes |
|---|---|---|---|
| EN16931 | D16B | 2 decimals | Standard currency precision |
| XRechnung | D16B | 2-4 decimals | Higher precision for unit prices |
| Factur-X | D16B | 2 decimals | Strict French requirements |
| ZUGFeRD | D16B | 2-4 decimals | German extended format |
| Peppol | D16B | 2 decimals | Pan-European standard |
Sources: settlement.go (function referenced but implementation details in GOBL core)
The conversion process validates transformations to ensure data integrity:
| Validation Check | When Applied | Error Condition |
|---|---|---|
| Addon Requirements | Before conversion | Missing required addon (e.g., eu-en16931-v2017) |
| Included Tax Removal | Before conversion | Cannot remove included taxes (calculation error) |
| Amount Precision | During generation | Invalid decimal format |
| Required Fields | During generation | Missing mandatory CII fields |
Transformation errors are returned with context:
This allows calling code to:
Sources: cii.go159-185 examples_test.go40-122
Do:
tax.prices_include consistently across all linesDon't:
RemoveIncludedTaxes()Do:
Don't:
Do:
Don't:
Sources: README.md149-156 examples_test.go40-210
Refresh this wiki