@fin.cx/einvoice 8.3.1 → 10.0.0

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.
Files changed (139) hide show
  1. package/dist_ts/00_commitinfo_data.js +2 -2
  2. package/dist_ts/einvoice.d.ts +26 -0
  3. package/dist_ts/einvoice.js +61 -3
  4. package/dist_ts/formats/base/base.decoder.d.ts +18 -1
  5. package/dist_ts/formats/base/base.decoder.js +30 -17
  6. package/dist_ts/formats/cii/cii.decoder.d.ts +89 -6
  7. package/dist_ts/formats/cii/cii.decoder.js +125 -30
  8. package/dist_ts/formats/cii/cii.encoder.d.ts +71 -1
  9. package/dist_ts/formats/cii/cii.encoder.js +195 -12
  10. package/dist_ts/formats/cii/cii.types.d.ts +2 -5
  11. package/dist_ts/formats/cii/cii.types.js +7 -9
  12. package/dist_ts/formats/cii/cii.validator.d.ts +13 -6
  13. package/dist_ts/formats/cii/cii.validator.js +29 -30
  14. package/dist_ts/formats/cii/facturx/facturx.decoder.js +19 -3
  15. package/dist_ts/formats/cii/facturx/facturx.encoder.d.ts +0 -6
  16. package/dist_ts/formats/cii/facturx/facturx.encoder.js +17 -26
  17. package/dist_ts/formats/cii/facturx/facturx.types.d.ts +0 -5
  18. package/dist_ts/formats/cii/facturx/facturx.types.js +1 -8
  19. package/dist_ts/formats/cii/facturx/facturx.validator.d.ts +0 -5
  20. package/dist_ts/formats/cii/facturx/facturx.validator.js +5 -29
  21. package/dist_ts/formats/cii/zugferd/zugferd.decoder.js +19 -2
  22. package/dist_ts/formats/cii/zugferd/zugferd.encoder.d.ts +0 -7
  23. package/dist_ts/formats/cii/zugferd/zugferd.encoder.js +15 -68
  24. package/dist_ts/formats/cii/zugferd/zugferd.types.d.ts +0 -5
  25. package/dist_ts/formats/cii/zugferd/zugferd.types.js +1 -8
  26. package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.d.ts +26 -0
  27. package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.js +47 -1
  28. package/dist_ts/formats/cii/zugferd/zugferd.validator.js +6 -2
  29. package/dist_ts/formats/semantic/semantic.adapter.js +29 -20
  30. package/dist_ts/formats/semantic/semantic.validator.js +17 -2
  31. package/dist_ts/formats/ubl/en16931.ubl.validator.d.ts +0 -4
  32. package/dist_ts/formats/ubl/en16931.ubl.validator.js +5 -42
  33. package/dist_ts/formats/ubl/generic/ubl.encoder.js +29 -26
  34. package/dist_ts/formats/ubl/ubl.decoder.d.ts +4 -0
  35. package/dist_ts/formats/ubl/ubl.decoder.js +9 -1
  36. package/dist_ts/formats/ubl/ubl.encoder.js +13 -5
  37. package/dist_ts/formats/ubl/ubl.validator.d.ts +13 -0
  38. package/dist_ts/formats/ubl/ubl.validator.js +28 -1
  39. package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.js +14 -1
  40. package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.d.ts +0 -7
  41. package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.js +30 -45
  42. package/dist_ts/formats/ubl/xrechnung.validator.d.ts +5 -4
  43. package/dist_ts/formats/ubl/xrechnung.validator.js +71 -68
  44. package/dist_ts/formats/utils/date.value.d.ts +11 -0
  45. package/dist_ts/formats/utils/date.value.js +33 -1
  46. package/dist_ts/formats/utils/document.totals.d.ts +1 -3
  47. package/dist_ts/formats/utils/document.totals.js +1 -2
  48. package/dist_ts/formats/utils/format.detector.js +16 -10
  49. package/dist_ts/formats/utils/paid.amount.d.ts +44 -0
  50. package/dist_ts/formats/utils/paid.amount.js +124 -0
  51. package/dist_ts/formats/utils/party.contact.d.ts +16 -0
  52. package/dist_ts/formats/utils/party.contact.js +16 -0
  53. package/dist_ts/formats/utils/party.identifier.d.ts +28 -0
  54. package/dist_ts/formats/utils/party.identifier.js +49 -0
  55. package/dist_ts/formats/utils/peppol.profile.d.ts +10 -0
  56. package/dist_ts/formats/utils/peppol.profile.js +12 -0
  57. package/dist_ts/formats/utils/preceding.invoice.d.ts +12 -2
  58. package/dist_ts/formats/utils/preceding.invoice.js +22 -3
  59. package/dist_ts/formats/utils/seller.identifier.d.ts +46 -0
  60. package/dist_ts/formats/utils/seller.identifier.js +78 -0
  61. package/dist_ts/formats/utils/stated.values.d.ts +80 -0
  62. package/dist_ts/formats/utils/stated.values.js +418 -0
  63. package/dist_ts/formats/utils/vat.category.d.ts +30 -2
  64. package/dist_ts/formats/utils/vat.category.js +36 -6
  65. package/dist_ts/formats/utils/vat.id.d.ts +18 -0
  66. package/dist_ts/formats/utils/vat.id.js +22 -0
  67. package/dist_ts/formats/validation/conformance.harness.js +6 -6
  68. package/dist_ts/formats/validation/en16931.business-rules.validator.js +4 -19
  69. package/dist_ts/formats/validation/facturx.validator.js +6 -6
  70. package/dist_ts/formats/validation/integrated.validator.js +4 -13
  71. package/dist_ts/formats/validation/peppol.validator.js +6 -13
  72. package/dist_ts/formats/validation/validation.types.d.ts +5 -0
  73. package/dist_ts/formats/validation/validation.types.js +6 -1
  74. package/dist_ts/formats/validation/vat-categories.validator.d.ts +21 -42
  75. package/dist_ts/formats/validation/vat-categories.validator.js +137 -431
  76. package/dist_ts/formats/validation/xrechnung.validator.d.ts +11 -58
  77. package/dist_ts/formats/validation/xrechnung.validator.js +58 -324
  78. package/dist_ts/index.d.ts +1 -0
  79. package/dist_ts/index.js +1 -1
  80. package/dist_ts/interfaces/common.d.ts +1 -0
  81. package/dist_ts/interfaces/en16931-metadata.d.ts +0 -5
  82. package/dist_ts/interfaces/stated.values.d.ts +93 -0
  83. package/dist_ts/interfaces/stated.values.js +2 -0
  84. package/package.json +2 -2
  85. package/readme.md +182 -6
  86. package/ts/00_commitinfo_data.ts +1 -1
  87. package/ts/einvoice.ts +70 -2
  88. package/ts/formats/base/base.decoder.ts +32 -29
  89. package/ts/formats/cii/cii.decoder.ts +159 -32
  90. package/ts/formats/cii/cii.encoder.ts +210 -14
  91. package/ts/formats/cii/cii.types.ts +7 -9
  92. package/ts/formats/cii/cii.validator.ts +30 -32
  93. package/ts/formats/cii/facturx/facturx.decoder.ts +21 -2
  94. package/ts/formats/cii/facturx/facturx.encoder.ts +19 -26
  95. package/ts/formats/cii/facturx/facturx.types.ts +0 -9
  96. package/ts/formats/cii/facturx/facturx.validator.ts +5 -43
  97. package/ts/formats/cii/zugferd/zugferd.decoder.ts +21 -1
  98. package/ts/formats/cii/zugferd/zugferd.encoder.ts +16 -73
  99. package/ts/formats/cii/zugferd/zugferd.types.ts +0 -9
  100. package/ts/formats/cii/zugferd/zugferd.v1.decoder.ts +66 -0
  101. package/ts/formats/cii/zugferd/zugferd.validator.ts +5 -1
  102. package/ts/formats/semantic/semantic.adapter.ts +28 -19
  103. package/ts/formats/semantic/semantic.validator.ts +17 -1
  104. package/ts/formats/ubl/en16931.ubl.validator.ts +5 -64
  105. package/ts/formats/ubl/generic/ubl.encoder.ts +31 -25
  106. package/ts/formats/ubl/ubl.decoder.ts +12 -0
  107. package/ts/formats/ubl/ubl.encoder.ts +12 -4
  108. package/ts/formats/ubl/ubl.validator.ts +29 -0
  109. package/ts/formats/ubl/xrechnung/xrechnung.decoder.ts +13 -0
  110. package/ts/formats/ubl/xrechnung/xrechnung.encoder.ts +33 -45
  111. package/ts/formats/ubl/xrechnung.validator.ts +75 -127
  112. package/ts/formats/utils/date.value.ts +33 -0
  113. package/ts/formats/utils/document.totals.ts +1 -4
  114. package/ts/formats/utils/format.detector.ts +15 -9
  115. package/ts/formats/utils/paid.amount.ts +160 -0
  116. package/ts/formats/utils/party.contact.ts +30 -0
  117. package/ts/formats/utils/party.identifier.ts +61 -0
  118. package/ts/formats/utils/peppol.profile.ts +13 -0
  119. package/ts/formats/utils/preceding.invoice.ts +23 -2
  120. package/ts/formats/utils/seller.identifier.ts +102 -0
  121. package/ts/formats/utils/stated.values.ts +499 -0
  122. package/ts/formats/utils/vat.category.ts +47 -5
  123. package/ts/formats/utils/vat.id.ts +24 -0
  124. package/ts/formats/validation/conformance.harness.ts +5 -5
  125. package/ts/formats/validation/en16931.business-rules.validator.ts +3 -26
  126. package/ts/formats/validation/facturx.validator.ts +5 -5
  127. package/ts/formats/validation/integrated.validator.ts +3 -16
  128. package/ts/formats/validation/peppol.validator.ts +5 -16
  129. package/ts/formats/validation/validation.types.ts +7 -1
  130. package/ts/formats/validation/vat-categories.validator.ts +179 -761
  131. package/ts/formats/validation/xrechnung.validator.ts +61 -382
  132. package/ts/index.ts +9 -0
  133. package/ts/interfaces/common.ts +1 -0
  134. package/ts/interfaces/en16931-metadata.ts +3 -8
  135. package/ts/interfaces/stated.values.ts +94 -0
  136. package/ts/readme.md +1 -1
  137. package/dist_ts/formats/utils/eu.memberstates.d.ts +0 -11
  138. package/dist_ts/formats/utils/eu.memberstates.js +0 -16
  139. 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
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RhdGVkLnZhbHVlcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2ludGVyZmFjZXMvc3RhdGVkLnZhbHVlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIn0=
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fin.cx/einvoice",
3
- "version": "8.3.1",
3
+ "version": "10.0.0",
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",
@@ -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.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",
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
- - The Factur-X MINIMUM profile has no BG-3. A document with a preceding invoice
226
- reference is refused there with an `EInvoiceFormatError`.
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,100 @@ 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
+
423
+ ## Paid amount and advance payments
424
+
425
+ `paidAmount` is the gross sum received before the document was issued, the
426
+ paid amount (BT-113). Every format writes it (`cbc:PrepaidAmount`,
427
+ `ram:TotalPrepaidAmount`) and the amount due for payment (BT-115) as the gross
428
+ total less it (BR-CO-16), zero when paid in full and negative when more was
429
+ paid; a paid amount of zero is nothing paid and is not written. The decoders
430
+ read it back. A paid amount with more decimals than the currency has (two at
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.
435
+
436
+ A final invoice (an invoice, a corrected invoice or a self-billed invoice) may
437
+ list the `advancePayments` it deducts (`@tsclass/tsclass` 9.9.0 on).
438
+ `paidAmount` then equals their gross sum, the `net` plus the `vat` of every
439
+ group of every payment, to the last decimal; a document that breaks this, or
440
+ lists advance payments without a paid amount, is refused, and neither value is
441
+ recomputed from the other. The advance invoice of each payment that states one
442
+ is written as a preceding invoice reference (BG-3), after the invoice a
443
+ corrected invoice corrects. Another document type with advance payments is
444
+ refused.
445
+
446
+ The deduction a final invoice owes under § 14 Abs. 5 Satz 2 UStG, the
447
+ considerations received before the supply and the tax on them per rate
448
+ (Abschnitt 14.8 Abs. 7 UStAE), has no element in EN 16931: "Derzeit sind die
449
+ Anforderungen an eine Endrechnung noch nicht im strukturierten Teil einer
450
+ E-Rechnung darstellbar" (BMF letter of 15 October 2024, Rn. 48). The encoders
451
+ write the paid amount and the references and add no text of their own. The
452
+ issuer either adds the deduction as an annex (Abschnitt 14.8 Abs. 8 Nr. 2
453
+ UStAE), which Rn. 48 accepts as an unstructured file in an e-invoice issued
454
+ until 31 December 2027 and which `@design.estate/dees-document` 3.4 prints, or
455
+ issues an invoice over the remaining consideration only (Restrechnung,
456
+ Abschnitt 14.8 Abs. 11 UStAE), which Rn. 48 recommends. Reading an e-invoice
457
+ back yields its paid amount but no `advancePayments`: the XML does not carry the
458
+ net and the tax of every payment. A corrected invoice's advance invoices read
459
+ back as related documents it corrects, since BG-3 does not tell them apart,
460
+ and an invoice's are not read.
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
+
378
501
  ## Calendar dates
379
502
 
380
503
  A calendar date of the invoice (issue date, due date, delivery date, invoicing
@@ -383,7 +506,10 @@ zone: UBL writes it as `xsd:date` (`2026-09-20`), CII in format 102
383
506
  (`20260920`). In the accounting document envelope such a day is a timestamp:
384
507
  the UTC midnight of that day, `Date.UTC(2026, 8, 20)`.
385
508
 
386
- - 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.
387
513
  - The encoders write the day of that timestamp in UTC and count the payment
388
514
  term in UTC days, so what they write does not depend on the time zone of the
389
515
  machine that writes it.
@@ -400,7 +526,7 @@ the UTC midnight of that day, `Date.UTC(2026, 8, 20)`.
400
526
  | `xrechnung` | Yes | Yes | Yes | Yes | UBL export; UBL and CII import |
401
527
  | `cii` | Yes | Yes | Yes | Partial | Generic CII export currently uses the Factur-X encoder path |
402
528
  | `facturx` | Yes | Yes | Yes | Yes | Main CII-based generation path |
403
- | `zugferd` | Yes | Yes | Yes | Partial | Input supports v1 and v2+, export focuses on the current encoder |
529
+ | `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)) |
404
530
  | `fatturapa` | Yes | No | No | Basic placeholder | Detection exists, decoder/encoder do not |
405
531
 
406
532
  ### Important format notes
@@ -408,13 +534,63 @@ the UTC midnight of that day, `Date.UTC(2026, 8, 20)`.
408
534
  - There is no dedicated root-level `peppol` export format. PEPPOL-specific checks exist, but the XML export targets are still `ubl` / `xrechnung`.
409
535
  - XRechnung import routes UBL and CII by the namespace-qualified XML root.
410
536
  Missing or invalid document-level invoice IDs and issue dates are rejected.
537
+ - The buyer reference (BT-10): every format writes `buyerReference`
538
+ (`cbc:BuyerReference`, `ram:BuyerReference`) and reads it back, ZUGFeRD 1.0
539
+ input as well. XRechnung requires it (BR-DE-15), and it is the
540
+ buyer's: the identifier the buyer gives the seller for routing the invoice, a
541
+ Leitweg-ID for a public buyer, any identifier of the buyer's choice in B2B.
542
+ `exportXml('xrechnung')` writes `buyerReference` (or the reference a decoded
543
+ UBL document keeps) and refuses a document without one with an
544
+ `EInvoiceFormatError` whose `unsupportedFeatures` is `['BR-DE-15']`; nothing,
545
+ not the invoice number, is put in its place. A seller whose buyer gave no
546
+ reference exports `ubl` (EN 16931), where BT-10 is optional. The metadata has
547
+ no buyer reference of its own: `buyerReference` is the one field.
411
548
  - `FatturaPA` should be documented as detection-only right now.
412
- - 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 it back only when the document states it.
413
- - 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).
549
+ - 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.
550
+ - 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.
551
+ - 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.
414
552
  - 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.
553
+ - 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.
554
+ - 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`.
415
555
  - 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.
416
556
  - 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.
417
557
 
558
+ ### CII profiles
559
+
560
+ The Factur-X and ZUGFeRD encoders, and `cii`, which uses the Factur-X one, write
561
+ the full EN 16931 content, so they state the EN 16931 profile as the
562
+ specification identifier (BT-24): `urn:cen.eu:en16931:2017`, the identifier the
563
+ CEN validation artefacts use and the EN 16931 profile of Factur-X 1.0 and
564
+ ZUGFeRD 2.x, which ZUGFeRD also calls COMFORT.
565
+
566
+ `FacturXEncoder.setProfile` and `ZUGFeRDEncoder.setProfile` accept `EN16931` and
567
+ `COMFORT`, both written as that identifier. The other choices are refused with an
568
+ `EInvoiceFormatError` whose message names the profile and why, and whose
569
+ `unsupportedFeatures` is `['profile MINIMUM']` and so on; nothing is downgraded
570
+ to EN 16931 silently:
571
+
572
+ | Choice | Why it is refused |
573
+ | --- | --- |
574
+ | `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). |
575
+ | `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). |
576
+ | `EXTENDED` | The output has not been checked against the EXTENDED schema and rules. |
577
+
578
+ Factur-X 1.0 and ZUGFeRD 2.x are one standard with one XML, and their EN 16931
579
+ profile has one identifier. A ZUGFeRD 2.x export therefore reads back as
580
+ Factur-X, as XML and from a PDF alike, because a PDF's format is detected from
581
+ its embedded XML: `FormatDetector.detectFormat` and `getFormat()` return
582
+ `facturx`, and `FacturXDecoder` reads it. A ZUGFeRD PDF is still written with the
583
+ `zugferd-invoice.xml` attachment.
584
+
585
+ Decoding does not check the profile. A document that states another profile's
586
+ identifier, such as Factur-X MINIMUM or BASIC WL, a ZUGFeRD 2.0 identifier, the
587
+ `urn:zugferd:` identifiers earlier releases of this library wrote, or ZUGFeRD v1,
588
+ is detected and read as before. An identifier that names Factur-X reads as
589
+ `facturx`, one that names ZUGFeRD as `zugferd`: ZUGFeRD 2.0 has identifiers of
590
+ its own (`urn:zugferd.de:2p0:minimum`, and for BASIC and EXTENDED the EN 16931
591
+ identifier extended by `urn:zugferd.de:2p0:basic` or `urn:zugferd.de:2p0:extended`),
592
+ where ZUGFeRD 2.1 and Factur-X 1.0 share theirs.
593
+
418
594
  ## Public API overview
419
595
 
420
596
  Top-level exports from `@fin.cx/einvoice` include:
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@fin.cx/einvoice',
6
- version: '8.3.1',
6
+ version: '10.0.0',
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 { computeDocumentTotals } from './formats/utils/document.totals.js';
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
 
@@ -168,6 +172,34 @@ export class EInvoice implements TAccountingDocEnvelope<TAccountingDocType, {}>
168
172
  * written to the XML; an issuer states them as a note in `notes`.
169
173
  */
170
174
  public corrections?: finance.TCorrectedInvoice['corrections'];
175
+
176
+ /**
177
+ * The gross sum received before the document was issued, the paid amount
178
+ * (BT-113); the amount due (BT-115) is the gross total less it. Absent or
179
+ * zero: nothing was paid.
180
+ */
181
+ public paidAmount?: number;
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
+
195
+ /**
196
+ * On a final invoice (an invoice, a corrected invoice or a self-billed
197
+ * invoice): the advance payments it deducts; `paidAmount` equals their gross
198
+ * sum. Their advance invoices are written as preceding invoice references
199
+ * (BG-3); the deduction per rate has no element in EN 16931, and a document
200
+ * read from XML has none.
201
+ */
202
+ public advancePayments?: finance.TInvoice['advancePayments'];
171
203
  public printResult?: {
172
204
  pdfBufferString: string;
173
205
  totalNet: number;
@@ -338,6 +370,8 @@ export class EInvoice implements TAccountingDocEnvelope<TAccountingDocType, {}>
338
370
 
339
371
  // Map the decoded invoice to our properties
340
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();
341
375
 
342
376
  // Validate if requested
343
377
  if (this.options.validateOnLoad) {
@@ -465,6 +499,8 @@ export class EInvoice implements TAccountingDocEnvelope<TAccountingDocType, {}>
465
499
  relatedDocuments: this.relatedDocuments,
466
500
  correctedInvoice: this.correctedInvoice,
467
501
  corrections: this.corrections,
502
+ paidAmount: this.paidAmount,
503
+ advancePayments: this.advancePayments,
468
504
  printResult: this.printResult
469
505
  };
470
506
 
@@ -495,7 +531,14 @@ export class EInvoice implements TAccountingDocEnvelope<TAccountingDocType, {}>
495
531
  return await encoder.encode(invoice);
496
532
  } catch (error) {
497
533
  const errorMessage = error instanceof Error ? error.message : String(error);
498
- throw new EInvoiceFormatError(`Failed to encode to ${format}: ${errorMessage}`, { targetFormat: format });
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
+ );
499
542
  }
