@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,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 { 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
|
|
46
|
-
* @returns Promise resolving to
|
|
46
|
+
* Decodes CII XML into an accounting document
|
|
47
|
+
* @returns Promise resolving to the accounting document
|
|
47
48
|
*/
|
|
48
|
-
public async decode(): Promise<
|
|
49
|
-
//
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
|
94
|
-
* @
|
|
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
|
|
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 {
|
|
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
|
|
21
|
-
* @param invoice
|
|
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:
|
|
25
|
-
//
|
|
26
|
-
if (
|
|
27
|
-
return this.encodeCreditNote(invoice
|
|
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.
|
|
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
|
|
41
|
-
* @param
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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
|
|
28
|
-
* @
|
|
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
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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,
|
|
24
|
+
this.setDocumentTypeCode(xmlDoc, getDocumentTypeCode(creditNote.accountingDocType));
|
|
24
25
|
|
|
25
26
|
// Add common invoice data
|
|
26
|
-
this.addCommonInvoiceData(xmlDoc, creditNote
|
|
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
|
|
34
|
-
* @param
|
|
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
|
|
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
|
|
42
|
-
this.setDocumentTypeCode(xmlDoc,
|
|
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,
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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(
|
|
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 {
|
|
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
|
-
|
|
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
|
|
27
|
-
* @
|
|
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
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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,
|
|
28
|
+
this.setDocumentTypeCode(xmlDoc, getDocumentTypeCode(creditNote.accountingDocType));
|
|
28
29
|
|
|
29
30
|
// Add common invoice data
|
|
30
|
-
this.addCommonInvoiceData(xmlDoc, creditNote
|
|
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
|
|
38
|
-
* @param
|
|
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
|
|
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
|
|
46
|
-
this.setDocumentTypeCode(xmlDoc,
|
|
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,
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
|