@fin.cx/einvoice 9.0.0 → 10.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +2 -2
- package/dist_ts/einvoice.d.ts +12 -0
- package/dist_ts/einvoice.js +45 -3
- package/dist_ts/formats/base/base.decoder.d.ts +27 -2
- package/dist_ts/formats/base/base.decoder.js +52 -27
- package/dist_ts/formats/cii/cii.decoder.d.ts +89 -6
- package/dist_ts/formats/cii/cii.decoder.js +125 -30
- package/dist_ts/formats/cii/cii.encoder.d.ts +71 -1
- package/dist_ts/formats/cii/cii.encoder.js +191 -12
- package/dist_ts/formats/cii/cii.types.d.ts +2 -5
- package/dist_ts/formats/cii/cii.types.js +7 -9
- package/dist_ts/formats/cii/cii.validator.d.ts +13 -6
- package/dist_ts/formats/cii/cii.validator.js +29 -30
- package/dist_ts/formats/cii/facturx/facturx.decoder.js +15 -3
- package/dist_ts/formats/cii/facturx/facturx.encoder.d.ts +0 -6
- package/dist_ts/formats/cii/facturx/facturx.encoder.js +11 -25
- package/dist_ts/formats/cii/facturx/facturx.types.d.ts +0 -5
- package/dist_ts/formats/cii/facturx/facturx.types.js +1 -8
- package/dist_ts/formats/cii/facturx/facturx.validator.d.ts +0 -5
- package/dist_ts/formats/cii/facturx/facturx.validator.js +5 -29
- package/dist_ts/formats/cii/zugferd/zugferd.decoder.js +15 -2
- package/dist_ts/formats/cii/zugferd/zugferd.encoder.d.ts +0 -7
- package/dist_ts/formats/cii/zugferd/zugferd.encoder.js +9 -67
- package/dist_ts/formats/cii/zugferd/zugferd.types.d.ts +0 -5
- package/dist_ts/formats/cii/zugferd/zugferd.types.js +1 -8
- package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.d.ts +26 -0
- package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.js +43 -1
- package/dist_ts/formats/cii/zugferd/zugferd.validator.js +6 -2
- package/dist_ts/formats/semantic/semantic.adapter.js +20 -18
- package/dist_ts/formats/ubl/en16931.ubl.validator.d.ts +0 -4
- package/dist_ts/formats/ubl/en16931.ubl.validator.js +5 -42
- package/dist_ts/formats/ubl/generic/ubl.encoder.js +18 -24
- package/dist_ts/formats/ubl/ubl.decoder.d.ts +4 -0
- package/dist_ts/formats/ubl/ubl.decoder.js +9 -1
- package/dist_ts/formats/ubl/ubl.encoder.js +9 -5
- package/dist_ts/formats/ubl/ubl.validator.d.ts +13 -0
- package/dist_ts/formats/ubl/ubl.validator.js +28 -1
- package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.js +10 -1
- package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.d.ts +0 -7
- package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.js +30 -45
- package/dist_ts/formats/ubl/xrechnung.validator.d.ts +5 -4
- package/dist_ts/formats/ubl/xrechnung.validator.js +71 -68
- package/dist_ts/formats/utils/date.value.d.ts +35 -0
- package/dist_ts/formats/utils/date.value.js +98 -1
- package/dist_ts/formats/utils/format.detector.js +16 -10
- package/dist_ts/formats/utils/party.contact.d.ts +16 -0
- package/dist_ts/formats/utils/party.contact.js +16 -0
- package/dist_ts/formats/utils/party.identifier.d.ts +28 -0
- package/dist_ts/formats/utils/party.identifier.js +49 -0
- package/dist_ts/formats/utils/peppol.profile.d.ts +10 -0
- package/dist_ts/formats/utils/peppol.profile.js +12 -0
- package/dist_ts/formats/utils/seller.identifier.d.ts +46 -0
- package/dist_ts/formats/utils/seller.identifier.js +78 -0
- package/dist_ts/formats/utils/stated.values.d.ts +80 -0
- package/dist_ts/formats/utils/stated.values.js +418 -0
- package/dist_ts/formats/utils/vat.category.d.ts +30 -2
- package/dist_ts/formats/utils/vat.category.js +36 -6
- package/dist_ts/formats/utils/vat.id.d.ts +18 -0
- package/dist_ts/formats/utils/vat.id.js +22 -0
- package/dist_ts/formats/validation/conformance.harness.js +6 -6
- package/dist_ts/formats/validation/en16931.business-rules.validator.js +4 -21
- package/dist_ts/formats/validation/facturx.validator.js +6 -6
- package/dist_ts/formats/validation/integrated.validator.js +4 -13
- package/dist_ts/formats/validation/peppol.validator.js +6 -13
- package/dist_ts/formats/validation/validation.types.d.ts +5 -0
- package/dist_ts/formats/validation/validation.types.js +6 -1
- package/dist_ts/formats/validation/vat-categories.validator.d.ts +21 -42
- package/dist_ts/formats/validation/vat-categories.validator.js +137 -431
- package/dist_ts/formats/validation/xrechnung.validator.d.ts +11 -58
- package/dist_ts/formats/validation/xrechnung.validator.js +58 -324
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +1 -1
- package/dist_ts/interfaces/en16931-metadata.d.ts +0 -4
- package/dist_ts/interfaces/stated.values.d.ts +93 -0
- package/dist_ts/interfaces/stated.values.js +2 -0
- package/package.json +4 -4
- package/readme.md +152 -7
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/einvoice.ts +52 -2
- package/ts/formats/base/base.decoder.ts +55 -45
- package/ts/formats/cii/cii.decoder.ts +159 -32
- package/ts/formats/cii/cii.encoder.ts +206 -14
- package/ts/formats/cii/cii.types.ts +7 -9
- package/ts/formats/cii/cii.validator.ts +30 -32
- package/ts/formats/cii/facturx/facturx.decoder.ts +16 -2
- package/ts/formats/cii/facturx/facturx.encoder.ts +13 -25
- package/ts/formats/cii/facturx/facturx.types.ts +0 -9
- package/ts/formats/cii/facturx/facturx.validator.ts +5 -43
- package/ts/formats/cii/zugferd/zugferd.decoder.ts +16 -1
- package/ts/formats/cii/zugferd/zugferd.encoder.ts +10 -72
- package/ts/formats/cii/zugferd/zugferd.types.ts +0 -9
- package/ts/formats/cii/zugferd/zugferd.v1.decoder.ts +56 -0
- package/ts/formats/cii/zugferd/zugferd.validator.ts +5 -1
- package/ts/formats/semantic/semantic.adapter.ts +19 -17
- package/ts/formats/ubl/en16931.ubl.validator.ts +5 -64
- package/ts/formats/ubl/generic/ubl.encoder.ts +19 -23
- package/ts/formats/ubl/ubl.decoder.ts +12 -0
- package/ts/formats/ubl/ubl.encoder.ts +8 -4
- package/ts/formats/ubl/ubl.validator.ts +29 -0
- package/ts/formats/ubl/xrechnung/xrechnung.decoder.ts +8 -0
- package/ts/formats/ubl/xrechnung/xrechnung.encoder.ts +33 -45
- package/ts/formats/ubl/xrechnung.validator.ts +75 -127
- package/ts/formats/utils/date.value.ts +106 -0
- package/ts/formats/utils/format.detector.ts +15 -9
- package/ts/formats/utils/party.contact.ts +30 -0
- package/ts/formats/utils/party.identifier.ts +61 -0
- package/ts/formats/utils/peppol.profile.ts +13 -0
- package/ts/formats/utils/seller.identifier.ts +102 -0
- package/ts/formats/utils/stated.values.ts +499 -0
- package/ts/formats/utils/vat.category.ts +47 -5
- package/ts/formats/utils/vat.id.ts +24 -0
- package/ts/formats/validation/conformance.harness.ts +5 -5
- package/ts/formats/validation/en16931.business-rules.validator.ts +3 -28
- package/ts/formats/validation/facturx.validator.ts +5 -5
- package/ts/formats/validation/integrated.validator.ts +3 -16
- package/ts/formats/validation/peppol.validator.ts +5 -16
- package/ts/formats/validation/validation.types.ts +7 -1
- package/ts/formats/validation/vat-categories.validator.ts +179 -761
- package/ts/formats/validation/xrechnung.validator.ts +61 -382
- package/ts/index.ts +9 -0
- package/ts/interfaces/en16931-metadata.ts +3 -7
- package/ts/interfaces/stated.values.ts +94 -0
- package/ts/readme.md +1 -1
- package/dist_ts/formats/utils/eu.memberstates.d.ts +0 -11
- package/dist_ts/formats/utils/eu.memberstates.js +0 -16
- package/ts/formats/utils/eu.memberstates.ts +0 -16
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The amounts, VAT breakdown, line amounts and document level allowances and
|
|
3
|
+
* charges a received document states, read as they are written, with their
|
|
4
|
+
* EN 16931 business terms. They are not computed and not updated when the
|
|
5
|
+
* envelope changes, and no encoder writes them: the envelope computes its
|
|
6
|
+
* totals from quantity × net price (`computeDocumentTotals`), and cannot yet
|
|
7
|
+
* express document or line allowances and charges or a price base quantity.
|
|
8
|
+
* Every field is undefined when the document does not state it.
|
|
9
|
+
*/
|
|
10
|
+
export interface IEInvoiceStatedValues {
|
|
11
|
+
/** Document totals (BG-22) */
|
|
12
|
+
totals: IStatedDocumentTotals;
|
|
13
|
+
/** VAT breakdown (BG-23), in document order */
|
|
14
|
+
vatBreakdown: IStatedVatBreakdown[];
|
|
15
|
+
/** Invoice lines (BG-25), in document order */
|
|
16
|
+
lines: IStatedLine[];
|
|
17
|
+
/** Document level allowances (BG-20), in document order */
|
|
18
|
+
allowances: IStatedAllowanceCharge[];
|
|
19
|
+
/** Document level charges (BG-21), in document order */
|
|
20
|
+
charges: IStatedAllowanceCharge[];
|
|
21
|
+
}
|
|
22
|
+
/** Document totals (BG-22) as a document states them */
|
|
23
|
+
export interface IStatedDocumentTotals {
|
|
24
|
+
/** Sum of Invoice line net amount (BT-106) */
|
|
25
|
+
lineTotal?: number;
|
|
26
|
+
/** Sum of allowances on document level (BT-107) */
|
|
27
|
+
allowanceTotal?: number;
|
|
28
|
+
/** Sum of charges on document level (BT-108) */
|
|
29
|
+
chargeTotal?: number;
|
|
30
|
+
/** Invoice total amount without VAT (BT-109) */
|
|
31
|
+
taxExclusiveTotal?: number;
|
|
32
|
+
/** Invoice total VAT amount (BT-110), in the document currency */
|
|
33
|
+
taxTotal?: number;
|
|
34
|
+
/** Invoice total VAT amount in accounting currency (BT-111), with the VAT accounting currency code (BT-6) */
|
|
35
|
+
taxTotalInAccountingCurrency?: {
|
|
36
|
+
amount: number;
|
|
37
|
+
currency: string;
|
|
38
|
+
};
|
|
39
|
+
/** Invoice total amount with VAT (BT-112) */
|
|
40
|
+
taxInclusiveTotal?: number;
|
|
41
|
+
/** Paid amount (BT-113) */
|
|
42
|
+
paidAmount?: number;
|
|
43
|
+
/** Rounding amount (BT-114) */
|
|
44
|
+
roundingAmount?: number;
|
|
45
|
+
/** Amount due for payment (BT-115) */
|
|
46
|
+
amountDue?: number;
|
|
47
|
+
}
|
|
48
|
+
/** One VAT breakdown (BG-23) as a document states it */
|
|
49
|
+
export interface IStatedVatBreakdown {
|
|
50
|
+
/** VAT category taxable amount (BT-116) */
|
|
51
|
+
taxableAmount?: number;
|
|
52
|
+
/** VAT category tax amount (BT-117) */
|
|
53
|
+
taxAmount?: number;
|
|
54
|
+
/** VAT category code (BT-118), UNTDID 5305 */
|
|
55
|
+
categoryCode?: string;
|
|
56
|
+
/** VAT category rate (BT-119); a category not subject to VAT states none */
|
|
57
|
+
rate?: number;
|
|
58
|
+
/** VAT exemption reason text (BT-120) */
|
|
59
|
+
exemptionReason?: string;
|
|
60
|
+
/** VAT exemption reason code (BT-121) */
|
|
61
|
+
exemptionReasonCode?: string;
|
|
62
|
+
}
|
|
63
|
+
/** The amounts and the VAT category of one invoice line (BG-25) as a document states them */
|
|
64
|
+
export interface IStatedLine {
|
|
65
|
+
/** Invoice line identifier (BT-126) */
|
|
66
|
+
lineId?: string;
|
|
67
|
+
/** Invoice line net amount (BT-131) */
|
|
68
|
+
netAmount?: number;
|
|
69
|
+
/** Invoiced item VAT category code (BT-151) */
|
|
70
|
+
vatCategoryCode?: string;
|
|
71
|
+
/** Invoiced item VAT rate (BT-152) */
|
|
72
|
+
vatRate?: number;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* One document level allowance (BG-20: BT-92 to BT-98) or charge (BG-21:
|
|
76
|
+
* BT-99 to BT-105) as a document states it
|
|
77
|
+
*/
|
|
78
|
+
export interface IStatedAllowanceCharge {
|
|
79
|
+
/** Amount (BT-92, BT-99) */
|
|
80
|
+
amount?: number;
|
|
81
|
+
/** Base amount (BT-93, BT-100) */
|
|
82
|
+
baseAmount?: number;
|
|
83
|
+
/** Percentage (BT-94, BT-101) */
|
|
84
|
+
percentage?: number;
|
|
85
|
+
/** VAT category code (BT-95, BT-102) */
|
|
86
|
+
vatCategoryCode?: string;
|
|
87
|
+
/** VAT rate (BT-96, BT-103) */
|
|
88
|
+
vatRate?: number;
|
|
89
|
+
/** Reason (BT-97, BT-104) */
|
|
90
|
+
reason?: string;
|
|
91
|
+
/** Reason code (BT-98, BT-105) */
|
|
92
|
+
reasonCode?: string;
|
|
93
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fin.cx/einvoice",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.0.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A TypeScript module for creating, manipulating, and embedding XML data within PDF files specifically tailored for electronic invoice (einvoice) packages.",
|
|
6
6
|
"main": "dist_ts/index.js",
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
"@git.zone/tsbuild": "^5.0.0",
|
|
17
17
|
"@git.zone/tsbundle": "^2.15.0",
|
|
18
18
|
"@git.zone/tsrun": "^3.0.0",
|
|
19
|
-
"@git.zone/tstest": "^6.
|
|
19
|
+
"@git.zone/tstest": "^6.3.1",
|
|
20
20
|
"@push.rocks/smartfile": "^13.1.3",
|
|
21
|
-
"@types/node": "^26.6.
|
|
21
|
+
"@types/node": "^26.6.2"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@push.rocks/smartxml": "^2.1.0",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
],
|
|
68
68
|
"scripts": {
|
|
69
69
|
"test": "pnpm run test:unit",
|
|
70
|
-
"test:unit": "tstest test/test.cii-xrechnung.node.ts --verbose --logfile --timeout 60 && tstest test/test.einvoice.ts --verbose --logfile --timeout 60 && tstest test/test.decimal.ts --verbose --logfile --timeout 60 && tstest test/test.decimal-currency-calculator.ts --verbose --logfile --timeout 60 && tstest test/test.currency-utils.ts --verbose --logfile --timeout 60 && tstest test/test.en16931-validators.ts --verbose --logfile --timeout 60 && tstest test/test.schematron-validator.ts --verbose --logfile --timeout 60 && tstest test/test.xrechnung-creditnote.node.ts --verbose --logfile --timeout 60 && tstest test/test.accountingdoc-types.node.ts --verbose --logfile --timeout 60 && tstest test/test.corrected-invoice.node.ts --verbose --logfile --timeout 60 && tstest test/test.xrechnung-3-due-date.node.ts --verbose --logfile --timeout 60 && tstest test/test.calendar-dates.node.ts --verbose --logfile --timeout 60 && tstest test/test.document-totals.node.ts --verbose --logfile --timeout 60 && tstest test/test.reverse-charge.node.ts --verbose --logfile --timeout 60 && tstest test/test.cii-schema-order.node.ts --verbose --logfile --timeout 60 && tstest test/test.tax-number.node.ts --verbose --logfile --timeout 60 && tstest test/test.country-codes.node.ts --verbose --logfile --timeout 60 && tstest test/test.paid-amount.node.ts --verbose --logfile --timeout 60 && tstest test/test.en16931-unitcodes.node.ts --verbose --logfile --timeout 60 && tstest test/test.en16931-lineunit.node.ts --verbose --logfile --timeout 60",
|
|
70
|
+
"test:unit": "tstest test/test.cii-xrechnung.node.ts --verbose --logfile --timeout 60 && tstest test/test.einvoice.ts --verbose --logfile --timeout 60 && tstest test/test.decimal.ts --verbose --logfile --timeout 60 && tstest test/test.decimal-currency-calculator.ts --verbose --logfile --timeout 60 && tstest test/test.currency-utils.ts --verbose --logfile --timeout 60 && tstest test/test.en16931-validators.ts --verbose --logfile --timeout 60 && tstest test/test.schematron-validator.ts --verbose --logfile --timeout 60 && tstest test/test.xrechnung-creditnote.node.ts --verbose --logfile --timeout 60 && tstest test/test.accountingdoc-types.node.ts --verbose --logfile --timeout 60 && tstest test/test.corrected-invoice.node.ts --verbose --logfile --timeout 60 && tstest test/test.xrechnung-3-due-date.node.ts --verbose --logfile --timeout 60 && tstest test/test.calendar-dates.node.ts --verbose --logfile --timeout 60 && tstest test/test.document-totals.node.ts --verbose --logfile --timeout 60 && tstest test/test.reverse-charge.node.ts --verbose --logfile --timeout 60 && tstest test/test.cii-schema-order.node.ts --verbose --logfile --timeout 60 && tstest test/test.tax-number.node.ts --verbose --logfile --timeout 60 && tstest test/test.country-codes.node.ts --verbose --logfile --timeout 60 && tstest test/test.paid-amount.node.ts --verbose --logfile --timeout 60 && tstest test/test.en16931-unitcodes.node.ts --verbose --logfile --timeout 60 && tstest test/test.en16931-lineunit.node.ts --verbose --logfile --timeout 60 && tstest test/test.cii-profile.node.ts --verbose --logfile --timeout 60 && tstest test/test.validator-rule-ids.node.ts --verbose --logfile --timeout 60 && tstest test/test.cii-period-address.node.ts --verbose --logfile --timeout 60 && tstest test/test.amount-due.node.ts --verbose --logfile --timeout 60 && tstest test/test.ubl-zoned-dates.node.ts --verbose --logfile --timeout 60 && tstest test/test.vat-categories.node.ts --verbose --logfile --timeout 60 && tstest test/test.xrechnung-buyer-reference.node.ts --verbose --logfile --timeout 60 && tstest test/test.specification-identifier.node.ts --verbose --logfile --timeout 60 && tstest test/test.seller-identifier.node.ts --verbose --logfile --timeout 60 && tstest test/test.stated-values.node.ts --verbose --logfile --timeout 60 && tstest test/test.vat-category-limits.node.ts --verbose --logfile --timeout 60 && tstest test/test.received-conformance.node.ts --verbose --logfile --timeout 60 && tstest test/test.cii-stated-dates.node.ts --verbose --logfile --timeout 60",
|
|
71
71
|
"test:format": "tstest test/test.format-detection.ts --verbose --logfile --timeout 60",
|
|
72
72
|
"test:corpus": "tstest test/suite/einvoice_corpus-validation/ --verbose --logfile --timeout 60",
|
|
73
73
|
"test:performance": "NODE_OPTIONS=--expose-gc tstest test/suite/einvoice_performance/ --verbose --logfile --timeout 60",
|
package/readme.md
CHANGED
|
@@ -222,8 +222,8 @@ The encoders write only what the document states:
|
|
|
222
222
|
to enforce.
|
|
223
223
|
- `corrections` has no element in EN 16931 and is not written. State what is
|
|
224
224
|
corrected as a note (`notes`, BT-22); the encoders add no text of their own.
|
|
225
|
-
-
|
|
226
|
-
|
|
225
|
+
- Factur-X and ZUGFeRD write BG-3 in the EN 16931 profile. The MINIMUM profile,
|
|
226
|
+
which has no BG-3, is not written at all (see [CII profiles](#cii-profiles)).
|
|
227
227
|
|
|
228
228
|
With the reference stated, the XRechnung output of a corrected invoice passes
|
|
229
229
|
the official EN 16931 1.3.16 and KoSIT XRechnung Schematron 2.6.0 rules without
|
|
@@ -267,6 +267,35 @@ console.log(business.errors);
|
|
|
267
267
|
console.log(business.warnings ?? []);
|
|
268
268
|
```
|
|
269
269
|
|
|
270
|
+
At the business level, an XRechnung document in UBL syntax that was loaded from
|
|
271
|
+
XML is checked by the package's XRechnung validator. Its findings carry the IDs
|
|
272
|
+
of the official rule sets, the KoSIT XRechnung Schematron 2.6.0 and the CEN
|
|
273
|
+
EN 16931 validation artefacts 1.3.16, and check what those rules check, in their
|
|
274
|
+
context:
|
|
275
|
+
|
|
276
|
+
| Rule | What is missing |
|
|
277
|
+
| --- | --- |
|
|
278
|
+
| BR-DE-1 | Payment instructions (BG-16) |
|
|
279
|
+
| BR-49 | The payment means type code (BT-81) of a payment instruction |
|
|
280
|
+
| BR-DE-15 | The buyer reference (BT-10) |
|
|
281
|
+
| BR-DE-2 | The seller contact (BG-6) |
|
|
282
|
+
| BR-DE-6, BR-DE-7 | The telephone number (BT-42) or email address (BT-43) of the seller contact |
|
|
283
|
+
| BR-DE-3, BR-DE-4 | The seller city (BT-37) or post code (BT-38) |
|
|
284
|
+
| BR-DE-8, BR-DE-9 | The buyer city (BT-52) or post code (BT-53) |
|
|
285
|
+
| BR-DE-16 | The seller VAT identifier (BT-31), tax registration identifier (BT-32) or tax representative (BG-11), when a VAT category S, Z, E, AE, K, G, L or M is used |
|
|
286
|
+
| PEPPOL-EN16931-R010 | The buyer electronic address (BT-49), a rule the XRechnung Schematron includes |
|
|
287
|
+
| BR-21 | The identifier (BT-126) of an invoice line |
|
|
288
|
+
| BR-CO-25 | The payment due date (BT-9) or payment terms (BT-20), while an amount is due (BT-115). EN 16931 states this rule; the CEN artefacts for UBL and CII do not check it |
|
|
289
|
+
|
|
290
|
+
One check of its own has a code no official rule has: `EINVOICE-DE-VATID-FORMAT`,
|
|
291
|
+
a seller VAT identifier (BT-31) that begins with DE but is not DE followed by nine
|
|
292
|
+
digits, the format the Bundeszentralamt für Steuern issues. The official rules
|
|
293
|
+
do not check that format, and its finding has the source `EINVOICE`, not
|
|
294
|
+
`XRECHNUNG`.
|
|
295
|
+
|
|
296
|
+
These checks are a subset. The official Schematron remains the reference for
|
|
297
|
+
whether a document passes.
|
|
298
|
+
|
|
270
299
|
### Unit codes (BR-CL-23)
|
|
271
300
|
|
|
272
301
|
A line's `unitType` is written as the unit code of the invoiced quantity (BT-130), so it has to
|
|
@@ -375,6 +404,22 @@ The envelope states reverse charge for the whole document; an item has no VAT
|
|
|
375
404
|
category of its own. A document mixing reverse charge lines with standard rated
|
|
376
405
|
ones cannot be expressed until `@tsclass/tsclass` gives items a category.
|
|
377
406
|
|
|
407
|
+
Outside reverse charge every line is written as standard rated (`S`), whose
|
|
408
|
+
rate BR-S-05 asks to be greater than zero. Export refuses a document with a
|
|
409
|
+
line at the rate 0 outside reverse charge with an `EInvoiceFormatError` naming
|
|
410
|
+
BR-S-05 and the line (`unsupportedFeatures` `['BR-S-05']`), and `validate()`
|
|
411
|
+
reports it at the business level for a document built in code. The envelope cannot state
|
|
412
|
+
the other categories: zero rated (`Z`), exempt (`E`), intra-community supply
|
|
413
|
+
(`K`), export (`G`) or not subject to VAT (`O`), until `@tsclass/tsclass`
|
|
414
|
+
gives an item a VAT category, so a received document with such lines, which is
|
|
415
|
+
read as 0 % lines, cannot be written back or converted. The VAT category rules the
|
|
416
|
+
conformance harness checks on an envelope (BR-S-01, -05, -08 to -10, BR-AE-01,
|
|
417
|
+
-02, -05, -08 to -10 and BR-CO-17) take the categories and the VAT breakdown
|
|
418
|
+
from the same computation the encoders write, and report them under their
|
|
419
|
+
EN 16931 IDs and texts. A decoded line keeps the category its document states
|
|
420
|
+
(`statedValues.lines[].vatCategoryCode`); one that is not S is not reported as
|
|
421
|
+
BR-S-05, since the envelope reads it as S.
|
|
422
|
+
|
|
378
423
|
## Paid amount and advance payments
|
|
379
424
|
|
|
380
425
|
`paidAmount` is the gross sum received before the document was issued, the
|
|
@@ -383,7 +428,10 @@ paid amount (BT-113). Every format writes it (`cbc:PrepaidAmount`,
|
|
|
383
428
|
total less it (BR-CO-16), zero when paid in full and negative when more was
|
|
384
429
|
paid; a paid amount of zero is nothing paid and is not written. The decoders
|
|
385
430
|
read it back. A paid amount with more decimals than the currency has (two at
|
|
386
|
-
most, BR-DEC-16) is refused, not rounded.
|
|
431
|
+
most, BR-DEC-16) is refused, not rounded. The amount due is always derived, so
|
|
432
|
+
the envelope has no field for it and the business rule check of the `EInvoice`
|
|
433
|
+
has nothing to compare it with; the official rules check BR-CO-16 on the
|
|
434
|
+
written XML.
|
|
387
435
|
|
|
388
436
|
A final invoice (an invoice, a corrected invoice or a self-billed invoice) may
|
|
389
437
|
list the `advancePayments` it deducts (`@tsclass/tsclass` 9.9.0 on).
|
|
@@ -411,6 +459,45 @@ net and the tax of every payment. A corrected invoice's advance invoices read
|
|
|
411
459
|
back as related documents it corrects, since BG-3 does not tell them apart,
|
|
412
460
|
and an invoice's are not read.
|
|
413
461
|
|
|
462
|
+
## Received documents: what they state
|
|
463
|
+
|
|
464
|
+
A document read from XML carries what it states in `statedValues`, with the
|
|
465
|
+
EN 16931 business terms, as written: the document totals (BG-22: BT-106 to
|
|
466
|
+
BT-115, and BT-111 with its currency BT-6), the VAT breakdown (BG-23: BT-116
|
|
467
|
+
to BT-121), each line's net amount, VAT category and rate (BT-126, BT-131,
|
|
468
|
+
BT-151, BT-152) and the document level allowances and charges (BG-20, BG-21:
|
|
469
|
+
BT-92 to BT-105). A field is undefined when the document does not state it.
|
|
470
|
+
UBL, XRechnung, Factur-X and ZUGFeRD 2.x documents are read; ZUGFeRD 1.0
|
|
471
|
+
predates EN 16931 and has none, nor has a document built in code.
|
|
472
|
+
|
|
473
|
+
```ts
|
|
474
|
+
const invoice = await EInvoice.fromXml(receivedXml);
|
|
475
|
+
const { totals, vatBreakdown } = invoice.statedValues!;
|
|
476
|
+
console.log(totals.taxExclusiveTotal, totals.taxTotal, totals.amountDue);
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
Book what the document states. The envelope's totals (`totalNet`, `totalVat`,
|
|
480
|
+
`totalGross`, `taxBreakdown`) are computed from quantity × net price, and the
|
|
481
|
+
envelope cannot express document or line allowances and charges, a price base
|
|
482
|
+
quantity (BT-149) or a VAT category other than standard rated and reverse
|
|
483
|
+
charge, so for such a document they differ from what it states. Neither value
|
|
484
|
+
is changed: `validate()` reports each difference at the business level as a
|
|
485
|
+
warning `EINVOICE-STATED-DIFFERS`, the package's own code, naming the
|
|
486
|
+
business term and both values. `statedValues` is not updated when the envelope
|
|
487
|
+
changes, and no encoder writes it: an export writes the envelope's
|
|
488
|
+
computation, so a received document with allowances, charges or base
|
|
489
|
+
quantities is not written back with the amounts it states, and a document with
|
|
490
|
+
lines of a category other than S and AE is refused on export (BR-S-05). The
|
|
491
|
+
`EINVOICE-STATED-DIFFERS` warnings are the signal that an export would not
|
|
492
|
+
state what was received; a faithful round trip needs `@tsclass/tsclass` to
|
|
493
|
+
express allowances, charges, base quantities and line categories.
|
|
494
|
+
|
|
495
|
+
The stated values are checked against each other at the business level as the
|
|
496
|
+
CEN artefacts 1.3.16 check them, under the official rule IDs BR-CO-10 to
|
|
497
|
+
BR-CO-17, in UBL and CII; these checks replace the approximate ones of earlier
|
|
498
|
+
releases. A stated amount that is no decimal number is refused on read with an
|
|
499
|
+
`EInvoiceParsingError` that names the business term.
|
|
500
|
+
|
|
414
501
|
## Calendar dates
|
|
415
502
|
|
|
416
503
|
A calendar date of the invoice (issue date, due date, delivery date, invoicing
|
|
@@ -419,7 +506,15 @@ zone: UBL writes it as `xsd:date` (`2026-09-20`), CII in format 102
|
|
|
419
506
|
(`20260920`). In the accounting document envelope such a day is a timestamp:
|
|
420
507
|
the UTC midnight of that day, `Date.UTC(2026, 8, 20)`.
|
|
421
508
|
|
|
422
|
-
- The decoders return that timestamp.
|
|
509
|
+
- The decoders return that timestamp. A UBL date may carry a time zone
|
|
510
|
+
(`2026-09-20+02:00`, `2026-09-20Z`); the zone does not move the day, so the
|
|
511
|
+
decoders return the UTC midnight of the stated day, and every format writes
|
|
512
|
+
that day. A CII date stated with a time of day, in format 203
|
|
513
|
+
(`202609202330`) or 204 (`20260920233000`), or without a format code as an
|
|
514
|
+
ISO 8601 date or date and time (`2026-09-20T00:30:00+02:00`), is read the
|
|
515
|
+
same way: the day it states, whatever the time of day and the zone. A CII
|
|
516
|
+
date in another form, or in a format that names no day, is refused with an
|
|
517
|
+
`EInvoiceParsingError`; no day is guessed.
|
|
423
518
|
- The encoders write the day of that timestamp in UTC and count the payment
|
|
424
519
|
term in UTC days, so what they write does not depend on the time zone of the
|
|
425
520
|
machine that writes it.
|
|
@@ -436,7 +531,7 @@ the UTC midnight of that day, `Date.UTC(2026, 8, 20)`.
|
|
|
436
531
|
| `xrechnung` | Yes | Yes | Yes | Yes | UBL export; UBL and CII import |
|
|
437
532
|
| `cii` | Yes | Yes | Yes | Partial | Generic CII export currently uses the Factur-X encoder path |
|
|
438
533
|
| `facturx` | Yes | Yes | Yes | Yes | Main CII-based generation path |
|
|
439
|
-
| `zugferd` | Yes | Yes | Yes | Partial | Input supports v1 and v2
|
|
534
|
+
| `zugferd` | Yes | Yes | Yes | Partial | Input supports v1 and v2+; export writes the EN 16931 profile, and v2 reads back as `facturx`, as XML and from a PDF (see [CII profiles](#cii-profiles)) |
|
|
440
535
|
| `fatturapa` | Yes | No | No | Basic placeholder | Detection exists, decoder/encoder do not |
|
|
441
536
|
|
|
442
537
|
### Important format notes
|
|
@@ -444,13 +539,63 @@ the UTC midnight of that day, `Date.UTC(2026, 8, 20)`.
|
|
|
444
539
|
- There is no dedicated root-level `peppol` export format. PEPPOL-specific checks exist, but the XML export targets are still `ubl` / `xrechnung`.
|
|
445
540
|
- XRechnung import routes UBL and CII by the namespace-qualified XML root.
|
|
446
541
|
Missing or invalid document-level invoice IDs and issue dates are rejected.
|
|
542
|
+
- The buyer reference (BT-10): every format writes `buyerReference`
|
|
543
|
+
(`cbc:BuyerReference`, `ram:BuyerReference`) and reads it back, ZUGFeRD 1.0
|
|
544
|
+
input as well. XRechnung requires it (BR-DE-15), and it is the
|
|
545
|
+
buyer's: the identifier the buyer gives the seller for routing the invoice, a
|
|
546
|
+
Leitweg-ID for a public buyer, any identifier of the buyer's choice in B2B.
|
|
547
|
+
`exportXml('xrechnung')` writes `buyerReference` (or the reference a decoded
|
|
548
|
+
UBL document keeps) and refuses a document without one with an
|
|
549
|
+
`EInvoiceFormatError` whose `unsupportedFeatures` is `['BR-DE-15']`; nothing,
|
|
550
|
+
not the invoice number, is put in its place. A seller whose buyer gave no
|
|
551
|
+
reference exports `ubl` (EN 16931), where BT-10 is optional. The metadata has
|
|
552
|
+
no buyer reference of its own: `buyerReference` is the one field.
|
|
447
553
|
- `FatturaPA` should be documented as detection-only right now.
|
|
448
|
-
- The day of supply: UBL and XRechnung write `deliveryDate` as `cac:Delivery/cbc:ActualDeliveryDate` (BT-72) and `periodOfPerformance` as `cac:InvoicePeriod` (BG-14), and read both back. Factur-X and ZUGFeRD write `deliveryDate` as `ram:ActualDeliverySupplyChainEvent` and read
|
|
449
|
-
-
|
|
554
|
+
- The day of supply: UBL and XRechnung write `deliveryDate` as `cac:Delivery/cbc:ActualDeliveryDate` (BT-72) and `periodOfPerformance` as `cac:InvoicePeriod` (BG-14), and read both back. Factur-X and ZUGFeRD write `deliveryDate` as `ram:ActualDeliverySupplyChainEvent` and `periodOfPerformance` as `ram:BillingSpecifiedPeriod` of the header settlement, and read each back only when the document states it; ZUGFeRD 1.0 input has its period read as well. `periodOfPerformance` has a start and an end date, so a document that states only one of them (EN 16931 allows that, BR-CO-19) reads back without `periodOfPerformance`, in every format; the date it states is kept in `metadata.extensions.dateInformation.periodStart` or `.periodEnd` (YYYY-MM-DD), and every encoder writes it back as the one date of the period, so it survives a round trip and a conversion between UBL and CII.
|
|
555
|
+
- The seller electronic address (BT-34): Factur-X and ZUGFeRD write `electronicAddress` as `ram:URIUniversalCommunication/ram:URIID` of the seller, with its scheme (BR-62), for a company or a person, and read it back. A ZUGFeRD 1.0 trade party has no electronic address.
|
|
556
|
+
- Country codes: every format writes the seller and buyer country (BT-40, BT-55) as an ISO 3166-1 alpha-2 code EN 16931 accepts (BR-CL-14), taken from `address.countryCode`, or from `address.country` when that is already such a code (letter case does not matter). A code is never derived from a country name: a name without a `countryCode`, a code EN 16931 does not know, or a missing country is refused with an `EInvoiceFormatError` naming the field (BR-CL-14, BR-09, BR-11). The list of codes is the UBL one of the CEN EN 16931 validation artefacts 1.3.16, for every format. Their CII list differs in two codes: it has AN (Netherlands Antilles, withdrawn from ISO 3166-1) and lacks SS (South Sudan). So a Factur-X or ZUGFeRD document with a party in South Sudan is written, and the official CII rules report BR-CL-14 for it; AN is refused in every format.
|
|
450
557
|
- Party identifiers: `registrationDetails.vatId` is the VAT identifier (BT-31 seller, BT-48 buyer), `registrationId` the legal registration identifier such as the commercial register number (BT-30, BT-47), and `taxNumber` the seller's tax registration identifier, the Steuernummer (BT-32), which § 14 Abs. 4 Satz 1 Nr. 2 UStG allows in place of the VAT identifier. UBL and XRechnung write the tax number as a second `cac:PartyTaxScheme` with the tax scheme `FC`, after the VAT one; Factur-X and ZUGFeRD as `ram:SpecifiedTaxRegistration/ram:ID` with the scheme `FC`. The decoders read each by its scheme, whatever order the document lists them in. EN 16931 has no tax number for the buyer, so a buyer's `taxNumber` is not written.
|
|
558
|
+
- The seller's identification (BR-CO-26): EN 16931 asks the seller to state a seller identifier (BT-29), a legal registration identifier (BT-30, `registrationDetails.registrationId`) or a VAT identifier (BT-31, `registrationDetails.vatId`); a tax number (BT-32) alone does not identify it. Every export whose seller states none of them is refused with an `EInvoiceFormatError` whose `unsupportedFeatures` is `['BR-CO-26']`, and `validate()` reports BR-CO-26 at the business level, for a loaded document and for one built in code. The party identifiers, the seller identifier (BT-29) and the buyer identifier (BT-46), have no typed field in the envelope: the decoders keep them in the party's `additionalIdentifiers` (`{ value, scheme }`), and every format writes them from there, UBL as `cac:PartyIdentification/cbc:ID` with the scheme as `schemeID`, CII as `ram:GlobalID` with its scheme or `ram:ID` without one. A UBL seller identification with the scheme SEPA is the bank assigned creditor identifier (BT-90) and is not written to CII. A buyer identifier with the scheme SEPA is refused (BR-CL-10): UBL admits the scheme for the seller and the payee only, CII for no party.
|
|
559
|
+
- The specification identifier (BT-24): the decoders set `metadata.customizationId` to the identifier the document states (UBL `cbc:CustomizationID`, CII `ram:GuidelineSpecifiedDocumentContextParameter/ram:ID`), so `MainValidator` runs the XRechnung, PEPPOL or Factur-X checks on a decoded document of that kind. The PEPPOL checks run for the PEPPOL BIS Billing 3.0 identifier only, `urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0`, not for PINT, A-NZ or another derivative that names PEPPOL. `metadata.profileId` is the business process type (BT-23, UBL `cbc:ProfileID`). The encoders write the identifier of the format they write, not `customizationId`.
|
|
451
560
|
- Credit notes, debit notes, corrected invoices and self-billed invoices share the `EInvoice` class; see the type code table above. Credit and debit amounts are written as given, the encoders do not change signs.
|
|
452
561
|
- ZUGFeRD v1 input is detected and routed, but the v1 decoder does not read the v1 header, so such a document always arrives as an `invoice`. Known defect, to be fixed separately.
|
|
453
562
|
|
|
563
|
+
### CII profiles
|
|
564
|
+
|
|
565
|
+
The Factur-X and ZUGFeRD encoders, and `cii`, which uses the Factur-X one, write
|
|
566
|
+
the full EN 16931 content, so they state the EN 16931 profile as the
|
|
567
|
+
specification identifier (BT-24): `urn:cen.eu:en16931:2017`, the identifier the
|
|
568
|
+
CEN validation artefacts use and the EN 16931 profile of Factur-X 1.0 and
|
|
569
|
+
ZUGFeRD 2.x, which ZUGFeRD also calls COMFORT.
|
|
570
|
+
|
|
571
|
+
`FacturXEncoder.setProfile` and `ZUGFeRDEncoder.setProfile` accept `EN16931` and
|
|
572
|
+
`COMFORT`, both written as that identifier. The other choices are refused with an
|
|
573
|
+
`EInvoiceFormatError` whose message names the profile and why, and whose
|
|
574
|
+
`unsupportedFeatures` is `['profile MINIMUM']` and so on; nothing is downgraded
|
|
575
|
+
to EN 16931 silently:
|
|
576
|
+
|
|
577
|
+
| Choice | Why it is refused |
|
|
578
|
+
| --- | --- |
|
|
579
|
+
| `MINIMUM` | The content written is EN 16931, which MINIMUM does not allow (no lines, no VAT breakdown, no street, postcode or city). A MINIMUM document is not an e-invoice (BMF letter of 15 October 2024, Rn. 25 and 30). |
|
|
580
|
+
| `BASIC` | The content written is EN 16931, which neither BASIC nor BASIC WL allows; BASIC WL has no lines and is not an e-invoice either (same letter, Rn. 25 and 30). |
|
|
581
|
+
| `EXTENDED` | The output has not been checked against the EXTENDED schema and rules. |
|
|
582
|
+
|
|
583
|
+
Factur-X 1.0 and ZUGFeRD 2.x are one standard with one XML, and their EN 16931
|
|
584
|
+
profile has one identifier. A ZUGFeRD 2.x export therefore reads back as
|
|
585
|
+
Factur-X, as XML and from a PDF alike, because a PDF's format is detected from
|
|
586
|
+
its embedded XML: `FormatDetector.detectFormat` and `getFormat()` return
|
|
587
|
+
`facturx`, and `FacturXDecoder` reads it. A ZUGFeRD PDF is still written with the
|
|
588
|
+
`zugferd-invoice.xml` attachment.
|
|
589
|
+
|
|
590
|
+
Decoding does not check the profile. A document that states another profile's
|
|
591
|
+
identifier, such as Factur-X MINIMUM or BASIC WL, a ZUGFeRD 2.0 identifier, the
|
|
592
|
+
`urn:zugferd:` identifiers earlier releases of this library wrote, or ZUGFeRD v1,
|
|
593
|
+
is detected and read as before. An identifier that names Factur-X reads as
|
|
594
|
+
`facturx`, one that names ZUGFeRD as `zugferd`: ZUGFeRD 2.0 has identifiers of
|
|
595
|
+
its own (`urn:zugferd.de:2p0:minimum`, and for BASIC and EXTENDED the EN 16931
|
|
596
|
+
identifier extended by `urn:zugferd.de:2p0:basic` or `urn:zugferd.de:2p0:extended`),
|
|
597
|
+
where ZUGFeRD 2.1 and Factur-X 1.0 share theirs.
|
|
598
|
+
|
|
454
599
|
## Public API overview
|
|
455
600
|
|
|
456
601
|
Top-level exports from `@fin.cx/einvoice` include:
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@fin.cx/einvoice',
|
|
6
|
-
version: '
|
|
6
|
+
version: '10.0.1',
|
|
7
7
|
description: 'A TypeScript module for creating, manipulating, and embedding XML data within PDF files specifically tailored for electronic invoice (einvoice) packages.'
|
|
8
8
|
}
|
package/ts/einvoice.ts
CHANGED
|
@@ -46,7 +46,11 @@ import {
|
|
|
46
46
|
// Import enhanced validators
|
|
47
47
|
import { EN16931Validator } from './formats/validation/en16931.validator.js';
|
|
48
48
|
import { EN16931BusinessRulesValidator } from './formats/validation/en16931.business-rules.validator.js';
|
|
49
|
-
import {
|
|
49
|
+
import { isRateNotAboveZero, standardRatedLineWithoutRateMessage } from './formats/utils/vat.category.js';
|
|
50
|
+
import { BR_CO_26_TEXT, envelopeSellerMeetsBrCo26 } from './formats/utils/seller.identifier.js';
|
|
51
|
+
import { findEnvelopeDifferences } from './formats/utils/stated.values.js';
|
|
52
|
+
import type { IEInvoiceStatedValues } from './interfaces/stated.values.js';
|
|
53
|
+
import { computeDocumentTotals, findInvalidItemAmounts } from './formats/utils/document.totals.js';
|
|
50
54
|
import { CodeListValidator } from './formats/validation/codelist.validator.js';
|
|
51
55
|
import type { ValidationOptions } from './formats/validation/validation.types.js';
|
|
52
56
|
|
|
@@ -176,6 +180,18 @@ export class EInvoice implements TAccountingDocEnvelope<TAccountingDocType, {}>
|
|
|
176
180
|
*/
|
|
177
181
|
public paidAmount?: number;
|
|
178
182
|
|
|
183
|
+
/**
|
|
184
|
+
* What a loaded document states: its totals (BG-22), VAT breakdown (BG-23),
|
|
185
|
+
* line net amounts and VAT categories (BT-131, BT-151) and document level
|
|
186
|
+
* allowances and charges (BG-20, BG-21), with their EN 16931 business terms,
|
|
187
|
+
* as written. Undefined for a document built in code and for ZUGFeRD 1.0.
|
|
188
|
+
* The envelope's totals (`totalNet`, `totalVat`, `totalGross`) are computed
|
|
189
|
+
* from quantity × net price and may differ; `validate()` reports each
|
|
190
|
+
* difference at the business level as `EINVOICE-STATED-DIFFERS`. No encoder
|
|
191
|
+
* writes these values, and they are not updated when the envelope changes.
|
|
192
|
+
*/
|
|
193
|
+
public statedValues?: IEInvoiceStatedValues;
|
|
194
|
+
|
|
179
195
|
/**
|
|
180
196
|
* On a final invoice (an invoice, a corrected invoice or a self-billed
|
|
181
197
|
* invoice): the advance payments it deducts; `paidAmount` equals their gross
|
|
@@ -354,6 +370,8 @@ export class EInvoice implements TAccountingDocEnvelope<TAccountingDocType, {}>
|
|
|
354
370
|
|
|
355
371
|
// Map the decoded invoice to our properties
|
|
356
372
|
this.mapFromTInvoice(invoice);
|
|
373
|
+
// what the document states, as it states it; undefined for a ZUGFeRD 1.0 document
|
|
374
|
+
this.statedValues = decoder.getStatedValues();
|
|
357
375
|
|
|
358
376
|
// Validate if requested
|
|
359
377
|
if (this.options.validateOnLoad) {
|
|
@@ -513,7 +531,14 @@ export class EInvoice implements TAccountingDocEnvelope<TAccountingDocType, {}>
|
|
|
513
531
|
return await encoder.encode(invoice);
|
|
514
532
|
} catch (error) {
|
|
515
533
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
516
|
-
|
|
534
|
+
// a refusal keeps the rules it names, so a caller can tell why without reading the message
|
|
535
|
+
throw new EInvoiceFormatError(
|
|
536
|
+
`Failed to encode to ${format}: ${errorMessage}`,
|
|
537
|
+
{
|
|
538
|
+
targetFormat: format,
|
|
539
|
+
...(error instanceof EInvoiceFormatError && error.unsupportedFeatures ? { unsupportedFeatures: error.unsupportedFeatures } : {}),
|
|
540
|
+
},
|
|
541
|
+
);
|
|
517
542
|
}
|
|
518
543
|
}
|
|
519
544
|
|
|
@@ -554,6 +579,15 @@ export class EInvoice implements TAccountingDocEnvelope<TAccountingDocType, {}>
|
|
|
554
579
|
result = this.validateDecodedInvoice(level);
|
|
555
580
|
}
|
|
556
581
|
|
|
582
|
+
// What a loaded document states and the envelope computes differently, reported and not
|
|
583
|
+
// changed: the envelope cannot express allowances, charges or a price base quantity
|
|
584
|
+
if (level === ValidationLevel.BUSINESS && this.statedValues && findInvalidItemAmounts(this.items).length === 0) {
|
|
585
|
+
const differences = findEnvelopeDifferences(this.statedValues, computeDocumentTotals(this), this.paidAmount);
|
|
586
|
+
result.warnings = (result.warnings ?? []).concat(
|
|
587
|
+
differences.map((difference) => ({ code: difference.ruleId, message: difference.message, location: difference.location })),
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
|
|
557
591
|
// At the business level the document check covers the unit rules (BR-23, BR-CL-23) on every
|
|
558
592
|
// line. The feature flag validators below check the lines again on the decoded model, which
|
|
559
593
|
// cannot tell an absent unit from an empty unitCode="" attribute, so there they would report
|
|
@@ -624,6 +658,22 @@ export class EInvoice implements TAccountingDocEnvelope<TAccountingDocType, {}>
|
|
|
624
658
|
);
|
|
625
659
|
|
|
626
660
|
if (level === ValidationLevel.BUSINESS) {
|
|
661
|
+
// BR-S-05: a line outside reverse charge is written as standard rated, whose rate is greater than zero
|
|
662
|
+
if (!invoice.reverseCharge) {
|
|
663
|
+
(invoice.items ?? []).forEach((item, index) => {
|
|
664
|
+
if (isRateNotAboveZero(item.vatPercentage)) {
|
|
665
|
+
errors.push({
|
|
666
|
+
code: 'BR-S-05',
|
|
667
|
+
message: standardRatedLineWithoutRateMessage({ index, vatPercentage: item.vatPercentage }),
|
|
668
|
+
location: `items[${index}].vatPercentage`,
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
// BR-CO-26: the seller states a seller, legal registration or VAT identifier; a tax number alone does not identify it
|
|
674
|
+
if (!envelopeSellerMeetsBrCo26(invoice)) {
|
|
675
|
+
errors.push({ code: 'BR-CO-26', message: BR_CO_26_TEXT, location: 'from.registrationDetails' });
|
|
676
|
+
}
|
|
627
677
|
// BR-23 and BR-CL-23: every line has a unit code, and it is a code of UN/ECE Recommendation 20
|
|
628
678
|
// with the Recommendation 21 extension, as the XML validators check it on an exported document
|
|
629
679
|
(invoice.items ?? []).forEach((item, index) => {
|
|
@@ -2,6 +2,9 @@ import type { TAccountingDoc } from '../../interfaces/common.js';
|
|
|
2
2
|
import { ValidationLevel } from '../../interfaces/common.js';
|
|
3
3
|
import type { ValidationResult } from '../../interfaces/common.js';
|
|
4
4
|
import { EInvoiceParsingError } from '../../errors.js';
|
|
5
|
+
import { parseCiiDateDay, parseIsoDateTimeDay, parseXsdDateDay } from '../utils/date.value.js';
|
|
6
|
+
import type { IEInvoiceStatedValues } from '../../interfaces/stated.values.js';
|
|
7
|
+
import { statedValueProblemMessage, type IStatedValuesRead } from '../utils/stated.values.js';
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* Base decoder class that defines common decoding functionality
|
|
@@ -45,6 +48,29 @@ export abstract class BaseDecoder {
|
|
|
45
48
|
return undefined;
|
|
46
49
|
}
|
|
47
50
|
|
|
51
|
+
/**
|
|
52
|
+
* The amounts, VAT breakdown, line amounts and document level allowances
|
|
53
|
+
* and charges the decoded document states, as it states them; undefined for
|
|
54
|
+
* a format that has no EN 16931 business terms to read them by
|
|
55
|
+
*/
|
|
56
|
+
public getStatedValues(): IEInvoiceStatedValues | undefined {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Refuses a document with a stated amount that is no decimal number, naming
|
|
62
|
+
* the business term; nothing is put in its place
|
|
63
|
+
* @param read The stated values read
|
|
64
|
+
* @param format The format read
|
|
65
|
+
*/
|
|
66
|
+
protected assertStatedValuesReadable(read: IStatedValuesRead, format: string): IEInvoiceStatedValues {
|
|
67
|
+
const [problem] = read.problems;
|
|
68
|
+
if (problem) {
|
|
69
|
+
throw new EInvoiceParsingError(statedValueProblemMessage(problem), { format });
|
|
70
|
+
}
|
|
71
|
+
return read.values;
|
|
72
|
+
}
|
|
73
|
+
|
|
48
74
|
/**
|
|
49
75
|
* Parses a CII date string based on format code
|
|
50
76
|
* @param dateStr Date string
|
|
@@ -76,7 +102,15 @@ export abstract class BaseDecoder {
|
|
|
76
102
|
}
|
|
77
103
|
|
|
78
104
|
/**
|
|
79
|
-
* Parses a required CII date without inventing a fallback value
|
|
105
|
+
* Parses a required CII date without inventing a fallback value: the
|
|
106
|
+
* calendar day it states, as the UTC midnight of that day. Format 102
|
|
107
|
+
* (`YYYYMMDD`) is the one EN 16931 asks for; a date stated with a time of
|
|
108
|
+
* day in format 203 (`YYYYMMDDhhmm`) or 204 (`YYYYMMDDhhmmss`), or without a
|
|
109
|
+
* format code as an ISO 8601 date or date and time (`parseIsoDateTimeDay`),
|
|
110
|
+
* is read as the day it states, whatever the time of day and the zone.
|
|
111
|
+
* Format 610 (`YYYYMM`) is read as the 1st of the month. Any other value is
|
|
112
|
+
* refused with an `EInvoiceParsingError`: no day is guessed, and none is read
|
|
113
|
+
* in the zone of the machine.
|
|
80
114
|
*/
|
|
81
115
|
protected parseRequiredCIIDate(dateStr: string, format?: string): number {
|
|
82
116
|
const value = dateStr.trim();
|
|
@@ -85,22 +119,17 @@ export abstract class BaseDecoder {
|
|
|
85
119
|
format: 'cii',
|
|
86
120
|
});
|
|
87
121
|
}
|
|
122
|
+
const formatCode = (format ?? '').trim();
|
|
88
123
|
|
|
89
|
-
if (
|
|
90
|
-
const
|
|
91
|
-
if (
|
|
92
|
-
throw this.createInvalidDateError(value,
|
|
124
|
+
if (formatCode === '102' || formatCode === '203' || formatCode === '204') {
|
|
125
|
+
const day = parseCiiDateDay(value, formatCode);
|
|
126
|
+
if (day === undefined) {
|
|
127
|
+
throw this.createInvalidDateError(value, `CII format ${formatCode}`);
|
|
93
128
|
}
|
|
94
|
-
return
|
|
95
|
-
Number(match[1]),
|
|
96
|
-
Number(match[2]),
|
|
97
|
-
Number(match[3]),
|
|
98
|
-
value,
|
|
99
|
-
'CII format 102',
|
|
100
|
-
);
|
|
129
|
+
return day;
|
|
101
130
|
}
|
|
102
131
|
|
|
103
|
-
if (
|
|
132
|
+
if (formatCode === '610') {
|
|
104
133
|
const match = /^(\d{4})(\d{2})$/.exec(value);
|
|
105
134
|
if (!match) {
|
|
106
135
|
throw this.createInvalidDateError(value, 'CII format 610');
|
|
@@ -114,48 +143,29 @@ export abstract class BaseDecoder {
|
|
|
114
143
|
);
|
|
115
144
|
}
|
|
116
145
|
|
|
117
|
-
|
|
118
|
-
|
|
146
|
+
if (formatCode) {
|
|
147
|
+
throw this.createInvalidDateError(value, `CII format ${formatCode}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const day = parseIsoDateTimeDay(value);
|
|
151
|
+
if (day === undefined) {
|
|
119
152
|
throw this.createInvalidDateError(value, 'CII');
|
|
120
153
|
}
|
|
121
|
-
return
|
|
154
|
+
return day;
|
|
122
155
|
}
|
|
123
156
|
|
|
124
157
|
/**
|
|
125
|
-
* Parses a required UBL xsd:date value strictly
|
|
158
|
+
* Parses a required UBL xsd:date value strictly: the calendar day it states,
|
|
159
|
+
* as the UTC midnight of that day. A time zone does not move the day
|
|
160
|
+
* (`parseXsdDateDay`).
|
|
126
161
|
*/
|
|
127
162
|
protected parseRequiredUblDate(dateStr: string): number {
|
|
128
163
|
const value = dateStr.trim();
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
if (!match) {
|
|
132
|
-
throw this.createInvalidDateError(value, 'UBL xsd:date');
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const timestamp = this.createStrictUtcTimestamp(
|
|
136
|
-
Number(match[1]),
|
|
137
|
-
Number(match[2]),
|
|
138
|
-
Number(match[3]),
|
|
139
|
-
value,
|
|
140
|
-
'UBL xsd:date',
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
if (!match[5]) {
|
|
144
|
-
return timestamp;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const offsetHours = Number(match[6]);
|
|
148
|
-
const offsetMinutes = Number(match[7]);
|
|
149
|
-
if (
|
|
150
|
-
offsetHours > 14 ||
|
|
151
|
-
offsetMinutes > 59 ||
|
|
152
|
-
(offsetHours === 14 && offsetMinutes !== 0)
|
|
153
|
-
) {
|
|
164
|
+
const day = parseXsdDateDay(value);
|
|
165
|
+
if (day === undefined) {
|
|
154
166
|
throw this.createInvalidDateError(value, 'UBL xsd:date');
|
|
155
167
|
}
|
|
156
|
-
|
|
157
|
-
const direction = match[5] === '+' ? 1 : -1;
|
|
158
|
-
return timestamp - direction * (offsetHours * 60 + offsetMinutes) * 60_000;
|
|
168
|
+
return day;
|
|
159
169
|
}
|
|
160
170
|
|
|
161
171
|
private createStrictUtcTimestamp(
|