@fin.cx/einvoice 7.1.0 → 8.1.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 (90) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/einvoice.d.ts +10 -9
  3. package/dist_ts/einvoice.js +40 -10
  4. package/dist_ts/formats/base/base.decoder.d.ts +6 -6
  5. package/dist_ts/formats/base/base.decoder.js +1 -1
  6. package/dist_ts/formats/base/base.encoder.d.ts +4 -4
  7. package/dist_ts/formats/base/base.validator.d.ts +5 -0
  8. package/dist_ts/formats/base/base.validator.js +28 -1
  9. package/dist_ts/formats/cii/cii.decoder.d.ts +8 -7
  10. package/dist_ts/formats/cii/cii.decoder.js +8 -9
  11. package/dist_ts/formats/cii/cii.encoder.d.ts +7 -7
  12. package/dist_ts/formats/cii/cii.encoder.js +5 -5
  13. package/dist_ts/formats/cii/cii.validator.d.ts +11 -0
  14. package/dist_ts/formats/cii/cii.validator.js +35 -1
  15. package/dist_ts/formats/cii/facturx/facturx.decoder.d.ts +6 -4
  16. package/dist_ts/formats/cii/facturx/facturx.decoder.js +14 -21
  17. package/dist_ts/formats/cii/facturx/facturx.encoder.d.ts +5 -5
  18. package/dist_ts/formats/cii/facturx/facturx.encoder.js +14 -10
  19. package/dist_ts/formats/cii/facturx/facturx.validator.js +8 -4
  20. package/dist_ts/formats/cii/zugferd/zugferd.decoder.d.ts +6 -4
  21. package/dist_ts/formats/cii/zugferd/zugferd.decoder.js +14 -21
  22. package/dist_ts/formats/cii/zugferd/zugferd.encoder.d.ts +4 -4
  23. package/dist_ts/formats/cii/zugferd/zugferd.encoder.js +13 -9
  24. package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.d.ts +6 -4
  25. package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.js +14 -21
  26. package/dist_ts/formats/cii/zugferd/zugferd.validator.js +5 -4
  27. package/dist_ts/formats/converters/xml-to-einvoice.converter.d.ts +5 -0
  28. package/dist_ts/formats/converters/xml-to-einvoice.converter.js +11 -2
  29. package/dist_ts/formats/semantic/semantic.adapter.d.ts +0 -8
  30. package/dist_ts/formats/semantic/semantic.adapter.js +5 -34
  31. package/dist_ts/formats/ubl/en16931.ubl.validator.js +5 -1
  32. package/dist_ts/formats/ubl/generic/ubl.encoder.d.ts +4 -4
  33. package/dist_ts/formats/ubl/generic/ubl.encoder.js +13 -14
  34. package/dist_ts/formats/ubl/ubl.decoder.d.ts +8 -7
  35. package/dist_ts/formats/ubl/ubl.decoder.js +11 -8
  36. package/dist_ts/formats/ubl/ubl.encoder.d.ts +7 -7
  37. package/dist_ts/formats/ubl/ubl.encoder.js +5 -5
  38. package/dist_ts/formats/ubl/ubl.validator.d.ts +11 -0
  39. package/dist_ts/formats/ubl/ubl.validator.js +35 -1
  40. package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.d.ts +6 -4
  41. package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.js +14 -21
  42. package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.d.ts +4 -4
  43. package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.js +6 -6
  44. package/dist_ts/formats/utils/document.typecode.d.ts +18 -0
  45. package/dist_ts/formats/utils/document.typecode.js +52 -0
  46. package/dist_ts/formats/utils/unit.codes.d.ts +40 -0
  47. package/dist_ts/formats/utils/unit.codes.js +193 -0
  48. package/dist_ts/formats/validation/codelist.validator.d.ts +1 -1
  49. package/dist_ts/formats/validation/codelist.validator.js +10 -12
  50. package/dist_ts/formats/validation/en16931.business-rules.validator.js +13 -13
  51. package/dist_ts/formats/validation/validation.types.d.ts +0 -4
  52. package/dist_ts/formats/validation/validation.types.js +1 -28
  53. package/dist_ts/index.d.ts +7 -1
  54. package/dist_ts/index.js +11 -1
  55. package/dist_ts/interfaces/common.d.ts +11 -0
  56. package/dist_ts_install/download-schematron.js +3 -5
  57. package/package.json +7 -7
  58. package/readme.md +99 -2
  59. package/ts/00_commitinfo_data.ts +1 -1
  60. package/ts/einvoice.ts +59 -18
  61. package/ts/formats/base/base.decoder.ts +6 -6
  62. package/ts/formats/base/base.encoder.ts +4 -4
  63. package/ts/formats/base/base.validator.ts +28 -0
  64. package/ts/formats/cii/cii.decoder.ts +18 -14
  65. package/ts/formats/cii/cii.encoder.ts +11 -11
  66. package/ts/formats/cii/cii.validator.ts +47 -0
  67. package/ts/formats/cii/facturx/facturx.decoder.ts +23 -23
  68. package/ts/formats/cii/facturx/facturx.encoder.ts +20 -16
  69. package/ts/formats/cii/facturx/facturx.validator.ts +9 -3
  70. package/ts/formats/cii/zugferd/zugferd.decoder.ts +23 -23
  71. package/ts/formats/cii/zugferd/zugferd.encoder.ts +21 -17
  72. package/ts/formats/cii/zugferd/zugferd.v1.decoder.ts +23 -23
  73. package/ts/formats/cii/zugferd/zugferd.validator.ts +4 -3
  74. package/ts/formats/converters/xml-to-einvoice.converter.ts +11 -1
  75. package/ts/formats/semantic/semantic.adapter.ts +4 -35
  76. package/ts/formats/ubl/en16931.ubl.validator.ts +6 -0
  77. package/ts/formats/ubl/generic/ubl.encoder.ts +29 -24
  78. package/ts/formats/ubl/ubl.decoder.ts +23 -14
  79. package/ts/formats/ubl/ubl.encoder.ts +11 -11
  80. package/ts/formats/ubl/ubl.validator.ts +47 -0
  81. package/ts/formats/ubl/xrechnung/xrechnung.decoder.ts +23 -23
  82. package/ts/formats/ubl/xrechnung/xrechnung.encoder.ts +10 -10
  83. package/ts/formats/utils/document.typecode.ts +62 -0
  84. package/ts/formats/utils/unit.codes.ts +215 -0
  85. package/ts/formats/validation/codelist.validator.ts +12 -23
  86. package/ts/formats/validation/en16931.business-rules.validator.ts +13 -13
  87. package/ts/formats/validation/validation.types.ts +0 -28
  88. package/ts/index.ts +25 -0
  89. package/ts/interfaces/common.ts +17 -0
  90. package/ts/readme.md +3 -2
