@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
@@ -1,12 +1,12 @@
1
1
  import { CIIBaseEncoder } from '../cii.encoder.js';
2
2
  import type { TAccountingDoc, TCreditNote, TInvoiceDocument } from '../../../interfaces/common.js';
3
- import { FACTURX_PROFILE_IDS } from './facturx.types.js';
4
3
  import { DOMParser, XMLSerializer } from '../../../plugins.js';
5
4
  import { getDocumentTypeCode } from '../../utils/document.typecode.js';
6
5
  import { computeDocumentTotals } from '../../utils/document.totals.js';
6
+ import { getPaymentTotals } from '../../utils/paid.amount.js';
7
7
  import { toPlainDecimalString } from '../../utils/number.text.js';
8
8
  import { resolveCountryCode } from '../../utils/country.code.js';
9
- import { getWritableDate, getWritableDueDate } from '../../utils/date.value.js';
9
+ import { getWritableDueDate } from '../../utils/date.value.js';
10
10
 
11
11
  /**
12
12
  * Encoder for Factur-X invoice format
@@ -93,15 +93,7 @@ export class FacturXEncoder extends CIIBaseEncoder {
93
93
  // Add ID element with profile
94
94
  const idElement = doc.createElement('ram:ID');
95
95
 
96
- // Set profile based on the selected profile
97
- let profileId = FACTURX_PROFILE_IDS.EN16931;
98
- if (this.profile === 'BASIC') {
99
- profileId = FACTURX_PROFILE_IDS.BASIC;
100
- } else if (this.profile === 'MINIMUM') {
101
- profileId = FACTURX_PROFILE_IDS.MINIMUM;
102
- }
103
-
104
- idElement.textContent = profileId;
96
+ idElement.textContent = this.getProfileId();
105
97
  guidelineElement.appendChild(idElement);
106
98
  }
107
99
 
@@ -208,9 +200,14 @@ export class FacturXEncoder extends CIIBaseEncoder {
208
200
  const agreementElement = doc.createElement('ram:ApplicableHeaderTradeAgreement');
209
201
  transactionElement.appendChild(agreementElement);
210
202
 
203
+ // Buyer reference (BT-10)
204
+ this.addBuyerReference(doc, agreementElement, invoice);
205
+
211
206
  // Add seller
212
207
  const sellerElement = doc.createElement('ram:SellerTradeParty');
213
208
  this.addPartyInfo(doc, sellerElement, invoice.from, 'seller');
209
+ // Seller electronic address (BT-34)
210
+ this.addSellerElectronicAddress(doc, sellerElement, invoice);
214
211
  agreementElement.appendChild(sellerElement);
215
212
 
216
213
  // Add buyer
@@ -227,6 +224,9 @@ export class FacturXEncoder extends CIIBaseEncoder {
227
224
  * @param partyRole Whether the party is the seller or the buyer
228
225
  */
229
226
  private addPartyInfo(doc: Document, partyElement: Element, party: any, partyRole: 'seller' | 'buyer'): void {
227
+ // Party identifiers (BT-29 seller, BT-46 buyer)
228
+ this.addPartyIdentifiers(doc, partyElement, party);
229
+
230
230
  // Add name
231
231
  const nameElement = doc.createElement('ram:Name');
232
232
  nameElement.textContent = party.name;
@@ -380,6 +380,9 @@ export class FacturXEncoder extends CIIBaseEncoder {
380
380
 
381
381
  settlementElement.appendChild(paymentTermsElement);
382
382
 
383
+ // Invoicing period (BG-14)
384
+ this.addInvoicingPeriod(doc, settlementElement, invoice);
385
+
383
386
  // Add totals, in the order of the CII schema
384
387
  const monetarySummationElement = doc.createElement('ram:SpecifiedTradeSettlementHeaderMonetarySummation');
385
388
  const appendAmount = (name: string, value: { toFixed(decimalPlaces: number): string }, withCurrency = false) => {
@@ -394,7 +397,11 @@ export class FacturXEncoder extends CIIBaseEncoder {
394
397
  appendAmount('ram:TaxBasisTotalAmount', totals.taxBasisTotal); // BT-109
395
398
  appendAmount('ram:TaxTotalAmount', totals.taxTotal, true); // BT-110
396
399
  appendAmount('ram:GrandTotalAmount', totals.grandTotal); // BT-112
397
- appendAmount('ram:DuePayableAmount', totals.duePayable); // BT-115
400
+ const payment = getPaymentTotals(invoice, totals);
401
+ if (payment.paidAmount) {
402
+ appendAmount('ram:TotalPrepaidAmount', payment.paidAmount); // BT-113
403
+ }
404
+ appendAmount('ram:DuePayableAmount', payment.duePayable); // BT-115
398
405
 
399
406
  settlementElement.appendChild(monetarySummationElement);
400
407
  }
@@ -500,18 +507,4 @@ export class FacturXEncoder extends CIIBaseEncoder {
500
507
  }
501
508
  }
502
509
  }
503
-
504
- /**
505
- * Formats a date as YYYYMMDD
506
- * @param timestamp Timestamp to format
507
- * @returns Formatted date string
508
- */
509
- private formatDateYYYYMMDD(timestamp: number, field: string): string {
510
- const date = getWritableDate(timestamp, field, 'cii');
511
- // the UTC fields: a calendar day is its UTC midnight, whatever the server's zone
512
- const year = date.getUTCFullYear();
513
- const month = (date.getUTCMonth() + 1).toString().padStart(2, '0');
514
- const day = date.getUTCDate().toString().padStart(2, '0');
515
- return `${year}${month}${day}`;
516
- }
517
510
  }
