@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.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/einvoice.d.ts +10 -9
- package/dist_ts/einvoice.js +40 -10
- package/dist_ts/formats/base/base.decoder.d.ts +6 -6
- package/dist_ts/formats/base/base.decoder.js +1 -1
- package/dist_ts/formats/base/base.encoder.d.ts +4 -4
- package/dist_ts/formats/base/base.validator.d.ts +5 -0
- package/dist_ts/formats/base/base.validator.js +28 -1
- package/dist_ts/formats/cii/cii.decoder.d.ts +8 -7
- package/dist_ts/formats/cii/cii.decoder.js +8 -9
- package/dist_ts/formats/cii/cii.encoder.d.ts +7 -7
- package/dist_ts/formats/cii/cii.encoder.js +5 -5
- package/dist_ts/formats/cii/cii.validator.d.ts +11 -0
- package/dist_ts/formats/cii/cii.validator.js +35 -1
- package/dist_ts/formats/cii/facturx/facturx.decoder.d.ts +6 -4
- package/dist_ts/formats/cii/facturx/facturx.decoder.js +14 -21
- package/dist_ts/formats/cii/facturx/facturx.encoder.d.ts +5 -5
- package/dist_ts/formats/cii/facturx/facturx.encoder.js +14 -10
- package/dist_ts/formats/cii/facturx/facturx.validator.js +8 -4
- package/dist_ts/formats/cii/zugferd/zugferd.decoder.d.ts +6 -4
- package/dist_ts/formats/cii/zugferd/zugferd.decoder.js +14 -21
- package/dist_ts/formats/cii/zugferd/zugferd.encoder.d.ts +4 -4
- package/dist_ts/formats/cii/zugferd/zugferd.encoder.js +13 -9
- package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.d.ts +6 -4
- package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.js +14 -21
- package/dist_ts/formats/cii/zugferd/zugferd.validator.js +5 -4
- package/dist_ts/formats/converters/xml-to-einvoice.converter.d.ts +5 -0
- package/dist_ts/formats/converters/xml-to-einvoice.converter.js +11 -2
- package/dist_ts/formats/semantic/semantic.adapter.d.ts +0 -8
- package/dist_ts/formats/semantic/semantic.adapter.js +5 -34
- package/dist_ts/formats/ubl/en16931.ubl.validator.js +5 -1
- package/dist_ts/formats/ubl/generic/ubl.encoder.d.ts +4 -4
- package/dist_ts/formats/ubl/generic/ubl.encoder.js +13 -14
- package/dist_ts/formats/ubl/ubl.decoder.d.ts +8 -7
- package/dist_ts/formats/ubl/ubl.decoder.js +11 -8
- package/dist_ts/formats/ubl/ubl.encoder.d.ts +7 -7
- package/dist_ts/formats/ubl/ubl.encoder.js +5 -5
- package/dist_ts/formats/ubl/ubl.validator.d.ts +11 -0
- package/dist_ts/formats/ubl/ubl.validator.js +35 -1
- package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.d.ts +6 -4
- package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.js +14 -21
- package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.d.ts +4 -4
- package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.js +6 -6
- package/dist_ts/formats/utils/document.typecode.d.ts +18 -0
- package/dist_ts/formats/utils/document.typecode.js +52 -0
- package/dist_ts/formats/utils/unit.codes.d.ts +40 -0
- package/dist_ts/formats/utils/unit.codes.js +193 -0
- package/dist_ts/formats/validation/codelist.validator.d.ts +1 -1
- package/dist_ts/formats/validation/codelist.validator.js +10 -12
- package/dist_ts/formats/validation/en16931.business-rules.validator.js +13 -13
- package/dist_ts/formats/validation/validation.types.d.ts +0 -4
- package/dist_ts/formats/validation/validation.types.js +1 -28
- package/dist_ts/index.d.ts +7 -1
- package/dist_ts/index.js +11 -1
- package/dist_ts/interfaces/common.d.ts +11 -0
- package/dist_ts_install/download-schematron.js +3 -5
- package/package.json +7 -7
- package/readme.md +99 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/einvoice.ts +59 -18
- package/ts/formats/base/base.decoder.ts +6 -6
- package/ts/formats/base/base.encoder.ts +4 -4
- package/ts/formats/base/base.validator.ts +28 -0
- package/ts/formats/cii/cii.decoder.ts +18 -14
- package/ts/formats/cii/cii.encoder.ts +11 -11
- package/ts/formats/cii/cii.validator.ts +47 -0
- package/ts/formats/cii/facturx/facturx.decoder.ts +23 -23
- package/ts/formats/cii/facturx/facturx.encoder.ts +20 -16
- package/ts/formats/cii/facturx/facturx.validator.ts +9 -3
- package/ts/formats/cii/zugferd/zugferd.decoder.ts +23 -23
- package/ts/formats/cii/zugferd/zugferd.encoder.ts +21 -17
- package/ts/formats/cii/zugferd/zugferd.v1.decoder.ts +23 -23
- package/ts/formats/cii/zugferd/zugferd.validator.ts +4 -3
- package/ts/formats/converters/xml-to-einvoice.converter.ts +11 -1
- package/ts/formats/semantic/semantic.adapter.ts +4 -35
- package/ts/formats/ubl/en16931.ubl.validator.ts +6 -0
- package/ts/formats/ubl/generic/ubl.encoder.ts +29 -24
- package/ts/formats/ubl/ubl.decoder.ts +23 -14
- package/ts/formats/ubl/ubl.encoder.ts +11 -11
- package/ts/formats/ubl/ubl.validator.ts +47 -0
- package/ts/formats/ubl/xrechnung/xrechnung.decoder.ts +23 -23
- package/ts/formats/ubl/xrechnung/xrechnung.encoder.ts +10 -10
- package/ts/formats/utils/document.typecode.ts +62 -0
- package/ts/formats/utils/unit.codes.ts +215 -0
- package/ts/formats/validation/codelist.validator.ts +12 -23
- package/ts/formats/validation/en16931.business-rules.validator.ts +13 -13
- package/ts/formats/validation/validation.types.ts +0 -28
- package/ts/index.ts +25 -0
- package/ts/interfaces/common.ts +17 -0
- package/ts/readme.md +3 -2
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { CIIBaseDecoder } from '../cii.decoder.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
TAccountingDocEnvelope,
|
|
4
|
+
TAccountingDocType,
|
|
5
|
+
TCreditNote,
|
|
6
|
+
TInvoiceDocument,
|
|
7
|
+
} from '../../../interfaces/common.js';
|
|
3
8
|
import { ZUGFERD_V1_NAMESPACES } from '../cii.types.js';
|
|
4
9
|
import { business, finance } from '../../../plugins.js';
|
|
5
10
|
import { EN16931Validator } from '../../validation/en16931.validator.js';
|
|
@@ -28,36 +33,28 @@ export class ZUGFeRDV1Decoder extends CIIBaseDecoder {
|
|
|
28
33
|
* @returns Promise resolving to a TCreditNote object
|
|
29
34
|
*/
|
|
30
35
|
protected async decodeCreditNote(): Promise<TCreditNote> {
|
|
31
|
-
|
|
32
|
-
const commonData = await this.extractCommonData();
|
|
33
|
-
|
|
34
|
-
// Create a credit note with the common data
|
|
35
|
-
return {
|
|
36
|
-
...commonData,
|
|
37
|
-
accountingDocType: 'creditnote' as const
|
|
38
|
-
} as unknown as TCreditNote;
|
|
36
|
+
return this.extractCommonData('creditnote');
|
|
39
37
|
}
|
|
40
38
|
|
|
41
39
|
/**
|
|
42
|
-
* Decodes a ZUGFeRD v1 debit note
|
|
43
|
-
* @
|
|
40
|
+
* Decodes a ZUGFeRD v1 invoice document: an invoice, a debit note or a self-billed invoice
|
|
41
|
+
* @param accountingDocType The type the document type code stands for
|
|
42
|
+
* @returns Promise resolving to the invoice document
|
|
44
43
|
*/
|
|
45
|
-
protected async
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
// Create a debit note with the common data
|
|
50
|
-
return {
|
|
51
|
-
...commonData,
|
|
52
|
-
accountingDocType: 'debitnote' as const
|
|
53
|
-
} as unknown as TDebitNote;
|
|
44
|
+
protected async decodeInvoice(
|
|
45
|
+
accountingDocType: TInvoiceDocument['accountingDocType'],
|
|
46
|
+
): Promise<TInvoiceDocument> {
|
|
47
|
+
return this.extractCommonData(accountingDocType);
|
|
54
48
|
}
|
|
55
49
|
|
|
56
50
|
/**
|
|
57
51
|
* Extracts common invoice data from ZUGFeRD v1 XML
|
|
52
|
+
* @param accountingDocType The type the document is decoded as
|
|
58
53
|
* @returns Common invoice data
|
|
59
54
|
*/
|
|
60
|
-
private async extractCommonData
|
|
55
|
+
private async extractCommonData<TYPE extends TAccountingDocType>(
|
|
56
|
+
accountingDocType: TYPE,
|
|
57
|
+
): Promise<TAccountingDocEnvelope<TYPE, {}>> {
|
|
61
58
|
// Extract invoice ID
|
|
62
59
|
const invoiceId = this.getText('//ram:ID');
|
|
63
60
|
|
|
@@ -108,10 +105,12 @@ export class ZUGFeRDV1Decoder extends CIIBaseDecoder {
|
|
|
108
105
|
// Create the common invoice data
|
|
109
106
|
const invoiceData = {
|
|
110
107
|
type: 'accounting-doc' as const,
|
|
111
|
-
accountingDocType
|
|
108
|
+
accountingDocType,
|
|
112
109
|
id: invoiceId,
|
|
113
110
|
accountingDocId: invoiceId,
|
|
114
111
|
date: issueDate,
|
|
112
|
+
// a document read from XML has been issued; the envelope carries that as the document and the accounting status
|
|
113
|
+
status: 'issued',
|
|
115
114
|
accountingDocStatus: 'issued' as const,
|
|
116
115
|
versionInfo: {
|
|
117
116
|
type: 'final' as const,
|
|
@@ -207,7 +206,8 @@ export class ZUGFeRDV1Decoder extends CIIBaseDecoder {
|
|
|
207
206
|
const name = this.getText('ram:SpecifiedTradeProduct/ram:Name', itemNode);
|
|
208
207
|
const articleNumber = this.getText('ram:SpecifiedTradeProduct/ram:SellerAssignedID', itemNode);
|
|
209
208
|
const unitQuantity = this.getNumber('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity', itemNode);
|
|
210
|
-
|
|
209
|
+
// an absent unit code (BT-130) stays absent, so that BR-23 reports it
|
|
210
|
+
const unitType = this.getText('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity/@unitCode', itemNode);
|
|
211
211
|
const unitNetPrice = this.getNumber('ram:SpecifiedLineTradeAgreement/ram:NetPriceProductTradePrice/ram:ChargeAmount', itemNode);
|
|
212
212
|
const vatPercentage = this.getNumber('ram:SpecifiedLineTradeSettlement/ram:ApplicableTradeTax/ram:RateApplicablePercent', itemNode);
|
|
213
213
|
|
|
@@ -24,8 +24,9 @@ export class ZUGFeRDValidator extends CIIBaseValidator {
|
|
|
24
24
|
* @returns True if business validation passed
|
|
25
25
|
*/
|
|
26
26
|
protected validateBusinessRules(): boolean {
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
// BR-23: every line has a unit code
|
|
28
|
+
const linesHaveUnits = this.validateLineUnitCodePresence();
|
|
29
|
+
// BR-CL-23: unit codes of UN/ECE Recommendation 20 with the Recommendation 21 extension
|
|
30
|
+
return this.validateUnitCodes() && linesHaveUnits;
|
|
30
31
|
}
|
|
31
32
|
}
|
|
@@ -100,7 +100,8 @@ export class XMLToEInvoiceConverter {
|
|
|
100
100
|
position: i,
|
|
101
101
|
name: this.getElementTextFromNode(line, 'cbc:Name') || `Item ${i + 1}`,
|
|
102
102
|
unitQuantity: parseFloat(this.getElementTextFromNode(line, 'cbc:InvoicedQuantity') || '1'),
|
|
103
|
-
|
|
103
|
+
// the unit code (BT-130) the document states, or none, which BR-23 reports
|
|
104
|
+
unitType: this.getAttributeFromNode(line, 'cbc:InvoicedQuantity', 'unitCode'),
|
|
104
105
|
unitNetPrice: parseFloat(this.getElementTextFromNode(line, 'cbc:PriceAmount') || '100'),
|
|
105
106
|
vatPercentage: parseFloat(this.getElementTextFromNode(line, 'cbc:Percent') || '19')
|
|
106
107
|
};
|
|
@@ -118,6 +119,15 @@ export class XMLToEInvoiceConverter {
|
|
|
118
119
|
return mockInvoice as unknown as EInvoice;
|
|
119
120
|
}
|
|
120
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Helper to get an attribute of the first matching element below a node, or '' when the element
|
|
124
|
+
* or the attribute is absent
|
|
125
|
+
*/
|
|
126
|
+
private getAttributeFromNode(node: Element, tagName: string, attributeName: string): string {
|
|
127
|
+
const element = node.getElementsByTagName(tagName)[0];
|
|
128
|
+
return element?.getAttribute(attributeName) ?? '';
|
|
129
|
+
}
|
|
130
|
+
|
|
121
131
|
/**
|
|
122
132
|
* Helper to get element text from a node
|
|
123
133
|
*/
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { EInvoice } from '../../einvoice.js';
|
|
7
|
+
import { getAccountingDocType, getDocumentTypeCode } from '../utils/document.typecode.js';
|
|
7
8
|
import type {
|
|
8
9
|
EN16931SemanticModel,
|
|
9
10
|
Seller,
|
|
@@ -36,7 +37,7 @@ export class SemanticModelAdapter {
|
|
|
36
37
|
documentInformation: {
|
|
37
38
|
invoiceNumber: invoice.accountingDocId,
|
|
38
39
|
issueDate: invoice.issueDate,
|
|
39
|
-
typeCode:
|
|
40
|
+
typeCode: getDocumentTypeCode(invoice.accountingDocType),
|
|
40
41
|
currencyCode: invoice.currency,
|
|
41
42
|
notes: invoice.notes ? this.mapNotes(invoice.notes) : undefined
|
|
42
43
|
},
|
|
@@ -126,7 +127,7 @@ export class SemanticModelAdapter {
|
|
|
126
127
|
const invoice = new EInvoice();
|
|
127
128
|
invoice.accountingDocId = model.documentInformation.invoiceNumber;
|
|
128
129
|
invoice.issueDate = model.documentInformation.issueDate;
|
|
129
|
-
invoice.accountingDocType =
|
|
130
|
+
invoice.accountingDocType = getAccountingDocType(model.documentInformation.typeCode);
|
|
130
131
|
invoice.currency = model.documentInformation.currencyCode as any;
|
|
131
132
|
invoice.from = this.reverseMapSeller(model.seller);
|
|
132
133
|
invoice.to = this.reverseMapBuyer(model.buyer);
|
|
@@ -231,38 +232,6 @@ export class SemanticModelAdapter {
|
|
|
231
232
|
return invoice;
|
|
232
233
|
}
|
|
233
234
|
|
|
234
|
-
/**
|
|
235
|
-
* Map invoice type code
|
|
236
|
-
*/
|
|
237
|
-
private mapInvoiceType(type: string): string {
|
|
238
|
-
const typeMap: Record<string, string> = {
|
|
239
|
-
'invoice': '380',
|
|
240
|
-
'creditNote': '381',
|
|
241
|
-
'debitNote': '383',
|
|
242
|
-
'correctedInvoice': '384',
|
|
243
|
-
'prepaymentInvoice': '386',
|
|
244
|
-
'selfBilledInvoice': '389',
|
|
245
|
-
'invoice_380': '380',
|
|
246
|
-
'credit_note_381': '381'
|
|
247
|
-
};
|
|
248
|
-
return typeMap[type] || '380';
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Reverse map invoice type code
|
|
253
|
-
*/
|
|
254
|
-
private reverseMapInvoiceType(code: string): string {
|
|
255
|
-
const typeMap: Record<string, string> = {
|
|
256
|
-
'380': 'invoice',
|
|
257
|
-
'381': 'creditNote',
|
|
258
|
-
'383': 'debitNote',
|
|
259
|
-
'384': 'correctedInvoice',
|
|
260
|
-
'386': 'prepaymentInvoice',
|
|
261
|
-
'389': 'selfBilledInvoice'
|
|
262
|
-
};
|
|
263
|
-
return typeMap[code] || 'invoice';
|
|
264
|
-
}
|
|
265
|
-
|
|
266
235
|
/**
|
|
267
236
|
* Map notes
|
|
268
237
|
*/
|
|
@@ -473,7 +442,7 @@ export class SemanticModelAdapter {
|
|
|
473
442
|
identifier: (index + 1).toString(),
|
|
474
443
|
note: (item as any).description || (item as any).text || '',
|
|
475
444
|
invoicedQuantity: item.unitQuantity,
|
|
476
|
-
invoicedQuantityUnitOfMeasureCode: item.unitType
|
|
445
|
+
invoicedQuantityUnitOfMeasureCode: item.unitType,
|
|
477
446
|
lineExtensionAmount: item.unitNetPrice * item.unitQuantity,
|
|
478
447
|
purchaseOrderLineReference: (item as any).purchaseOrderLineRef,
|
|
479
448
|
buyerAccountingReference: (item as any).buyerAccountingRef,
|
|
@@ -148,6 +148,12 @@ export class EN16931UBLValidator extends UBLBaseValidator {
|
|
|
148
148
|
valid = false;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
// BR-23: every line has a unit code
|
|
152
|
+
valid = this.validateLineUnitCodePresence() && valid;
|
|
153
|
+
|
|
154
|
+
// BR-CL-23: unit codes of UN/ECE Recommendation 20 with the Recommendation 21 extension
|
|
155
|
+
valid = this.validateUnitCodes() && valid;
|
|
156
|
+
|
|
151
157
|
// Validate calculation rules if we have the necessary data
|
|
152
158
|
if (this.exists('//cac:LegalMonetaryTotal/cbc:LineExtensionAmount')) {
|
|
153
159
|
valid = this.validateCalculationRules() && valid;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { UBLBaseEncoder } from '../ubl.encoder.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { TAccountingDoc, TCreditNote, TInvoiceDocument } from '../../../interfaces/common.js';
|
|
3
3
|
import { UBLDocumentType } from '../ubl.types.js';
|
|
4
4
|
import { DOMParser, XMLSerializer } from '../../../plugins.js';
|
|
5
|
+
import { getDocumentTypeCode } from '../../utils/document.typecode.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* UBL Encoder implementation
|
|
@@ -19,7 +20,7 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
19
20
|
const doc = new DOMParser().parseFromString(xmlString, 'application/xml');
|
|
20
21
|
|
|
21
22
|
// Add common document elements
|
|
22
|
-
this.addCommonElements(doc, creditNote
|
|
23
|
+
this.addCommonElements(doc, creditNote, UBLDocumentType.CREDIT_NOTE);
|
|
23
24
|
|
|
24
25
|
// Add credit note specific data
|
|
25
26
|
this.addCreditNoteSpecificData(doc, creditNote);
|
|
@@ -29,20 +30,20 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
|
-
* Encodes
|
|
33
|
-
* @param
|
|
33
|
+
* Encodes an invoice document (invoice, debit note or self-billed invoice) into UBL XML
|
|
34
|
+
* @param invoice Invoice document to encode
|
|
34
35
|
* @returns UBL XML string
|
|
35
36
|
*/
|
|
36
|
-
protected async
|
|
37
|
+
protected async encodeInvoice(invoice: TInvoiceDocument): Promise<string> {
|
|
37
38
|
// Create XML document from template
|
|
38
39
|
const xmlString = this.createXmlRoot(UBLDocumentType.INVOICE);
|
|
39
40
|
const doc = new DOMParser().parseFromString(xmlString, 'application/xml');
|
|
40
41
|
|
|
41
42
|
// Add common document elements
|
|
42
|
-
this.addCommonElements(doc,
|
|
43
|
+
this.addCommonElements(doc, invoice, UBLDocumentType.INVOICE);
|
|
43
44
|
|
|
44
45
|
// Add invoice specific data
|
|
45
|
-
this.addInvoiceSpecificData(doc,
|
|
46
|
+
this.addInvoiceSpecificData(doc, invoice);
|
|
46
47
|
|
|
47
48
|
// Serialize to string
|
|
48
49
|
return new XMLSerializer().serializeToString(doc);
|
|
@@ -54,7 +55,7 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
54
55
|
* @param invoice Invoice or credit note data
|
|
55
56
|
* @param documentType Document type (Invoice or CreditNote)
|
|
56
57
|
*/
|
|
57
|
-
private addCommonElements(doc: Document, invoice:
|
|
58
|
+
private addCommonElements(doc: Document, invoice: TAccountingDoc, documentType: UBLDocumentType): void {
|
|
58
59
|
const root = doc.documentElement;
|
|
59
60
|
|
|
60
61
|
// UBL Version ID (2.1 is standard for EN16931)
|
|
@@ -79,12 +80,13 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
79
80
|
dueDate.setDate(dueDate.getDate() + (invoice.dueInDays || 30));
|
|
80
81
|
this.appendElement(doc, root, 'cbc:DueDate', this.formatDate(dueDate.getTime()));
|
|
81
82
|
}
|
|
82
|
-
// Document Type Code (380 invoice, 381 credit note; the element differs per schema)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
// Document Type Code (380 invoice, 381 credit note, 383 debit note, 389 self-billed invoice; the element differs per schema)
|
|
84
|
+
this.appendElement(
|
|
85
|
+
doc,
|
|
86
|
+
root,
|
|
87
|
+
documentType === UBLDocumentType.INVOICE ? 'cbc:InvoiceTypeCode' : 'cbc:CreditNoteTypeCode',
|
|
88
|
+
getDocumentTypeCode(invoice.accountingDocType),
|
|
89
|
+
);
|
|
88
90
|
|
|
89
91
|
// Notes - include subject as first note if it exists
|
|
90
92
|
const allNotes: string[] = [];
|
|
@@ -140,7 +142,7 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
140
142
|
* @param doc XML document
|
|
141
143
|
* @param invoice Invoice data
|
|
142
144
|
*/
|
|
143
|
-
private addInvoiceSpecificData(doc: Document, invoice:
|
|
145
|
+
private addInvoiceSpecificData(doc: Document, invoice: TInvoiceDocument): void {
|
|
144
146
|
// For now, there's no specific data to add for invoices that's not already covered
|
|
145
147
|
// If needed, additional invoice specific fields would be added here
|
|
146
148
|
}
|
|
@@ -247,7 +249,7 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
247
249
|
* @param parentElement Parent element
|
|
248
250
|
* @param invoice Invoice data
|
|
249
251
|
*/
|
|
250
|
-
private addPaymentTerms(doc: Document, parentElement: Element, invoice:
|
|
252
|
+
private addPaymentTerms(doc: Document, parentElement: Element, invoice: TAccountingDoc): void {
|
|
251
253
|
const paymentTermsNode = doc.createElement('cac:PaymentTerms');
|
|
252
254
|
parentElement.appendChild(paymentTermsNode);
|
|
253
255
|
|
|
@@ -266,7 +268,7 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
266
268
|
* @param parentElement Parent element
|
|
267
269
|
* @param invoice Invoice data
|
|
268
270
|
*/
|
|
269
|
-
private addPaymentMeans(doc: Document, parentElement: Element, invoice:
|
|
271
|
+
private addPaymentMeans(doc: Document, parentElement: Element, invoice: TAccountingDoc): void {
|
|
270
272
|
const paymentOptions = invoice.paymentOptions;
|
|
271
273
|
if (!paymentOptions) {
|
|
272
274
|
return;
|
|
@@ -315,7 +317,7 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
315
317
|
* @param parentElement Parent element
|
|
316
318
|
* @param invoice Invoice data
|
|
317
319
|
*/
|
|
318
|
-
private addTaxTotal(doc: Document, parentElement: Element, invoice:
|
|
320
|
+
private addTaxTotal(doc: Document, parentElement: Element, invoice: TAccountingDoc): void {
|
|
319
321
|
const taxTotalNode = doc.createElement('cac:TaxTotal');
|
|
320
322
|
parentElement.appendChild(taxTotalNode);
|
|
321
323
|
|
|
@@ -392,7 +394,7 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
392
394
|
* @param parentElement Parent element
|
|
393
395
|
* @param invoice Invoice data
|
|
394
396
|
*/
|
|
395
|
-
private addLegalMonetaryTotal(doc: Document, parentElement: Element, invoice:
|
|
397
|
+
private addLegalMonetaryTotal(doc: Document, parentElement: Element, invoice: TAccountingDoc): void {
|
|
396
398
|
const legalMonetaryTotalNode = doc.createElement('cac:LegalMonetaryTotal');
|
|
397
399
|
parentElement.appendChild(legalMonetaryTotalNode);
|
|
398
400
|
|
|
@@ -444,7 +446,7 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
444
446
|
* @param parentElement Parent element
|
|
445
447
|
* @param invoice Invoice data
|
|
446
448
|
*/
|
|
447
|
-
private addInvoiceLines(doc: Document, parentElement: Element, invoice:
|
|
449
|
+
private addInvoiceLines(doc: Document, parentElement: Element, invoice: TAccountingDoc, documentType: UBLDocumentType = UBLDocumentType.INVOICE): void {
|
|
448
450
|
if (!invoice.items) return;
|
|
449
451
|
const creditNote = documentType === UBLDocumentType.CREDIT_NOTE;
|
|
450
452
|
|
|
@@ -457,7 +459,10 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
457
459
|
|
|
458
460
|
// Invoiced quantity
|
|
459
461
|
const quantityElement = doc.createElement(creditNote ? 'cbc:CreditedQuantity' : 'cbc:InvoicedQuantity');
|
|
460
|
-
|
|
462
|
+
// a line without a unit is written without one, so that BR-23 reports it
|
|
463
|
+
if (item.unitType) {
|
|
464
|
+
quantityElement.setAttribute('unitCode', item.unitType);
|
|
465
|
+
}
|
|
461
466
|
quantityElement.textContent = item.unitQuantity.toString();
|
|
462
467
|
invoiceLineNode.appendChild(quantityElement);
|
|
463
468
|
|
|
@@ -544,7 +549,7 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
544
549
|
* @param root Root element
|
|
545
550
|
* @param invoice Invoice data
|
|
546
551
|
*/
|
|
547
|
-
private preserveMetadata(doc: Document, root: Element, invoice:
|
|
552
|
+
private preserveMetadata(doc: Document, root: Element, invoice: TAccountingDoc): void {
|
|
548
553
|
// Extract metadata if available
|
|
549
554
|
const metadata = (invoice as any).metadata?.extensions;
|
|
550
555
|
if (!metadata) return;
|
|
@@ -815,7 +820,7 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
815
820
|
* @param doc XML document
|
|
816
821
|
* @param invoice Invoice data
|
|
817
822
|
*/
|
|
818
|
-
private enhancePartyInformationUBL(doc: Document, invoice:
|
|
823
|
+
private enhancePartyInformationUBL(doc: Document, invoice: TAccountingDoc): void {
|
|
819
824
|
// Enhance supplier party
|
|
820
825
|
this.enhancePartyUBL(doc, 'cac:AccountingSupplierParty', invoice.from);
|
|
821
826
|
|
|
@@ -940,7 +945,7 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
940
945
|
* @param doc XML document
|
|
941
946
|
* @param invoice Invoice data
|
|
942
947
|
*/
|
|
943
|
-
private enhanceLineItemsUBL(doc: Document, invoice:
|
|
948
|
+
private enhanceLineItemsUBL(doc: Document, invoice: TAccountingDoc): void {
|
|
944
949
|
const invoiceLines = doc.getElementsByTagName('cac:InvoiceLine');
|
|
945
950
|
|
|
946
951
|
for (let i = 0; i < invoiceLines.length && i < invoice.items.length; i++) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { BaseDecoder } from '../base/base.decoder.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { TAccountingDoc, TCreditNote, TInvoiceDocument } from '../../interfaces/common.js';
|
|
3
3
|
import { UBLDocumentType, UBL_NAMESPACES } from './ubl.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 ublParser = new DOMParser();
|
|
8
9
|
const ublNamespaces = {
|
|
@@ -37,18 +38,23 @@ export abstract class UBLBaseDecoder extends BaseDecoder {
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
|
-
* Decodes UBL XML into
|
|
41
|
-
* @returns Promise resolving to
|
|
41
|
+
* Decodes UBL XML into an accounting document
|
|
42
|
+
* @returns Promise resolving to the accounting document
|
|
42
43
|
*/
|
|
43
|
-
public async decode(): Promise<
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (documentType === UBLDocumentType.CREDIT_NOTE) {
|
|
48
|
-
return this.decodeCreditNote() as unknown as TInvoice;
|
|
49
|
-
} else {
|
|
50
|
-
return this.decodeDebitNote() as unknown as TInvoice;
|
|
44
|
+
public async decode(): Promise<TAccountingDoc> {
|
|
45
|
+
// a CreditNote root is a credit note whatever its type code says
|
|
46
|
+
if (this.getDocumentType() === UBLDocumentType.CREDIT_NOTE) {
|
|
47
|
+
return this.decodeCreditNote();
|
|
51
48
|
}
|
|
49
|
+
|
|
50
|
+
// an Invoice root takes its type from the document type code (BT-3)
|
|
51
|
+
const accountingDocType = getAccountingDocType(
|
|
52
|
+
this.getText('./cbc:InvoiceTypeCode', this.doc.documentElement),
|
|
53
|
+
);
|
|
54
|
+
if (accountingDocType === 'creditnote') {
|
|
55
|
+
return this.decodeCreditNote();
|
|
56
|
+
}
|
|
57
|
+
return this.decodeInvoice(accountingDocType);
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
/**
|
|
@@ -83,10 +89,13 @@ export abstract class UBLBaseDecoder extends BaseDecoder {
|
|
|
83
89
|
protected abstract decodeCreditNote(): Promise<TCreditNote>;
|
|
84
90
|
|
|
85
91
|
/**
|
|
86
|
-
* Decodes a UBL debit note
|
|
87
|
-
* @
|
|
92
|
+
* Decodes a UBL invoice document: an invoice, a debit note or a self-billed invoice
|
|
93
|
+
* @param accountingDocType The type the document type code stands for
|
|
94
|
+
* @returns Promise resolving to the invoice document
|
|
88
95
|
*/
|
|
89
|
-
protected abstract
|
|
96
|
+
protected abstract decodeInvoice(
|
|
97
|
+
accountingDocType: TInvoiceDocument['accountingDocType'],
|
|
98
|
+
): Promise<TInvoiceDocument>;
|
|
90
99
|
|
|
91
100
|
/**
|
|
92
101
|
* Gets a text value from an XPath expression
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BaseEncoder } from '../base/base.encoder.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { TAccountingDoc, TCreditNote, TInvoiceDocument } from '../../interfaces/common.js';
|
|
3
3
|
import { UBLDocumentType, UBL_NAMESPACES } from './ubl.types.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -7,16 +7,16 @@ import { UBLDocumentType, UBL_NAMESPACES } from './ubl.types.js';
|
|
|
7
7
|
*/
|
|
8
8
|
export abstract class UBLBaseEncoder extends BaseEncoder {
|
|
9
9
|
/**
|
|
10
|
-
* Encodes
|
|
11
|
-
* @param invoice
|
|
10
|
+
* Encodes an accounting document into UBL XML
|
|
11
|
+
* @param invoice Accounting document to encode
|
|
12
12
|
* @returns UBL XML string
|
|
13
13
|
*/
|
|
14
|
-
public async encode(invoice:
|
|
15
|
-
// a credit note
|
|
16
|
-
if (
|
|
17
|
-
return this.encodeCreditNote(invoice
|
|
14
|
+
public async encode(invoice: TAccountingDoc): Promise<string> {
|
|
15
|
+
// a credit note is a CreditNote document; an invoice, a debit note and a self-billed invoice are Invoice documents that differ in their type code
|
|
16
|
+
if (invoice.accountingDocType === 'creditnote') {
|
|
17
|
+
return this.encodeCreditNote(invoice);
|
|
18
18
|
}
|
|
19
|
-
return this.
|
|
19
|
+
return this.encodeInvoice(invoice);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/**
|
|
@@ -27,11 +27,11 @@ export abstract class UBLBaseEncoder extends BaseEncoder {
|
|
|
27
27
|
protected abstract encodeCreditNote(creditNote: TCreditNote): Promise<string>;
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
* Encodes
|
|
31
|
-
* @param
|
|
30
|
+
* Encodes an invoice document (invoice, debit note or self-billed invoice) into UBL XML
|
|
31
|
+
* @param invoice Invoice document to encode
|
|
32
32
|
* @returns UBL XML string
|
|
33
33
|
*/
|
|
34
|
-
protected abstract
|
|
34
|
+
protected abstract encodeInvoice(invoice: TInvoiceDocument): Promise<string>;
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* 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 { UBLDocumentType, UBL_NAMESPACES } from './ubl.types.js';
|
|
5
5
|
import { DOMParser, xpath } from '../../plugins.js';
|
|
6
|
+
import { meetsUnitCodeRule } from '../utils/unit.codes.js';
|
|
6
7
|
|
|
7
8
|
const ublValidatorParser = new DOMParser();
|
|
8
9
|
const ublValidatorNamespaces = {
|
|
@@ -104,6 +105,52 @@ export abstract class UBLBaseValidator extends BaseValidator {
|
|
|
104
105
|
*/
|
|
105
106
|
protected abstract validateStructure(): boolean;
|
|
106
107
|
|
|
108
|
+
/**
|
|
109
|
+
* BR-23: every invoice or credit note line has a unit code on its invoiced or credited quantity.
|
|
110
|
+
* @returns True if every line has one
|
|
111
|
+
*/
|
|
112
|
+
protected validateLineUnitCodePresence(): boolean {
|
|
113
|
+
// BR-23: every invoice or credit note line (BG-25) has an invoiced quantity unit of measure code (BT-130)
|
|
114
|
+
const lines = this.select('//cac:InvoiceLine | //cac:CreditNoteLine', this.doc) as Element[];
|
|
115
|
+
let valid = true;
|
|
116
|
+
for (const line of lines) {
|
|
117
|
+
if (!this.exists('./cbc:InvoicedQuantity/@unitCode | ./cbc:CreditedQuantity/@unitCode', line)) {
|
|
118
|
+
this.addError(
|
|
119
|
+
'BR-23',
|
|
120
|
+
'An Invoice line (BG-25) shall have an Invoiced quantity unit of measure code (BT-130)',
|
|
121
|
+
this.locationOf(line)
|
|
122
|
+
);
|
|
123
|
+
valid = false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return valid;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* BR-CL-23: every unit code of an invoiced, credited or base quantity (BT-130, BT-150) is a
|
|
131
|
+
* code of UN/ECE Recommendation 20 with the Recommendation 21 extension.
|
|
132
|
+
* @returns True if every unit code passed
|
|
133
|
+
*/
|
|
134
|
+
protected validateUnitCodes(): boolean {
|
|
135
|
+
const quantities = this.select(
|
|
136
|
+
'//cbc:InvoicedQuantity[@unitCode] | //cbc:CreditedQuantity[@unitCode] | //cbc:BaseQuantity[@unitCode]',
|
|
137
|
+
this.doc
|
|
138
|
+
) as Element[];
|
|
139
|
+
let valid = true;
|
|
140
|
+
for (const quantity of quantities) {
|
|
141
|
+
const unitCode = quantity.getAttribute('unitCode') ?? '';
|
|
142
|
+
if (!meetsUnitCodeRule(unitCode)) {
|
|
143
|
+
this.addError(
|
|
144
|
+
'BR-CL-23',
|
|
145
|
+
`Unit code MUST be coded according to the UN/ECE Recommendation 20 with Rec 21 extension; found "${unitCode}"`,
|
|
146
|
+
`${this.locationOf(quantity)}/@unitCode`
|
|
147
|
+
);
|
|
148
|
+
valid = false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return valid;
|
|
152
|
+
}
|
|
153
|
+
|
|
107
154
|
/**
|
|
108
155
|
* Gets a text value from an XPath expression
|
|
109
156
|
* @param xpath XPath expression
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { UBLBaseDecoder } from '../ubl.decoder.js';
|
|
2
|
-
import type {
|
|
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 { UBLDocumentType } from '../ubl.types.js';
|
|
5
10
|
import { EN16931Validator } from '../../validation/en16931.validator.js';
|
|
@@ -14,36 +19,28 @@ export class XRechnungDecoder extends UBLBaseDecoder {
|
|
|
14
19
|
* @returns Promise resolving to a TCreditNote object
|
|
15
20
|
*/
|
|
16
21
|
protected async decodeCreditNote(): Promise<TCreditNote> {
|
|
17
|
-
|
|
18
|
-
const commonData = await this.extractCommonData();
|
|
19
|
-
|
|
20
|
-
// Return the invoice data as a credit note
|
|
21
|
-
return {
|
|
22
|
-
...commonData,
|
|
23
|
-
accountingDocType: 'creditnote' as const
|
|
24
|
-
} as unknown as TCreditNote;
|
|
22
|
+
return this.extractCommonData('creditnote');
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
/**
|
|
28
|
-
* Decodes a UBL debit note
|
|
29
|
-
* @
|
|
26
|
+
* Decodes a UBL invoice document: an invoice, a debit note or a self-billed invoice
|
|
27
|
+
* @param accountingDocType The type the document type code stands for
|
|
28
|
+
* @returns Promise resolving to the invoice document
|
|
30
29
|
*/
|
|
31
|
-
protected async
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// Return the invoice data as a debit note
|
|
36
|
-
return {
|
|
37
|
-
...commonData,
|
|
38
|
-
accountingDocType: 'debitnote' as const
|
|
39
|
-
} as unknown as TDebitNote;
|
|
30
|
+
protected async decodeInvoice(
|
|
31
|
+
accountingDocType: TInvoiceDocument['accountingDocType'],
|
|
32
|
+
): Promise<TInvoiceDocument> {
|
|
33
|
+
return this.extractCommonData(accountingDocType);
|
|
40
34
|
}
|
|
41
35
|
|
|
42
36
|
/**
|
|
43
37
|
* Extracts common invoice data from XRechnung XML
|
|
38
|
+
* @param accountingDocType The type the document is decoded as
|
|
44
39
|
* @returns Common invoice data
|
|
45
40
|
*/
|
|
46
|
-
private async extractCommonData
|
|
41
|
+
private async extractCommonData<TYPE extends TAccountingDocType>(
|
|
42
|
+
accountingDocType: TYPE,
|
|
43
|
+
): Promise<TAccountingDocEnvelope<TYPE, {}>> {
|
|
47
44
|
try {
|
|
48
45
|
const root = this.doc.documentElement;
|
|
49
46
|
const invoiceId = this.getRequiredText(
|
|
@@ -86,7 +83,8 @@ export class XRechnungDecoder extends UBLBaseDecoder {
|
|
|
86
83
|
const buyerItemID = this.getText('./cac:Item/cac:BuyersItemIdentification/cbc:ID', line) || '';
|
|
87
84
|
const standardItemID = this.getText('./cac:Item/cac:StandardItemIdentification/cbc:ID', line) || '';
|
|
88
85
|
const commodityClassification = this.getText('./cac:Item/cac:CommodityClassification/cbc:ItemClassificationCode', line) || '';
|
|
89
|
-
|
|
86
|
+
// an absent unit code (BT-130) stays absent, so that BR-23 reports it
|
|
87
|
+
const unitType = this.getText(`./${quantityElement}/@unitCode`, line);
|
|
90
88
|
|
|
91
89
|
let unitQuantity = 1;
|
|
92
90
|
const quantityText = this.getText(`./${quantityElement}`, line);
|
|
@@ -236,10 +234,12 @@ export class XRechnungDecoder extends UBLBaseDecoder {
|
|
|
236
234
|
// Create the common invoice data with metadata only when the source XML actually contains it.
|
|
237
235
|
const invoiceData: any = {
|
|
238
236
|
type: 'accounting-doc' as const,
|
|
239
|
-
accountingDocType
|
|
237
|
+
accountingDocType,
|
|
240
238
|
id: invoiceId,
|
|
241
239
|
accountingDocId: invoiceId,
|
|
242
240
|
date: issueDate,
|
|
241
|
+
// a document read from XML has been issued; the envelope carries that as the document and the accounting status
|
|
242
|
+
status: 'issued',
|
|
243
243
|
accountingDocStatus: 'issued' as const,
|
|
244
244
|
versionInfo: {
|
|
245
245
|
type: 'final',
|