@@ -1,8 +1,9 @@
1
1
  import { BaseDecoder } from '../base/base.decoder.js';
2
- import type { TInvoice, TCreditNote, TDebitNote } from '../../interfaces/common.js';
2
+ import type { TAccountingDoc, TCreditNote, TInvoiceDocument } from '../../interfaces/common.js';
3
3
  import { CII_NAMESPACES, CIIProfile } from './cii.types.js';
4
4
  import { DOMParser, xpath } from '../../plugins.js';
5
5
  import { EInvoiceParsingError } from '../../errors.js';
6
+ import { getAccountingDocType } from '../utils/document.typecode.js';
6
7
 
7
8
  const ciiParser = new DOMParser();
8
9
  const ciiNamespaces = {
@@ -42,18 +43,18 @@ export abstract class CIIBaseDecoder extends BaseDecoder {
42
43
  }
43
44
 
44
45
  /**
45
- * Decodes CII XML into a TInvoice object
46
- * @returns Promise resolving to a TInvoice object
46
+ * Decodes CII XML into an accounting document
47
+ * @returns Promise resolving to the accounting document
47
48
  */
48
- public async decode(): Promise<TInvoice> {
49
- // Determine if it's a credit note or debit note based on type code
50
- const typeCode = this.getText('//ram:TypeCode');
51
-
52
- if (typeCode === '381') { // Credit note type code
53
- return this.decodeCreditNote() as unknown as TInvoice;
54
- } else {
55
- return this.decodeDebitNote() as unknown as TInvoice;
49
+ public async decode(): Promise<TAccountingDoc> {
50
+ // CII has one root for every document; the document type code (BT-3) alone tells them apart
51
+ const accountingDocType = getAccountingDocType(
52
+ this.getText('/rsm:CrossIndustryInvoice/rsm:ExchangedDocument/ram:TypeCode'),
53
+ );
54
+ if (accountingDocType === 'creditnote') {
55
+ return this.decodeCreditNote();
56
56
  }
57
+ return this.decodeInvoice(accountingDocType);
57
58
  }
58
59
 
59
60
  /**
@@ -90,10 +91,13 @@ export abstract class CIIBaseDecoder extends BaseDecoder {
90
91
  protected abstract decodeCreditNote(): Promise<TCreditNote>;
91
92
 
92
93
  /**
93
- * Decodes a CII debit note (invoice)
94
- * @returns Promise resolving to a TDebitNote object
94
+ * Decodes a CII invoice document: an invoice, a debit note or a self-billed invoice
95
+ * @param accountingDocType The type the document type code stands for
96
+ * @returns Promise resolving to the invoice document
95
97
  */
96
- protected abstract decodeDebitNote(): Promise<TDebitNote>;
98
+ protected abstract decodeInvoice(
99
+ accountingDocType: TInvoiceDocument['accountingDocType'],
100
+ ): Promise<TInvoiceDocument>;
97
101
 
98
102
  /**
99
103
  * Gets a text value from an XPath expression
@@ -1,5 +1,5 @@
1
1
  import { BaseEncoder } from '../base/base.encoder.js';
2
- import type { TInvoice, TCreditNote, TDebitNote } from '../../interfaces/common.js';
2
+ import type { TAccountingDoc, TCreditNote, TInvoiceDocument } from '../../interfaces/common.js';
3
3
  import { CII_NAMESPACES, CIIProfile } from './cii.types.js';
4
4
 
5
5
  /**
@@ -17,16 +17,16 @@ export abstract class CIIBaseEncoder extends BaseEncoder {
17
17
  }
18
18
 
19
19
  /**
20
- * Encodes a TInvoice object into CII XML
21
- * @param invoice TInvoice object to encode
20
+ * Encodes an accounting document into CII XML
21
+ * @param invoice Accounting document to encode
22
22
  * @returns CII XML string
23
23
  */
24
- public async encode(invoice: TInvoice): Promise<string> {
25
- // a credit note takes the credit-note path (type code 381); everything else is encoded as an invoice
26
- if ((invoice as { accountingDocType?: string }).accountingDocType === 'creditnote') {
27
- return this.encodeCreditNote(invoice as unknown as TCreditNote);
24
+ public async encode(invoice: TAccountingDoc): Promise<string> {
25
+ // CII has one root for every document; the two paths differ in the type code they write
26
+ if (invoice.accountingDocType === 'creditnote') {
27
+ return this.encodeCreditNote(invoice);
28
28
  }
29
- return this.encodeDebitNote(invoice as unknown as TDebitNote);
29
+ return this.encodeInvoice(invoice);
30
30
  }
31
31
 
32
32
  /**
@@ -37,11 +37,11 @@ export abstract class CIIBaseEncoder extends BaseEncoder {
37
37
  protected abstract encodeCreditNote(creditNote: TCreditNote): Promise<string>;
38
38
 
39
39
  /**
40
- * Encodes a TDebitNote object into CII XML
41
- * @param debitNote TDebitNote object to encode
40
+ * Encodes an invoice document (invoice, debit note or self-billed invoice) into CII XML
41
+ * @param invoice Invoice document to encode
42
42
  * @returns CII XML string
43
43
  */
44
- protected abstract encodeDebitNote(debitNote: TDebitNote): Promise<string>;
44
+ protected abstract encodeInvoice(invoice: TInvoiceDocument): Promise<string>;
45
45
 
46
46
  /**
47
47
  * Creates the XML declaration and root element
@@ -3,6 +3,7 @@ import { ValidationLevel } from '../../interfaces/common.js';
3
3
  import type { ValidationResult } from '../../interfaces/common.js';
4
4
  import { CII_NAMESPACES, CIIProfile } from './cii.types.js';
5
5
  import { DOMParser, xpath } from '../../plugins.js';
6
+ import { meetsUnitCodeRule } from '../utils/unit.codes.js';
6
7
 
7
8
  const ciiValidatorParser = new DOMParser();
8
9
  const ciiValidatorNamespaces = {
@@ -113,6 +114,52 @@ export abstract class CIIBaseValidator extends BaseValidator {
113
114
  */
114
115
  protected abstract validateStructure(): boolean;
115
116
 
117
+ /**
118
+ * BR-23: every invoice line has a unit code on its billed quantity.
119
+ * @returns True if every line has one
120
+ */
121
+ protected validateLineUnitCodePresence(): boolean {
122
+ // BR-23: every invoice line (BG-25) has an invoiced quantity unit of measure code (BT-130)
123
+ const lines = this.select('//ram:IncludedSupplyChainTradeLineItem', this.doc) as Element[];
124
+ let valid = true;
125
+ for (const line of lines) {
126
+ if (!this.exists('./ram:SpecifiedLineTradeDelivery/ram:BilledQuantity/@unitCode', line)) {
127
+ this.addError(
128
+ 'BR-23',
129
+ 'An Invoice line (BG-25) shall have an Invoiced quantity unit of measure code (BT-130)',
130
+ this.locationOf(line)
131
+ );
132
+ valid = false;
133
+ }
134
+ }
135
+ return valid;
136
+ }
137
+
138
+ /**
139
+ * BR-CL-23: every unit code of a billed or basis quantity (BT-130, BT-150) is a code of
140
+ * UN/ECE Recommendation 20 with the Recommendation 21 extension.
141
+ * @returns True if every unit code passed
142
+ */
143
+ protected validateUnitCodes(): boolean {
144
+ const quantities = this.select(
145
+ '//ram:BilledQuantity[@unitCode] | //ram:BasisQuantity[@unitCode]',
146
+ this.doc
147
+ ) as Element[];
148
+ let valid = true;
149
+ for (const quantity of quantities) {
150
+ const unitCode = quantity.getAttribute('unitCode') ?? '';
151
+ if (!meetsUnitCodeRule(unitCode)) {
152
+ this.addError(
153
+ 'BR-CL-23',
154
+ `Unit code MUST be coded according to the UN/ECE Recommendation 20 with Rec 21 extension; found "${unitCode}"`,
155
+ `${this.locationOf(quantity)}/@unitCode`
156
+ );
157
+ valid = false;
158
+ }
159
+ }
160
+ return valid;
161
+ }
162
+
116
163
  /**
117
164
  * Detects the CII profile from the XML
118
165
  */
@@ -1,5 +1,10 @@
1
1
  import { CIIBaseDecoder } from '../cii.decoder.js';
2
- import type { TInvoice, TCreditNote, TDebitNote } from '../../../interfaces/common.js';
2
+ import type {
3
+ TAccountingDocEnvelope,
4
+ TAccountingDocType,
5
+ TCreditNote,
6
+ TInvoiceDocument,
7
+ } from '../../../interfaces/common.js';
3
8
  import { FACTURX_PROFILE_IDS } from './facturx.types.js';
4
9
  import { business, finance, general } from '../../../plugins.js';
5
10
  import { EN16931Validator } from '../../validation/en16931.validator.js';
@@ -13,36 +18,28 @@ export class FacturXDecoder extends CIIBaseDecoder {
13
18
  * @returns Promise resolving to a TCreditNote object
14
19
  */
15
20
  protected async decodeCreditNote(): Promise<TCreditNote> {
16
- // Get common invoice data
17
- const commonData = await this.extractCommonData();
18
-
19
- // Create a credit note with the common data
20
- return {
21
- ...commonData,
22
- accountingDocType: 'creditnote' as const
23
- } as unknown as TCreditNote;
21
+ return this.extractCommonData('creditnote');
24
22
  }
25
23
 
26
24
  /**
27
- * Decodes a Factur-X debit note (invoice)
28
- * @returns Promise resolving to a TDebitNote object
25
+ * Decodes a Factur-X invoice document: an invoice, a debit note or a self-billed invoice
26
+ * @param accountingDocType The type the document type code stands for
27
+ * @returns Promise resolving to the invoice document
29
28
  */
30
- protected async decodeDebitNote(): Promise<TDebitNote> {
31
- // Get common invoice data
32
- const commonData = await this.extractCommonData();
33
-
34
- // Create a debit note with the common data
35
- return {
36
- ...commonData,
37
- accountingDocType: 'debitnote' as const
38
- } as unknown as TDebitNote;
29
+ protected async decodeInvoice(
30
+ accountingDocType: TInvoiceDocument['accountingDocType'],
31
+ ): Promise<TInvoiceDocument> {
32
+ return this.extractCommonData(accountingDocType);
39
33
  }
40
34
 
41
35
  /**
42
36
  * Extracts common invoice data from Factur-X XML
37
+ * @param accountingDocType The type the document is decoded as
43
38
  * @returns Common invoice data
44
39
  */
45
- private async extractCommonData(): Promise<Partial<TInvoice>> {
40
+ private async extractCommonData<TYPE extends TAccountingDocType>(
41
+ accountingDocType: TYPE,
42
+ ): Promise<TAccountingDocEnvelope<TYPE, {}>> {
46
43
  // Extract invoice ID
47
44
  const invoiceId = this.getRequiredText(
48
45
  '/rsm:CrossIndustryInvoice/rsm:ExchangedDocument/ram:ID',
@@ -101,10 +98,12 @@ export class FacturXDecoder extends CIIBaseDecoder {
101
98
  // Create the common invoice data
102
99
  const invoiceData = {
103
100
  type: 'accounting-doc' as const,
104
- accountingDocType: 'invoice' as const,
101
+ accountingDocType,
105
102
  id: invoiceId,
106
103
  accountingDocId: invoiceId,
107
104
  date: issueDate,
105
+ // a document read from XML has been issued; the envelope carries that as the document and the accounting status
106
+ status: 'issued',
108
107
  accountingDocStatus: 'issued' as const,
109
108
  versionInfo: {
110
109
  type: 'final' as const,
@@ -192,7 +191,8 @@ export class FacturXDecoder extends CIIBaseDecoder {
192
191
  const name = this.getText('ram:SpecifiedTradeProduct/ram:Name', itemNode);
193
192
  const articleNumber = this.getText('ram:SpecifiedTradeProduct/ram:SellerAssignedID', itemNode);
194
193
  const unitQuantity = this.getNumber('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity', itemNode);
195
- const unitType = this.getText('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity/@unitCode', itemNode) || 'EA';
194
+ // an absent unit code (BT-130) stays absent, so that BR-23 reports it
195
+ const unitType = this.getText('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity/@unitCode', itemNode);
196
196
  const unitNetPrice = this.getNumber('ram:SpecifiedLineTradeAgreement/ram:NetPriceProductTradePrice/ram:ChargeAmount', itemNode);
197
197
  const vatPercentage = this.getNumber('ram:SpecifiedLineTradeSettlement/ram:ApplicableTradeTax/ram:RateApplicablePercent', itemNode);
198
198
 
@@ -1,10 +1,11 @@
1
1
  import { CIIBaseEncoder } from '../cii.encoder.js';
2
- import type { TInvoice, TCreditNote, TDebitNote } from '../../../interfaces/common.js';
2
+ import type { TAccountingDoc, TCreditNote, TInvoiceDocument } from '../../../interfaces/common.js';
3
3
  import { FACTURX_PROFILE_IDS } from './facturx.types.js';
4
4
  import { DOMParser, XMLSerializer } from '../../../plugins.js';
5
5
  import { Decimal } from '../../utils/decimal.js';
6
6
  import { DecimalCurrencyCalculator } from '../../utils/currency.calculator.decimal.js';
7
7
  import { getCurrencyMinorUnits } from '../../utils/currency.utils.js';
8
+ import { getDocumentTypeCode } from '../../utils/document.typecode.js';
8
9
 
9
10
  /**
10
11
  * Encoder for Factur-X invoice format
@@ -20,29 +21,29 @@ export class FacturXEncoder extends CIIBaseEncoder {
20
21
  const xmlDoc = this.createBaseXml();
21
22
 
22
23
  // Set document type code to credit note (381)
23
- this.setDocumentTypeCode(xmlDoc, '381');
24
+ this.setDocumentTypeCode(xmlDoc, getDocumentTypeCode(creditNote.accountingDocType));
24
25
 
25
26
  // Add common invoice data
26
- this.addCommonInvoiceData(xmlDoc, creditNote as unknown as TInvoice);
27
+ this.addCommonInvoiceData(xmlDoc, creditNote);
27
28
 
28
29
  // Serialize to string
29
30
  return new XMLSerializer().serializeToString(xmlDoc);
30
31
  }
31
32
 
32
33
  /**
33
- * Encodes a TDebitNote object into Factur-X XML
34
- * @param debitNote TDebitNote object to encode
34
+ * Encodes an invoice document (invoice, debit note or self-billed invoice) into Factur-X XML
35
+ * @param invoice Invoice document to encode
35
36
  * @returns Factur-X XML string
36
37
  */
37
- protected async encodeDebitNote(debitNote: TDebitNote): Promise<string> {
38
+ protected async encodeInvoice(invoice: TInvoiceDocument): Promise<string> {
38
39
  // Create base XML
39
40
  const xmlDoc = this.createBaseXml();
40
41
 
41
- // Set document type code to invoice (380)
42
- this.setDocumentTypeCode(xmlDoc, '380');
42
+ // Set document type code (380 invoice, 383 debit note, 389 self-billed invoice)
43
+ this.setDocumentTypeCode(xmlDoc, getDocumentTypeCode(invoice.accountingDocType));
43
44
 
44
45
  // Add common invoice data
45
- this.addCommonInvoiceData(xmlDoc, debitNote as unknown as TInvoice);
46
+ this.addCommonInvoiceData(xmlDoc, invoice);
46
47
 
47
48
  // Serialize to string
48
49
  return new XMLSerializer().serializeToString(xmlDoc);
@@ -100,7 +101,7 @@ export class FacturXEncoder extends CIIBaseEncoder {
100
101
  /**
101
102
  * Sets the document type code in the XML document
102
103
  * @param doc XML document
103
- * @param typeCode Document type code (380 for invoice, 381 for credit note)
104
+ * @param typeCode Document type code (380 invoice, 381 credit note, 383 debit note, 389 self-billed invoice)
104
105
  */
105
106
  private setDocumentTypeCode(doc: Document, typeCode: string): void {
106
107
  // Get root element
@@ -124,7 +125,7 @@ export class FacturXEncoder extends CIIBaseEncoder {
124
125
  * @param doc XML document
125
126
  * @param invoice Invoice data
126
127
  */
127
- private addCommonInvoiceData(doc: Document, invoice: TInvoice): void {
128
+ private addCommonInvoiceData(doc: Document, invoice: TAccountingDoc): void {
128
129
  // Get root element
129
130
  const root = doc.documentElement;
130
131
 
@@ -192,7 +193,7 @@ export class FacturXEncoder extends CIIBaseEncoder {
192
193
  * @param transactionElement Transaction element
193
194
  * @param invoice Invoice data
194
195
  */
195
- private addAgreementSection(doc: Document, transactionElement: Element, invoice: TInvoice): void {
196
+ private addAgreementSection(doc: Document, transactionElement: Element, invoice: TAccountingDoc): void {
196
197
  // Create agreement element
197
198
  const agreementElement = doc.createElement('ram:ApplicableHeaderTradeAgreement');
198
199
  transactionElement.appendChild(agreementElement);
@@ -277,7 +278,7 @@ export class FacturXEncoder extends CIIBaseEncoder {
277
278
  * @param transactionElement Transaction element
278
279
  * @param invoice Invoice data
279
280
  */
280
- private addDeliverySection(doc: Document, transactionElement: Element, invoice: TInvoice): void {
281
+ private addDeliverySection(doc: Document, transactionElement: Element, invoice: TAccountingDoc): void {
281
282
  // Create delivery element
282
283
  const deliveryElement = doc.createElement('ram:ApplicableHeaderTradeDelivery');
283
284
  transactionElement.appendChild(deliveryElement);
@@ -301,7 +302,7 @@ export class FacturXEncoder extends CIIBaseEncoder {
301
302
  * @param transactionElement Transaction element
302
303
  * @param invoice Invoice data
303
304
  */
304
- private addSettlementSection(doc: Document, transactionElement: Element, invoice: TInvoice): void {
305
+ private addSettlementSection(doc: Document, transactionElement: Element, invoice: TAccountingDoc): void {
305
306
  // Create settlement element
306
307
  const settlementElement = doc.createElement('ram:ApplicableHeaderTradeSettlement');
307
308
  transactionElement.appendChild(settlementElement);
@@ -383,7 +384,7 @@ export class FacturXEncoder extends CIIBaseEncoder {
383
384
  * @param transactionElement Transaction element
384
385
  * @param invoice Invoice data
385
386
  */
386
- private addLineItems(doc: Document, transactionElement: Element, invoice: TInvoice): void {
387
+ private addLineItems(doc: Document, transactionElement: Element, invoice: TAccountingDoc): void {
387
388
  const calculator = new DecimalCurrencyCalculator(invoice.currency);
388
389
  const minorUnits = getCurrencyMinorUnits(invoice.currency);
389
390
 
@@ -430,7 +431,10 @@ export class FacturXEncoder extends CIIBaseEncoder {
430
431
  const deliveryElement = doc.createElement('ram:SpecifiedLineTradeDelivery');
431
432
  const quantityElement = doc.createElement('ram:BilledQuantity');
432
433
  quantityElement.textContent = item.unitQuantity.toString();
433
- quantityElement.setAttribute('unitCode', item.unitType);
434
+ // a line without a unit is written without one, so that BR-23 reports it
435
+ if (item.unitType) {
436
+ quantityElement.setAttribute('unitCode', item.unitType);
437
+ }
434
438
  deliveryElement.appendChild(quantityElement);
435
439
  lineItemElement.appendChild(deliveryElement);
436
440
 
@@ -24,14 +24,14 @@ export class FacturXValidator extends CIIBaseValidator {
24
24
  ];
25
25
 
26
26
  for (const section of sections) {
27
- if (!this.exists(section)) {
27
+ if (!this.exists(`/rsm:CrossIndustryInvoice/${section}`)) {
28
28
  this.addError('FX-STRUCT-1', `Required section ${section} is missing`, '/rsm:CrossIndustryInvoice');
29
29
  valid = false;
30
30
  }
31
31
  }
32
32
 
33
33
  // Check for SupplyChainTradeTransaction sections
34
- if (this.exists('rsm:SupplyChainTradeTransaction')) {
34
+ if (this.exists('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction')) {
35
35
  const tradeSubsections = [
36
36
  'ram:ApplicableHeaderTradeAgreement',
37
37
  'ram:ApplicableHeaderTradeDelivery',
@@ -39,7 +39,7 @@ export class FacturXValidator extends CIIBaseValidator {
39
39
  ];
40
40
 
41
41
  for (const subsection of tradeSubsections) {
42
- if (!this.exists(`rsm:SupplyChainTradeTransaction/${subsection}`)) {
42
+ if (!this.exists(`/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/${subsection}`)) {
43
43
  this.addError('FX-STRUCT-2', `Required subsection ${subsection} is missing`,
44
44
  '/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction');
45
45
  valid = false;
@@ -70,6 +70,12 @@ 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-23: every line has a unit code
74
+ valid = this.validateLineUnitCodePresence() && valid;
75
+
76
+ // BR-CL-23: unit codes of UN/ECE Recommendation 20 with the Recommendation 21 extension
77
+ valid = this.validateUnitCodes() && valid;
78
+
73
79
  return valid;
74
80
  }
75
81
 
@@ -1,5 +1,10 @@
1
1
  import { CIIBaseDecoder } from '../cii.decoder.js';
2
- import type { TInvoice, TCreditNote, TDebitNote } from '../../../interfaces/common.js';
2
+ import type {
3
+ TAccountingDocEnvelope,
4
+ TAccountingDocType,
5
+ TCreditNote,
6
+ TInvoiceDocument,
7
+ } from '../../../interfaces/common.js';
3
8
  import { business, finance } from '../../../plugins.js';
4
9
  import { EN16931Validator } from '../../validation/en16931.validator.js';
5
10
 
@@ -12,36 +17,28 @@ export class ZUGFeRDDecoder extends CIIBaseDecoder {
12
17
  * @returns Promise resolving to a TCreditNote object
13
18
  */
14
19
  protected async decodeCreditNote(): Promise<TCreditNote> {
15
- // Get common invoice data
16
- const commonData = await this.extractCommonData();
17
-
18
- // Create a credit note with the common data
19
- return {
20
- ...commonData,
21
- accountingDocType: 'creditnote' as const
22
- } as unknown as TCreditNote;
20
+ return this.extractCommonData('creditnote');
23
21
  }
24
22
 
25
23
  /**
26
- * Decodes a ZUGFeRD debit note (invoice)
27
- * @returns Promise resolving to a TDebitNote object
24
+ * Decodes a ZUGFeRD invoice document: an invoice, a debit note or a self-billed invoice
25
+ * @param accountingDocType The type the document type code stands for
26
+ * @returns Promise resolving to the invoice document
28
27
  */
29
- protected async decodeDebitNote(): Promise<TDebitNote> {
30
- // Get common invoice data
31
- const commonData = await this.extractCommonData();
32
-
33
- // Create a debit note with the common data
34
- return {
35
- ...commonData,
36
- accountingDocType: 'debitnote' as const
37
- } as unknown as TDebitNote;
28
+ protected async decodeInvoice(
29
+ accountingDocType: TInvoiceDocument['accountingDocType'],
30
+ ): Promise<TInvoiceDocument> {
31
+ return this.extractCommonData(accountingDocType);
38
32
  }
39
33
 
40
34
  /**
41
35
  * Extracts common invoice data from ZUGFeRD XML
36
+ * @param accountingDocType The type the document is decoded as
42
37
  * @returns Common invoice data
43
38
  */
44
- private async extractCommonData(): Promise<Partial<TInvoice>> {
39
+ private async extractCommonData<TYPE extends TAccountingDocType>(
40
+ accountingDocType: TYPE,
41
+ ): Promise<TAccountingDocEnvelope<TYPE, {}>> {
45
42
  // Extract invoice ID
46
43
  const invoiceId = this.getText('//rsm:ExchangedDocument/ram:ID');
47
44
 
@@ -92,10 +89,12 @@ export class ZUGFeRDDecoder extends CIIBaseDecoder {
92
89
  // Create the common invoice data
93
90
  const invoiceData = {
94
91
  type: 'accounting-doc' as const,
95
- accountingDocType: 'invoice' as const,
92
+ accountingDocType,
96
93
  id: invoiceId,
97
94
  accountingDocId: invoiceId,
98
95
  date: issueDate,
96
+ // a document read from XML has been issued; the envelope carries that as the document and the accounting status
97
+ status: 'issued',
99
98
  accountingDocStatus: 'issued' as const,
100
99
  versionInfo: {
101
100
  type: 'final' as const,
@@ -196,7 +195,8 @@ export class ZUGFeRDDecoder extends CIIBaseDecoder {
196
195
  const name = this.getText('ram:SpecifiedTradeProduct/ram:Name', itemNode);
197
196
  const articleNumber = this.getText('ram:SpecifiedTradeProduct/ram:SellerAssignedID', itemNode);
198
197
  const unitQuantity = this.getNumber('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity', itemNode);
199
- const unitType = this.getText('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity/@unitCode', itemNode) || 'EA';
198
+ // an absent unit code (BT-130) stays absent, so that BR-23 reports it
199
+ const unitType = this.getText('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity/@unitCode', itemNode);
200
200
  const unitNetPrice = this.getNumber('ram:SpecifiedLineTradeAgreement/ram:NetPriceProductTradePrice/ram:ChargeAmount', itemNode);
201
201
  const vatPercentage = this.getNumber('ram:SpecifiedLineTradeSettlement/ram:ApplicableTradeTax/ram:RateApplicablePercent', itemNode);
202
202
 
@@ -1,8 +1,9 @@
1
1
  import { CIIBaseEncoder } from '../cii.encoder.js';
2
- import type { TInvoice, TCreditNote, TDebitNote } from '../../../interfaces/common.js';
2
+ import type { TAccountingDoc, TCreditNote, TInvoiceDocument } from '../../../interfaces/common.js';
3
3
  import { ZUGFERD_PROFILE_IDS } from './zugferd.types.js';
4
4
  import { CIIProfile } from '../cii.types.js';
5
5
  import { DOMParser, XMLSerializer } from '../../../plugins.js';
6
+ import { getDocumentTypeCode } from '../../utils/document.typecode.js';
6
7
 
7
8
  /**
8
9
  * Encoder for ZUGFeRD invoice format
@@ -24,29 +25,29 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
24
25
  const xmlDoc = this.createBaseXml();
25
26
 
26
27
  // Set document type code to credit note (381)
27
- this.setDocumentTypeCode(xmlDoc, '381');
28
+ this.setDocumentTypeCode(xmlDoc, getDocumentTypeCode(creditNote.accountingDocType));
28
29
 
29
30
  // Add common invoice data
30
- this.addCommonInvoiceData(xmlDoc, creditNote as unknown as TInvoice);
31
+ this.addCommonInvoiceData(xmlDoc, creditNote);
31
32
 
32
33
  // Serialize to string
33
34
  return new XMLSerializer().serializeToString(xmlDoc);
34
35
  }
35
36
 
36
37
  /**
37
- * Encodes a debit note (invoice) into ZUGFeRD XML
38
- * @param debitNote Debit note to encode
38
+ * Encodes an invoice document (invoice, debit note or self-billed invoice) into ZUGFeRD XML
39
+ * @param invoice Invoice document to encode
39
40
  * @returns ZUGFeRD XML string
40
41
  */
41
- protected async encodeDebitNote(debitNote: TDebitNote): Promise<string> {
42
+ protected async encodeInvoice(invoice: TInvoiceDocument): Promise<string> {
42
43
  // Create base XML
43
44
  const xmlDoc = this.createBaseXml();
44
45
 
45
- // Set document type code to invoice (380)
46
- this.setDocumentTypeCode(xmlDoc, '380');
46
+ // Set document type code (380 invoice, 383 debit note, 389 self-billed invoice)
47
+ this.setDocumentTypeCode(xmlDoc, getDocumentTypeCode(invoice.accountingDocType));
47
48
 
48
49
  // Add common invoice data
49
- this.addCommonInvoiceData(xmlDoc, debitNote as unknown as TInvoice);
50
+ this.addCommonInvoiceData(xmlDoc, invoice);
50
51
 
51
52
  // Serialize to string
52
53
  return new XMLSerializer().serializeToString(xmlDoc);
@@ -128,7 +129,7 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
128
129
  * @param doc XML document
129
130
  * @param invoice Invoice data
130
131
  */
131
- private addCommonInvoiceData(doc: Document, invoice: TInvoice): void {
132
+ private addCommonInvoiceData(doc: Document, invoice: TAccountingDoc): void {
132
133
  // Get root element
133
134
  const root = doc.documentElement;
134
135
 
@@ -196,7 +197,7 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
196
197
  * @param transactionElement Transaction element
197
198
  * @param invoice Invoice data
198
199
  */
199
- private addAgreementSection(doc: Document, transactionElement: Element, invoice: TInvoice): void {
200
+ private addAgreementSection(doc: Document, transactionElement: Element, invoice: TAccountingDoc): void {
200
201
  // Create agreement element
201
202
  const agreementElement = doc.createElement('ram:ApplicableHeaderTradeAgreement');
202
203
  transactionElement.appendChild(agreementElement);
@@ -309,7 +310,7 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
309
310
  * @param transactionElement Transaction element
310
311
  * @param invoice Invoice data
311
312
  */
312
- private addDeliverySection(doc: Document, transactionElement: Element, invoice: TInvoice): void {
313
+ private addDeliverySection(doc: Document, transactionElement: Element, invoice: TAccountingDoc): void {
313
314
  // Create delivery element
314
315
  const deliveryElement = doc.createElement('ram:ApplicableHeaderTradeDelivery');
315
316
  transactionElement.appendChild(deliveryElement);
@@ -360,7 +361,7 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
360
361
  * @param transactionElement Transaction element
361
362
  * @param invoice Invoice data
362
363
  */
363
- private addSettlementSection(doc: Document, transactionElement: Element, invoice: TInvoice): void {
364
+ private addSettlementSection(doc: Document, transactionElement: Element, invoice: TAccountingDoc): void {
364
365
  // Create settlement element
365
366
  const settlementElement = doc.createElement('ram:ApplicableHeaderTradeSettlement');
366
367
  transactionElement.appendChild(settlementElement);
@@ -447,7 +448,7 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
447
448
  * @param settlementElement Settlement element
448
449
  * @param invoice Invoice data
449
450
  */
450
- private addTaxDetails(doc: Document, settlementElement: Element, invoice: TInvoice): void {
451
+ private addTaxDetails(doc: Document, settlementElement: Element, invoice: TAccountingDoc): void {
451
452
  // Calculate tax categories and totals
452
453
  const taxCategories = new Map<number, number>(); // Map of VAT rate to net amount
453
454
 
@@ -504,7 +505,7 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
504
505
  * @param settlementElement Settlement element
505
506
  * @param invoice Invoice data
506
507
  */
507
- private addMonetarySummation(doc: Document, settlementElement: Element, invoice: TInvoice): void {
508
+ private addMonetarySummation(doc: Document, settlementElement: Element, invoice: TAccountingDoc): void {
508
509
  const monetarySummationElement = doc.createElement('ram:SpecifiedTradeSettlementHeaderMonetarySummation');
509
510
 
510
511
  // Calculate totals
@@ -554,7 +555,7 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
554
555
  * @param transactionElement Transaction element
555
556
  * @param invoice Invoice data
556
557
  */
557
- private addLineItems(doc: Document, transactionElement: Element, invoice: TInvoice): void {
558
+ private addLineItems(doc: Document, transactionElement: Element, invoice: TAccountingDoc): void {
558
559
  // Add each line item
559
560
  if (invoice.items) {
560
561
  for (const item of invoice.items) {
@@ -598,7 +599,10 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
598
599
  const deliveryElement = doc.createElement('ram:SpecifiedLineTradeDelivery');
599
600
  const quantityElement = doc.createElement('ram:BilledQuantity');
600
601
  quantityElement.textContent = item.unitQuantity.toString();
601
- quantityElement.setAttribute('unitCode', item.unitType);
602
+ // a line without a unit is written without one, so that BR-23 reports it
603
+ if (item.unitType) {
604
+ quantityElement.setAttribute('unitCode', item.unitType);
605
+ }
602
606
  deliveryElement.appendChild(quantityElement);
603
607
  lineItemElement.appendChild(deliveryElement);
604
608