@@ -1,16 +1,7 @@
1
- import { CIIProfile, CII_PROFILE_IDS } from '../cii.types.js';
2
-
3
1
  /**
4
2
  * Factur-X specific constants and types
5
3
  */
6
4
 
7
- // Factur-X profile IDs
8
- export const FACTURX_PROFILE_IDS = {
9
- MINIMUM: CII_PROFILE_IDS.FACTURX_MINIMUM,
10
- BASIC: CII_PROFILE_IDS.FACTURX_BASIC,
11
- EN16931: CII_PROFILE_IDS.FACTURX_EN16931
12
- };
13
-
14
5
  // Factur-X PDF attachment filename
15
6
  export const FACTURX_ATTACHMENT_FILENAME = 'factur-x.xml';
16
7
 
@@ -59,8 +59,8 @@ export class FacturXValidator extends CIIBaseValidator {
59
59
 
60
60
  let valid = true;
61
61
 
62
- // BR-16: Amount due for payment (BT-115) = Invoice total amount with VAT (BT-112) - Paid amount (BT-113)
63
- valid = this.validateAmounts() && valid;
62
+ // BR-CO-10 to BR-CO-17: the stated totals, VAT breakdown and line amounts agree with each other
63
+ valid = this.validateStatedTotals() && valid;
64
64
 
65
65
  // BR-CO-3: Value added tax point date (BT-7) and Value added tax point date code (BT-8) are mutually exclusive
66
66
  valid = this.validateMutuallyExclusiveFields() && valid;
@@ -70,6 +70,9 @@ export class FacturXValidator extends CIIBaseValidator {
70
70
  // and/or the Seller tax representative VAT identifier (BT-63).
71
71
  valid = this.validateSellerVatIdentifier() && valid;
72
72
 
73
+ // BR-CO-26: the seller states a seller, legal registration or VAT identifier
74
+ valid = this.validateSellerIdentifier() && valid;
75
+
73
76
  // BR-23: every line has a unit code
74
77
  valid = this.validateLineUnitCodePresence() && valid;
75
78
 
@@ -79,47 +82,6 @@ export class FacturXValidator extends CIIBaseValidator {
79
82
  return valid;
80
83
  }
81
84
 
82
- /**
83
- * Validates amount calculations in the invoice
84
- * @returns True if amount validation passed
85
- */
86
- private validateAmounts(): boolean {
87
- if (!this.doc) return false;
88
-
89
- try {
90
- // Extract amounts
91
- const totalAmount = this.getNumber(
92
- '//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:GrandTotalAmount'
93
- );
94
-
95
- const paidAmount = this.getNumber(
96
- '//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:TotalPrepaidAmount'
97
- ) || 0;
98
-
99
- const dueAmount = this.getNumber(
100
- '//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:DuePayableAmount'
101
- );
102
-
103
- // Calculate expected due amount
104
- const expectedDueAmount = totalAmount - paidAmount;
105
-
106
- // Compare with a small tolerance for rounding errors
107
- if (Math.abs(dueAmount - expectedDueAmount) > 0.01) {
108
- this.addError(
109
- 'BR-16',
110
- `Amount due for payment (${dueAmount}) must equal Invoice total amount with VAT (${totalAmount}) - Paid amount (${paidAmount})`,
111
- '//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation'
112
- );
113
- return false;
114
- }
115
-
116
- return true;
117
- } catch (error) {
118
- this.addError('FX-AMOUNT', `Error validating amounts: ${error}`, '/');
119
- return false;
120
- }
121
- }
122
-
123
85
  /**
124
86
  * Validates mutually exclusive fields
125
87
  * @returns True if validation passed
@@ -8,6 +8,7 @@ import type {
8
8
  import { business, finance } from '../../../plugins.js';
9
9
  import { EN16931Validator } from '../../validation/en16931.validator.js';
10
10
  import { getPrecedingInvoiceFields } from '../../utils/preceding.invoice.js';
11
+ import { readPaidAmount } from '../../utils/paid.amount.js';
11
12
 
12
13
  /**
13
14
  * Decoder for ZUGFeRD invoice format
@@ -87,6 +88,14 @@ export class ZUGFeRDDecoder extends CIIBaseDecoder {
87
88
  // Reverse charge: every line has the VAT category AE
88
89
  const reverseCharge = this.isReverseChargeDocument();
89
90
 
91
+ // the invoicing period (BG-14) and the seller electronic address (BT-34), when stated
92
+ const invoicingPeriod = this.invoicingPeriodFields(this.extractInvoicingPeriod());
93
+ // the buyer reference (BT-10), when stated
94
+ const buyerReference = this.extractBuyerReference();
95
+ const electronicAddress = this.extractSellerElectronicAddress();
96
+
97
+ const paidAmount = readPaidAmount(this.getText('//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:TotalPrepaidAmount'), 'zugferd');
98
+
90
99
  // Create the common invoice data
91
100
  const invoiceData = {
92
101
  type: 'accounting-doc' as const,
@@ -113,8 +122,15 @@ export class ZUGFeRDDecoder extends CIIBaseDecoder {
113
122
  notes: notes,
114
123
  // the day of supply as the document states it (BT-72)
115
124
  ...(deliveryDate === undefined ? {} : { deliveryDate }),
125
+ ...invoicingPeriod,
126
+ // the specification identifier (BT-24), with the metadata of a one-date period
127
+ ...this.metadataFields(invoicingPeriod),
128
+ ...(buyerReference === undefined ? {} : { buyerReference }),
129
+ ...(electronicAddress === undefined ? {} : { electronicAddress }),
116
130
  objectActions: [],
117
131
  ...getPrecedingInvoiceFields(accountingDocType, this.extractPrecedingInvoiceReferences()),
132
+ // the paid amount (BT-113); the amount due (BT-115) follows from it and the totals
133
+ ...(paidAmount === undefined ? {} : { paidAmount }),
118
134
  };
119
135
 
120
136
  // Validate mandatory EN16931 fields unless validation is skipped
@@ -166,6 +182,9 @@ export class ZUGFeRDDecoder extends CIIBaseDecoder {
166
182
  // the tax registration identifier (BT-32 for the seller), the Steuernummer: scheme FC
167
183
  const taxNumber = this.getText(`${partyXPath}/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="FC"]`) || '';
168
184
 
185
+ // the party identifiers (BT-29 seller, BT-46 buyer)
186
+ const additionalIdentifiers = this.extractPartyIdentifiers(partyXPath);
187
+
169
188
  // Create contact object
170
189
  return {
171
190
  type: 'company',
@@ -179,7 +198,8 @@ export class ZUGFeRDDecoder extends CIIBaseDecoder {
179
198
  registrationId: registrationId,
180
199
  registrationName: '',
181
200
  ...(taxNumber ? { taxNumber } : {})
182
- }
201
+ },
202
+ ...(additionalIdentifiers.length > 0 ? { additionalIdentifiers } : {})
183
203
  } as business.TContact;
184
204
  }
185
205
 
@@ -1,12 +1,11 @@
1
1
  import { CIIBaseEncoder } from '../cii.encoder.js';
2
2
  import type { TAccountingDoc, TCreditNote, TInvoiceDocument } from '../../../interfaces/common.js';
3
- import { ZUGFERD_PROFILE_IDS } from './zugferd.types.js';
4
- import { CIIProfile } from '../cii.types.js';
5
3
  import { DOMParser, XMLSerializer } from '../../../plugins.js';
6
4
  import { getDocumentTypeCode } from '../../utils/document.typecode.js';
7
- import { getWritableDate, getWritableDueDate } from '../../utils/date.value.js';
5
+ import { getWritableDueDate } from '../../utils/date.value.js';
8
6
  import { getPaymentTermsNote } from '../../utils/payment.terms.js';
9
7
  import { computeDocumentTotals } from '../../utils/document.totals.js';
8
+ import { getPaymentTotals } from '../../utils/paid.amount.js';
10
9
  import { toPlainDecimalString } from '../../utils/number.text.js';
11
10
  import { resolveCountryCode } from '../../utils/country.code.js';
12
11
 
@@ -14,12 +13,6 @@ import { resolveCountryCode } from '../../utils/country.code.js';
14
13
  * Encoder for ZUGFeRD invoice format
15
14
  */
16
15
  export class ZUGFeRDEncoder extends CIIBaseEncoder {
17
- constructor() {
18
- super();
19
- // Set default profile to BASIC
20
- this.profile = CIIProfile.BASIC;
21
- }
22
-
23
16
  /**
24
17
  * Encodes a credit note into ZUGFeRD XML
25
18
  * @param creditNote Credit note to encode
@@ -101,15 +94,7 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
101
94
  // Add ID element with profile
102
95
  const idElement = doc.createElement('ram:ID');
103
96
 
104
- // Set profile based on the selected profile
105
- let profileId = ZUGFERD_PROFILE_IDS.BASIC;
106
- if (this.profile === CIIProfile.COMFORT) {
107
- profileId = ZUGFERD_PROFILE_IDS.COMFORT;
108
- } else if (this.profile === CIIProfile.EXTENDED) {
109
- profileId = ZUGFERD_PROFILE_IDS.EXTENDED;
110
- }
111
-
112
- idElement.textContent = profileId;
97
+ idElement.textContent = this.getProfileId();
113
98
  guidelineElement.appendChild(idElement);
114
99
  }
115
100
 
@@ -216,12 +201,8 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
216
201
  const agreementElement = doc.createElement('ram:ApplicableHeaderTradeAgreement');
217
202
  transactionElement.appendChild(agreementElement);
218
203
 
219
- // Add buyer reference if available
220
- if (invoice.buyerReference) {
221
- const buyerRefElement = doc.createElement('ram:BuyerReference');
222
- buyerRefElement.textContent = invoice.buyerReference;
223
- agreementElement.appendChild(buyerRefElement);
224
- }
204
+ // Buyer reference (BT-10)
205
+ this.addBuyerReference(doc, agreementElement, invoice);
225
206
 
226
207
  // Add seller
227
208
  const sellerElement = doc.createElement('ram:SellerTradeParty');
@@ -229,14 +210,7 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
229
210
 
230
211
  // Seller electronic address (BT-34): ram:URIUniversalCommunication/ram:URIID of the party; a
231
212
  // trade contact (ram:DefinedTradeContact) has no URIID
232
- if (invoice.electronicAddress && invoice.from.type === 'company') {
233
- const communicationElement = doc.createElement('ram:URIUniversalCommunication');
234
- const uriElement = doc.createElement('ram:URIID');
235
- uriElement.setAttribute('schemeID', invoice.electronicAddress.scheme);
236
- uriElement.textContent = invoice.electronicAddress.value;
237
- communicationElement.appendChild(uriElement);
238
- sellerElement.appendChild(communicationElement);
239
- }
213
+ this.addSellerElectronicAddress(doc, sellerElement, invoice);
240
214
 
241
215
  agreementElement.appendChild(sellerElement);
242
216
 
@@ -254,6 +228,9 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
254
228
  * @param partyRole Whether the party is the seller or the buyer
255
229
  */
256
230
  private addPartyInfo(doc: Document, partyElement: Element, party: any, partyRole: 'seller' | 'buyer'): void {
231
+ // Party identifiers (BT-29 seller, BT-46 buyer)
232
+ this.addPartyIdentifiers(doc, partyElement, party);
233
+
257
234
  // Add name
258
235
  const nameElement = doc.createElement('ram:Name');
259
236
  nameElement.textContent = party.name;
@@ -442,31 +419,7 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
442
419
  this.addTaxDetails(doc, settlementElement, invoice);
443
420
 
444
421
  // Invoicing period (BG-14): in the header settlement, where the CII schema puts it
445
- if (invoice.periodOfPerformance) {
446
- const periodElement = doc.createElement('ram:BillingSpecifiedPeriod');
447
-
448
- // Start date
449
- if (invoice.periodOfPerformance.from !== undefined) {
450
- const startDateElement = doc.createElement('ram:StartDateTime');
451
- const startDateStringElement = doc.createElement('udt:DateTimeString');
452
- startDateStringElement.setAttribute('format', '102'); // YYYYMMDD format
453
- startDateStringElement.textContent = this.formatDateYYYYMMDD(invoice.periodOfPerformance.from, 'BT-73 invoicing period start date');
454
- startDateElement.appendChild(startDateStringElement);
455
- periodElement.appendChild(startDateElement);
456
- }
457
-
458
- // End date
459
- if (invoice.periodOfPerformance.to !== undefined) {
460
- const endDateElement = doc.createElement('ram:EndDateTime');
461
- const endDateStringElement = doc.createElement('udt:DateTimeString');
462
- endDateStringElement.setAttribute('format', '102'); // YYYYMMDD format
463
- endDateStringElement.textContent = this.formatDateYYYYMMDD(invoice.periodOfPerformance.to, 'BT-74 invoicing period end date');
464
- endDateElement.appendChild(endDateStringElement);
465
- periodElement.appendChild(endDateElement);
466
- }
467
-
468
- settlementElement.appendChild(periodElement);
469
- }
422
+ this.addInvoicingPeriod(doc, settlementElement, invoice);
470
423
 
471
424
  // Add totals
472
425
  this.addMonetarySummation(doc, settlementElement, invoice);
@@ -554,7 +507,11 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
554
507
  appendAmount('ram:TaxBasisTotalAmount', totals.taxBasisTotal); // BT-109
555
508
  appendAmount('ram:TaxTotalAmount', totals.taxTotal, true); // BT-110
556
509
  appendAmount('ram:GrandTotalAmount', totals.grandTotal); // BT-112
557
- appendAmount('ram:DuePayableAmount', totals.duePayable); // BT-115
510
+ const payment = getPaymentTotals(invoice, totals);
511
+ if (payment.paidAmount) {
512
+ appendAmount('ram:TotalPrepaidAmount', payment.paidAmount); // BT-113
513
+ }
514
+ appendAmount('ram:DuePayableAmount', payment.duePayable); // BT-115
558
515
 
559
516
  settlementElement.appendChild(monetarySummationElement);
560
517
  }
@@ -658,18 +615,4 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
658
615
  }
659
616
  }
660
617
  }