500
543
  }
501
544
 
@@ -536,6 +579,15 @@ export class EInvoice implements TAccountingDocEnvelope<TAccountingDocType, {}>
536
579
  result = this.validateDecodedInvoice(level);
537
580
  }
538
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
+
539
591
  // At the business level the document check covers the unit rules (BR-23, BR-CL-23) on every
540
592
  // line. The feature flag validators below check the lines again on the decoded model, which
541
593
  // cannot tell an absent unit from an empty unitCode="" attribute, so there they would report
@@ -606,6 +658,22 @@ export class EInvoice implements TAccountingDocEnvelope<TAccountingDocType, {}>
606
658
  );
607
659
 
608
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
+ }
609
677
  // BR-23 and BR-CL-23: every line has a unit code, and it is a code of UN/ECE Recommendation 20
610
678
  // with the Recommendation 21 extension, as the XML validators check it on an exported document
611
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 { 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
@@ -122,40 +148,17 @@ export abstract class BaseDecoder {
122
148
  }
123
149
 
124
150
  /**
125
- * Parses a required UBL xsd:date value strictly and deterministically.
151
+ * Parses a required UBL xsd:date value strictly: the calendar day it states,
152
+ * as the UTC midnight of that day. A time zone does not move the day
153
+ * (`parseXsdDateDay`).
126
154
  */
127
155
  protected parseRequiredUblDate(dateStr: string): number {
128
156
  const value = dateStr.trim();
129
- const match =
130
- /^(\d{4})-(\d{2})-(\d{2})(?:(Z)|([+-])(\d{2}):(\d{2}))?$/.exec(value);
131
- if (!match) {
157
+ const day = parseXsdDateDay(value);
158
+ if (day === undefined) {
132
159
  throw this.createInvalidDateError(value, 'UBL xsd:date');
133
160
  }
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
- ) {
154
- throw this.createInvalidDateError(value, 'UBL xsd:date');
155
- }
156
-
157
- const direction = match[5] === '+' ? 1 : -1;
158
- return timestamp - direction * (offsetHours * 60 + offsetMinutes) * 60_000;
161
+ return day;
159
162
  }
160
163
 
161
164
  private createStrictUtcTimestamp(