661
-
662
- /**
663
- * Formats a date as YYYYMMDD
664
- * @param timestamp Timestamp to format
665
- * @returns Formatted date string
666
- */
667
- private formatDateYYYYMMDD(timestamp: number, field: string): string {
668
- const date = getWritableDate(timestamp, field, 'cii');
669
- // the UTC fields: a calendar day is its UTC midnight, whatever the server's zone
670
- const year = date.getUTCFullYear();
671
- const month = (date.getUTCMonth() + 1).toString().padStart(2, '0');
672
- const day = date.getUTCDate().toString().padStart(2, '0');
673
- return `${year}${month}${day}`;
674
- }
675
- }
618
+ }
@@ -1,16 +1,7 @@
1
- import { CIIProfile, CII_PROFILE_IDS } from '../cii.types.js';
2
-
3
1
  /**
4
2
  * ZUGFeRD specific constants and types
5
3
  */
6
4
 
7
- // ZUGFeRD profile IDs
8
- export const ZUGFERD_PROFILE_IDS = {
9
- BASIC: CII_PROFILE_IDS.ZUGFERD_BASIC,
10
- COMFORT: CII_PROFILE_IDS.ZUGFERD_COMFORT,
11
- EXTENDED: CII_PROFILE_IDS.ZUGFERD_EXTENDED
12
- };
13
-
14
5
  // ZUGFeRD PDF attachment filename
15
6
  export const ZUGFERD_ATTACHMENT_FILENAME = 'zugferd-invoice.xml';
16
7
 
@@ -1,4 +1,5 @@
1
1
  import { CIIBaseDecoder } from '../cii.decoder.js';
2
+ import type { ICIIInvoicingPeriod } from '../cii.decoder.js';
2
3
  import type {
3
4
  TAccountingDocEnvelope,
4
5
  TAccountingDocType,
@@ -8,6 +9,8 @@ import type {
8
9
  import { ZUGFERD_V1_NAMESPACES } from '../cii.types.js';
9
10
  import { business, finance, xpath } from '../../../plugins.js';
10
11
  import { EN16931Validator } from '../../validation/en16931.validator.js';
12
+ import { readPaidAmount } from '../../utils/paid.amount.js';
13
+ import type { IEInvoiceStatedValues } from '../../../interfaces/stated.values.js';
11
14
 
12
15
  /** XPath selection with the ZUGFeRD v1 namespaces */
13
16
  const zugferdV1Select = xpath.useNamespaces({
@@ -68,6 +71,38 @@ export class ZUGFeRDV1Decoder extends CIIBaseDecoder {
68
71
  return dueDate ? this.parseRequiredCIIDate(dueDate, this.v1Text(`${dueDatePath}/@format`)) : undefined;
69
72
  }
70
73
 
74
+ /**
75
+ * Reads the buyer reference of the v1 header trade agreement
76
+ * (`ram:ApplicableSupplyChainTradeAgreement/ram:BuyerReference`); undefined
77
+ * when the document states none.
78
+ */
79
+ protected override extractBuyerReference(): string | undefined {
80
+ return (
81
+ this.v1Text('/rsm:CrossIndustryDocument/rsm:SpecifiedSupplyChainTradeTransaction/ram:ApplicableSupplyChainTradeAgreement/ram:BuyerReference') ||
82
+ undefined
83
+ );
84
+ }
85
+
86
+ /**
87
+ * Reads the identifier of the v1 document context
88
+ * (`rsm:SpecifiedExchangedDocumentContext/ram:GuidelineSpecifiedDocumentContextParameter/ram:ID`);
89
+ * undefined when the document states none.
90
+ */
91
+ protected override extractSpecificationIdentifier(): string | undefined {
92
+ return (
93
+ this.v1Text('/rsm:CrossIndustryDocument/rsm:SpecifiedExchangedDocumentContext/ram:GuidelineSpecifiedDocumentContextParameter/ram:ID') ||
94
+ undefined
95
+ );
96
+ }
97
+
98
+ /**
99
+ * ZUGFeRD 1.0 predates EN 16931 and has no business terms to read the stated
100
+ * values by; a v1 document has none
101
+ */
102
+ protected override readStatedValues(): IEInvoiceStatedValues | undefined {
103
+ return undefined;
104
+ }
105
+
71
106
  /** The trimmed text of the first node a v1 path selects, or '' */
72
107
  private v1Text(path: string): string {
73
108
  return String(zugferdV1Select(`string(${path})`, this.doc)).trim();
@@ -89,6 +124,19 @@ export class ZUGFeRDV1Decoder extends CIIBaseDecoder {
89
124
  return this.parseRequiredCIIDate(deliveryDate, format);
90
125
  }
91
126
 
127
+ /**
128
+ * Reads the invoicing period of the v1 header settlement
129
+ * (`ram:ApplicableSupplyChainTradeSettlement/ram:BillingSpecifiedPeriod`).
130
+ * A ZUGFeRD 1.0 trade party has no electronic address, so a v1 document has
131
+ * no seller electronic address (BT-34).
132
+ */
133
+ protected override extractInvoicingPeriod(): ICIIInvoicingPeriod {
134
+ return this.readInvoicingPeriod(
135
+ '/rsm:CrossIndustryDocument/rsm:SpecifiedSupplyChainTradeTransaction/ram:ApplicableSupplyChainTradeSettlement/ram:BillingSpecifiedPeriod',
136
+ (path) => this.v1Text(path),
137
+ );
138
+ }
139
+
92
140
  /**
93
141
  * Decodes a ZUGFeRD v1 credit note
94
142
  * @returns Promise resolving to a TCreditNote object
@@ -163,6 +211,19 @@ export class ZUGFeRDV1Decoder extends CIIBaseDecoder {
163
211
  // Reverse charge: every line has the VAT category AE
164
212
  const reverseCharge = this.isReverseChargeDocument();
165
213
 
214
+ // the invoicing period (BG-14), when stated
215
+ const invoicingPeriod = this.invoicingPeriodFields(this.extractInvoicingPeriod());
216
+ // the buyer reference (BT-10), when stated
217
+ const buyerReference = this.extractBuyerReference();
218
+
219
+ // the paid amount (BT-113) of the v1 header settlement, read with the v1 namespaces
220
+ const paidAmount = readPaidAmount(
221
+ this.v1Text(
222
+ '/rsm:CrossIndustryDocument/rsm:SpecifiedSupplyChainTradeTransaction/ram:ApplicableSupplyChainTradeSettlement/ram:SpecifiedTradeSettlementMonetarySummation/ram:TotalPrepaidAmount',
223
+ ),
224
+ 'zugferd',
225
+ );
226
+
166
227
  // Create the common invoice data
167
228
  const invoiceData = {
168
229
  type: 'accounting-doc' as const,
@@ -189,6 +250,11 @@ export class ZUGFeRDV1Decoder extends CIIBaseDecoder {
189
250
  notes: notes,
190
251
  // the day of supply as the document states it (BT-72), never the issue date in its place
191
252
  ...(deliveryDate === undefined ? {} : { deliveryDate }),
253
+ ...invoicingPeriod,
254
+ // the specification identifier (BT-24), with the metadata of a one-date period
255
+ ...this.metadataFields(invoicingPeriod),
256
+ ...(buyerReference === undefined ? {} : { buyerReference }),
257
+ ...(paidAmount === undefined ? {} : { paidAmount }),
192
258
  objectActions: []
193
259
  };
194
260
 
@@ -24,9 +24,13 @@ export class ZUGFeRDValidator extends CIIBaseValidator {
24
24
  * @returns True if business validation passed
25
25
  */
26
26
  protected validateBusinessRules(): boolean {
27
+ // BR-CO-10 to BR-CO-17: the stated totals, VAT breakdown and line amounts agree with each other
28
+ const totalsAgree = this.validateStatedTotals();
29
+ // BR-CO-26: the seller states a seller, legal registration or VAT identifier
30
+ const sellerIdentified = this.validateSellerIdentifier();
27
31
  // BR-23: every line has a unit code
28
32
  const linesHaveUnits = this.validateLineUnitCodePresence();
29
33
  // BR-CL-23: unit codes of UN/ECE Recommendation 20 with the Recommendation 21 extension
30
- return this.validateUnitCodes() && linesHaveUnits;
34
+ return this.validateUnitCodes() && linesHaveUnits && sellerIdentified && totalsAgree;
31
35
  }
32
36
  }
@@ -5,6 +5,8 @@
5
5
 
6
6
  import { EInvoice } from '../../einvoice.js';
7
7
  import { getAccountingDocType, getDocumentTypeCode } from '../utils/document.typecode.js';
8
+ import { computeDocumentTotals } from '../utils/document.totals.js';
9
+ import { getPaymentTotals } from '../utils/paid.amount.js';
8
10
  import type {
9
11
  EN16931SemanticModel,
10
12
  Seller,
@@ -43,14 +45,15 @@ export class SemanticModelAdapter {
43
45
  },
44
46
 
45
47
  // Process metadata
46
- processControl: invoice.metadata?.profileId ? {
47
- businessProcessType: invoice.metadata?.extensions?.businessProcessId,
48
- specificationIdentifier: invoice.metadata.profileId
48
+ // the specification identifier (BT-24) is customizationId, the business process type (BT-23) profileId
49
+ processControl: invoice.metadata?.customizationId ? {
50
+ ...(invoice.metadata.profileId ? { businessProcessType: invoice.metadata.profileId } : {}),
51
+ specificationIdentifier: invoice.metadata.customizationId
49
52
  } : undefined,
50
53
 
51
54
  // References
52
55
  references: {
53
- buyerReference: invoice.metadata?.buyerReference,
56
+ buyerReference: invoice.buyerReference,
54
57
  projectReference: invoice.metadata?.extensions?.projectReference,
55
58
  contractReference: invoice.metadata?.extensions?.contractReference,
56
59
  purchaseOrderReference: invoice.metadata?.extensions?.purchaseOrderReference,
@@ -132,24 +135,27 @@ export class SemanticModelAdapter {
132
135
  invoice.from = this.reverseMapSeller(model.seller);
133
136
  invoice.to = this.reverseMapBuyer(model.buyer);
134
137
  invoice.items = this.reverseMapInvoiceLines(model.invoiceLines);
138
+ // the paid amount (BT-113); a stated zero is nothing paid
139
+ if (model.documentTotals?.prepaidAmount) {
140
+ invoice.paidAmount = model.documentTotals.prepaidAmount;
141
+ }
135
142
 
136
143
  // Set metadata
137
144
  if (model.processControl) {
138
145
  invoice.metadata = {
139
146
  ...invoice.metadata,
140
- profileId: model.processControl.specificationIdentifier,
141
- extensions: {
142
- ...invoice.metadata?.extensions,
143
- businessProcessId: model.processControl.businessProcessType
144
- }
147
+ customizationId: model.processControl.specificationIdentifier,
148
+ ...(model.processControl.businessProcessType ? { profileId: model.processControl.businessProcessType } : {}),
145
149
  };
146
150
  }
147
151
 
148
- // Set references
152
+ // Set references; the buyer reference (BT-10) is the envelope's
149
153
  if (model.references) {
154
+ if (model.references.buyerReference) {
155
+ invoice.buyerReference = model.references.buyerReference;
156
+ }
150
157
  invoice.metadata = {
151
158
  ...invoice.metadata,
152
- buyerReference: model.references.buyerReference,
153
159
  extensions: {
154
160
  ...invoice.metadata?.extensions,
155
161
  contractReference: model.references.contractReference,
@@ -251,8 +257,8 @@ export class SemanticModelAdapter {
251
257
  tradingName: contact.tradingName,
252
258
  identifier: contact.registrationDetails?.registrationId,
253
259
  legalRegistrationIdentifier: contact.registrationDetails?.registrationId,
254
- vatIdentifier: contact.registrationDetails?.vatId || contact.vatNumber,
255
- taxRegistrationIdentifier: contact.taxId,
260
+ vatIdentifier: contact.registrationDetails?.vatId || undefined,
261
+ taxRegistrationIdentifier: contact.registrationDetails?.taxNumber || undefined,
256
262
  additionalLegalInfo: contact.description,
257
263
  electronicAddress: contact.email || contact.contact?.email
258
264
  };
@@ -260,7 +266,8 @@ export class SemanticModelAdapter {
260
266
  return {
261
267
  name: contact.name || `${contact.firstName || ''} ${contact.lastName || ''}`.trim(),
262
268
  identifier: contact.registrationDetails?.registrationId,
263
- vatIdentifier: contact.registrationDetails?.vatId || contact.vatNumber,
269
+ vatIdentifier: contact.registrationDetails?.vatId || undefined,
270
+ taxRegistrationIdentifier: contact.registrationDetails?.taxNumber || undefined,
264
271
  electronicAddress: contact.email
265
272
  };
266
273
  }
@@ -277,14 +284,14 @@ export class SemanticModelAdapter {
277
284
  tradingName: contact.tradingName,
278
285
  identifier: contact.registrationDetails?.registrationId,
279
286
  legalRegistrationIdentifier: contact.registrationDetails?.registrationId,
280
- vatIdentifier: contact.registrationDetails?.vatId || contact.vatNumber,
287
+ vatIdentifier: contact.registrationDetails?.vatId || undefined,
281
288
  electronicAddress: contact.email || contact.contact?.email
282
289
  };
283
290
  } else {
284
291
  return {
285
292
  name: contact.name || `${contact.firstName || ''} ${contact.lastName || ''}`.trim(),
286
293
  identifier: contact.registrationDetails?.registrationId,
287
- vatIdentifier: contact.registrationDetails?.vatId || contact.vatNumber,
294
+ vatIdentifier: contact.registrationDetails?.vatId || undefined,
288
295
  electronicAddress: contact.email
289
296
  };
290
297
  }
@@ -405,9 +412,10 @@ export class SemanticModelAdapter {
405
412
  chargeTotalAmount: invoice.metadata?.extensions?.documentCharges?.reduce(
406
413
  (sum: number, c: { amount: number }) => sum + c.amount, 0
407
414
  ),
408
- prepaidAmount: invoice.metadata?.extensions?.prepaidAmount,
415
+ // the paid amount (BT-113) of the envelope; the amount due (BT-115) is the gross total less it
416
+ prepaidAmount: invoice.paidAmount,
409
417
  roundingAmount: invoice.metadata?.extensions?.roundingAmount,
410
- payableAmount: invoice.totalGross
418
+ payableAmount: getPaymentTotals(invoice, computeDocumentTotals(invoice)).duePayable.toNumber()
411
419
  };
412
420
  }
413
421
 
@@ -502,7 +510,8 @@ export class SemanticModelAdapter {
502
510
  registrationDetails: {
503
511
  vatId: seller.vatIdentifier || '',
504
512
  registrationId: seller.identifier || seller.legalRegistrationIdentifier || '',
505
- registrationName: seller.name
513
+ registrationName: seller.name,
514
+ ...(seller.taxRegistrationIdentifier ? { taxNumber: seller.taxRegistrationIdentifier } : {})
506
515
  },
507
516
  status: 'active',
508
517
  foundedDate: {