@fin.cx/einvoice 8.3.1 → 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +2 -2
- package/dist_ts/einvoice.d.ts +26 -0
- package/dist_ts/einvoice.js +61 -3
- package/dist_ts/formats/base/base.decoder.d.ts +18 -1
- package/dist_ts/formats/base/base.decoder.js +30 -17
- package/dist_ts/formats/cii/cii.decoder.d.ts +89 -6
- package/dist_ts/formats/cii/cii.decoder.js +125 -30
- package/dist_ts/formats/cii/cii.encoder.d.ts +71 -1
- package/dist_ts/formats/cii/cii.encoder.js +195 -12
- package/dist_ts/formats/cii/cii.types.d.ts +2 -5
- package/dist_ts/formats/cii/cii.types.js +7 -9
- package/dist_ts/formats/cii/cii.validator.d.ts +13 -6
- package/dist_ts/formats/cii/cii.validator.js +29 -30
- package/dist_ts/formats/cii/facturx/facturx.decoder.js +19 -3
- package/dist_ts/formats/cii/facturx/facturx.encoder.d.ts +0 -6
- package/dist_ts/formats/cii/facturx/facturx.encoder.js +17 -26
- package/dist_ts/formats/cii/facturx/facturx.types.d.ts +0 -5
- package/dist_ts/formats/cii/facturx/facturx.types.js +1 -8
- package/dist_ts/formats/cii/facturx/facturx.validator.d.ts +0 -5
- package/dist_ts/formats/cii/facturx/facturx.validator.js +5 -29
- package/dist_ts/formats/cii/zugferd/zugferd.decoder.js +19 -2
- package/dist_ts/formats/cii/zugferd/zugferd.encoder.d.ts +0 -7
- package/dist_ts/formats/cii/zugferd/zugferd.encoder.js +15 -68
- package/dist_ts/formats/cii/zugferd/zugferd.types.d.ts +0 -5
- package/dist_ts/formats/cii/zugferd/zugferd.types.js +1 -8
- package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.d.ts +26 -0
- package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.js +47 -1
- package/dist_ts/formats/cii/zugferd/zugferd.validator.js +6 -2
- package/dist_ts/formats/semantic/semantic.adapter.js +29 -20
- package/dist_ts/formats/semantic/semantic.validator.js +17 -2
- package/dist_ts/formats/ubl/en16931.ubl.validator.d.ts +0 -4
- package/dist_ts/formats/ubl/en16931.ubl.validator.js +5 -42
- package/dist_ts/formats/ubl/generic/ubl.encoder.js +29 -26
- package/dist_ts/formats/ubl/ubl.decoder.d.ts +4 -0
- package/dist_ts/formats/ubl/ubl.decoder.js +9 -1
- package/dist_ts/formats/ubl/ubl.encoder.js +13 -5
- package/dist_ts/formats/ubl/ubl.validator.d.ts +13 -0
- package/dist_ts/formats/ubl/ubl.validator.js +28 -1
- package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.js +14 -1
- package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.d.ts +0 -7
- package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.js +30 -45
- package/dist_ts/formats/ubl/xrechnung.validator.d.ts +5 -4
- package/dist_ts/formats/ubl/xrechnung.validator.js +71 -68
- package/dist_ts/formats/utils/date.value.d.ts +11 -0
- package/dist_ts/formats/utils/date.value.js +33 -1
- package/dist_ts/formats/utils/document.totals.d.ts +1 -3
- package/dist_ts/formats/utils/document.totals.js +1 -2
- package/dist_ts/formats/utils/format.detector.js +16 -10
- package/dist_ts/formats/utils/paid.amount.d.ts +44 -0
- package/dist_ts/formats/utils/paid.amount.js +124 -0
- package/dist_ts/formats/utils/party.contact.d.ts +16 -0
- package/dist_ts/formats/utils/party.contact.js +16 -0
- package/dist_ts/formats/utils/party.identifier.d.ts +28 -0
- package/dist_ts/formats/utils/party.identifier.js +49 -0
- package/dist_ts/formats/utils/peppol.profile.d.ts +10 -0
- package/dist_ts/formats/utils/peppol.profile.js +12 -0
- package/dist_ts/formats/utils/preceding.invoice.d.ts +12 -2
- package/dist_ts/formats/utils/preceding.invoice.js +22 -3
- package/dist_ts/formats/utils/seller.identifier.d.ts +46 -0
- package/dist_ts/formats/utils/seller.identifier.js +78 -0
- package/dist_ts/formats/utils/stated.values.d.ts +80 -0
- package/dist_ts/formats/utils/stated.values.js +418 -0
- package/dist_ts/formats/utils/vat.category.d.ts +30 -2
- package/dist_ts/formats/utils/vat.category.js +36 -6
- package/dist_ts/formats/utils/vat.id.d.ts +18 -0
- package/dist_ts/formats/utils/vat.id.js +22 -0
- package/dist_ts/formats/validation/conformance.harness.js +6 -6
- package/dist_ts/formats/validation/en16931.business-rules.validator.js +4 -19
- package/dist_ts/formats/validation/facturx.validator.js +6 -6
- package/dist_ts/formats/validation/integrated.validator.js +4 -13
- package/dist_ts/formats/validation/peppol.validator.js +6 -13
- package/dist_ts/formats/validation/validation.types.d.ts +5 -0
- package/dist_ts/formats/validation/validation.types.js +6 -1
- package/dist_ts/formats/validation/vat-categories.validator.d.ts +21 -42
- package/dist_ts/formats/validation/vat-categories.validator.js +137 -431
- package/dist_ts/formats/validation/xrechnung.validator.d.ts +11 -58
- package/dist_ts/formats/validation/xrechnung.validator.js +58 -324
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +1 -1
- package/dist_ts/interfaces/common.d.ts +1 -0
- package/dist_ts/interfaces/en16931-metadata.d.ts +0 -5
- package/dist_ts/interfaces/stated.values.d.ts +93 -0
- package/dist_ts/interfaces/stated.values.js +2 -0
- package/package.json +2 -2
- package/readme.md +182 -6
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/einvoice.ts +70 -2
- package/ts/formats/base/base.decoder.ts +32 -29
- package/ts/formats/cii/cii.decoder.ts +159 -32
- package/ts/formats/cii/cii.encoder.ts +210 -14
- package/ts/formats/cii/cii.types.ts +7 -9
- package/ts/formats/cii/cii.validator.ts +30 -32
- package/ts/formats/cii/facturx/facturx.decoder.ts +21 -2
- package/ts/formats/cii/facturx/facturx.encoder.ts +19 -26
- package/ts/formats/cii/facturx/facturx.types.ts +0 -9
- package/ts/formats/cii/facturx/facturx.validator.ts +5 -43
- package/ts/formats/cii/zugferd/zugferd.decoder.ts +21 -1
- package/ts/formats/cii/zugferd/zugferd.encoder.ts +16 -73
- package/ts/formats/cii/zugferd/zugferd.types.ts +0 -9
- package/ts/formats/cii/zugferd/zugferd.v1.decoder.ts +66 -0
- package/ts/formats/cii/zugferd/zugferd.validator.ts +5 -1
- package/ts/formats/semantic/semantic.adapter.ts +28 -19
- package/ts/formats/semantic/semantic.validator.ts +17 -1
- package/ts/formats/ubl/en16931.ubl.validator.ts +5 -64
- package/ts/formats/ubl/generic/ubl.encoder.ts +31 -25
- package/ts/formats/ubl/ubl.decoder.ts +12 -0
- package/ts/formats/ubl/ubl.encoder.ts +12 -4
- package/ts/formats/ubl/ubl.validator.ts +29 -0
- package/ts/formats/ubl/xrechnung/xrechnung.decoder.ts +13 -0
- package/ts/formats/ubl/xrechnung/xrechnung.encoder.ts +33 -45
- package/ts/formats/ubl/xrechnung.validator.ts +75 -127
- package/ts/formats/utils/date.value.ts +33 -0
- package/ts/formats/utils/document.totals.ts +1 -4
- package/ts/formats/utils/format.detector.ts +15 -9
- package/ts/formats/utils/paid.amount.ts +160 -0
- package/ts/formats/utils/party.contact.ts +30 -0
- package/ts/formats/utils/party.identifier.ts +61 -0
- package/ts/formats/utils/peppol.profile.ts +13 -0
- package/ts/formats/utils/preceding.invoice.ts +23 -2
- package/ts/formats/utils/seller.identifier.ts +102 -0
- package/ts/formats/utils/stated.values.ts +499 -0
- package/ts/formats/utils/vat.category.ts +47 -5
- package/ts/formats/utils/vat.id.ts +24 -0
- package/ts/formats/validation/conformance.harness.ts +5 -5
- package/ts/formats/validation/en16931.business-rules.validator.ts +3 -26
- package/ts/formats/validation/facturx.validator.ts +5 -5
- package/ts/formats/validation/integrated.validator.ts +3 -16
- package/ts/formats/validation/peppol.validator.ts +5 -16
- package/ts/formats/validation/validation.types.ts +7 -1
- package/ts/formats/validation/vat-categories.validator.ts +179 -761
- package/ts/formats/validation/xrechnung.validator.ts +61 -382
- package/ts/index.ts +9 -0
- package/ts/interfaces/common.ts +1 -0
- package/ts/interfaces/en16931-metadata.ts +3 -8
- package/ts/interfaces/stated.values.ts +94 -0
- package/ts/readme.md +1 -1
- package/dist_ts/formats/utils/eu.memberstates.d.ts +0 -11
- package/dist_ts/formats/utils/eu.memberstates.js +0 -16
- package/ts/formats/utils/eu.memberstates.ts +0 -16
|
@@ -1,31 +1,11 @@
|
|
|
1
1
|
import { EN16931UBLValidator } from './en16931.ubl.validator.js';
|
|
2
|
+
import { GERMAN_VAT_ID_FORMAT_CODE, germanVatIdFormatMessage, isMalformedGermanVatId } from '../utils/vat.id.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* XRechnung-specific validator that extends EN16931 validation
|
|
5
6
|
* Implements additional German CIUS (Core Invoice Usage Specification) rules
|
|
6
7
|
*/
|
|
7
8
|
export class XRechnungValidator extends EN16931UBLValidator {
|
|
8
|
-
/**
|
|
9
|
-
* Validates XRechnung-specific structure requirements
|
|
10
|
-
*/
|
|
11
|
-
protected validateStructure(): boolean {
|
|
12
|
-
// First validate EN16931 structure
|
|
13
|
-
let valid = super.validateStructure();
|
|
14
|
-
|
|
15
|
-
// XRechnung-specific: Check for proper customization ID
|
|
16
|
-
const customizationID = this.getText('//cbc:CustomizationID');
|
|
17
|
-
if (!customizationID || !customizationID.includes('xrechnung')) {
|
|
18
|
-
this.addError(
|
|
19
|
-
'XRECH-STRUCT-1',
|
|
20
|
-
'XRechnung customization ID is missing or invalid',
|
|
21
|
-
'//cbc:CustomizationID'
|
|
22
|
-
);
|
|
23
|
-
valid = false;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return valid;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
9
|
/**
|
|
30
10
|
* Validates XRechnung-specific business rules
|
|
31
11
|
*/
|
|
@@ -33,26 +13,6 @@ export class XRechnungValidator extends EN16931UBLValidator {
|
|
|
33
13
|
// First validate EN16931 business rules
|
|
34
14
|
let valid = super.validateBusinessRules();
|
|
35
15
|
|
|
36
|
-
// BR-DE-1: Payment terms (BT-20) or Payment due date (BT-9) shall be provided.
|
|
37
|
-
if (!this.exists('//cbc:PaymentDueDate') && !this.exists('//cac:PaymentTerms/cbc:Note')) {
|
|
38
|
-
this.addError(
|
|
39
|
-
'BR-DE-1',
|
|
40
|
-
'Payment terms or Payment due date shall be provided',
|
|
41
|
-
'//cac:PaymentTerms'
|
|
42
|
-
);
|
|
43
|
-
valid = false;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// BR-DE-2: The element "Buyer reference" (BT-10) shall be provided.
|
|
47
|
-
if (!this.exists('//cbc:BuyerReference')) {
|
|
48
|
-
this.addError(
|
|
49
|
-
'BR-DE-2',
|
|
50
|
-
'Buyer reference is required in XRechnung',
|
|
51
|
-
'//cbc:BuyerReference'
|
|
52
|
-
);
|
|
53
|
-
valid = false;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
16
|
// BR-DE-16 (XRechnung): when a VAT category code S, Z, E, AE, K, G, L or M is used (BT-95,
|
|
57
17
|
// BT-102, BT-151), the seller states a VAT identifier (BT-31) or a tax registration
|
|
58
18
|
// identifier (BT-32), which is any party tax scheme of the seller, or a tax representative
|
|
@@ -79,111 +39,99 @@ export class XRechnungValidator extends EN16931UBLValidator {
|
|
|
79
39
|
valid = false;
|
|
80
40
|
}
|
|
81
41
|
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
|
|
42
|
+
// The rules below carry the IDs of the official rule sets and check what they check, in
|
|
43
|
+
// their context: KoSIT XRechnung Schematron 2.6.0 (BR-DE-*, and PEPPOL-EN16931-R010, which it
|
|
44
|
+
// includes) and the CEN EN 16931 validation artefacts 1.3.16 (BR-21, BR-49).
|
|
45
|
+
const supplierParty = '/*/cac:AccountingSupplierParty/cac:Party';
|
|
46
|
+
const customerParty = '/*/cac:AccountingCustomerParty/cac:Party';
|
|
47
|
+
const requireElement = (context: string, element: string, ruleId: string, message: string): void => {
|
|
48
|
+
for (const node of this.nodes(context)) {
|
|
49
|
+
if (!this.exists(`${element}[normalize-space(.)]`, node)) {
|
|
50
|
+
this.addError(ruleId, message, `${context}/${element}`);
|
|
51
|
+
valid = false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
91
55
|
|
|
92
|
-
// BR-DE-
|
|
93
|
-
if (!this.exists('
|
|
94
|
-
this.addError(
|
|
95
|
-
'BR-DE-8',
|
|
96
|
-
'Seller post code is required',
|
|
97
|
-
'//cac:AccountingSupplierParty//cac:PostalAddress'
|
|
98
|
-
);
|
|
56
|
+
// BR-DE-1: an invoice states PAYMENT INSTRUCTIONS (BG-16)
|
|
57
|
+
if (!this.exists('/*/cac:PaymentMeans')) {
|
|
58
|
+
this.addError('BR-DE-1', 'An invoice states the payment instructions (BG-16)', '/*/cac:PaymentMeans');
|
|
99
59
|
valid = false;
|
|
100
60
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
'//cac:AccountingCustomerParty//cac:PostalAddress'
|
|
108
|
-
);
|
|
109
|
-
valid = false;
|
|
61
|
+
// BR-49 (EN 16931): a payment instruction (BG-16) states the payment means type code (BT-81)
|
|
62
|
+
for (const [index, paymentMeans] of this.nodes('/*/cac:PaymentMeans').entries()) {
|
|
63
|
+
if (!this.exists('cbc:PaymentMeansCode', paymentMeans)) {
|
|
64
|
+
this.addError('BR-49', 'A payment instruction (BG-16) states the payment means type code (BT-81)', `/*/cac:PaymentMeans[${index + 1}]/cbc:PaymentMeansCode`);
|
|
65
|
+
valid = false;
|
|
66
|
+
}
|
|
110
67
|
}
|
|
111
68
|
|
|
112
|
-
// BR-DE-
|
|
113
|
-
|
|
114
|
-
this.addError(
|
|
115
|
-
'BR-DE-10',
|
|
116
|
-
'Buyer post code is required',
|
|
117
|
-
'//cac:AccountingCustomerParty//cac:PostalAddress'
|
|
118
|
-
);
|
|
119
|
-
valid = false;
|
|
120
|
-
}
|
|
69
|
+
// BR-DE-15: the buyer reference (BT-10) is stated
|
|
70
|
+
requireElement('/*', 'cbc:BuyerReference', 'BR-DE-15', 'The buyer reference (BT-10) is stated');
|
|
121
71
|
|
|
122
|
-
// BR-DE-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
'Seller contact telephone number is required',
|
|
127
|
-
'//cac:AccountingSupplierParty//cac:Contact'
|
|
128
|
-
);
|
|
72
|
+
// BR-DE-2: the seller contact (BG-6) is stated; BR-DE-6 and BR-DE-7: with a telephone number
|
|
73
|
+
// (BT-42) and an email address (BT-43)
|
|
74
|
+
if (this.exists(supplierParty) && !this.exists(`${supplierParty}/cac:Contact`)) {
|
|
75
|
+
this.addError('BR-DE-2', 'The seller contact (BG-6) is stated', `${supplierParty}/cac:Contact`);
|
|
129
76
|
valid = false;
|
|
130
77
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
78
|
+
requireElement(`${supplierParty}/cac:Contact`, 'cbc:Telephone', 'BR-DE-6', 'The seller contact telephone number (BT-42) is stated');
|
|
79
|
+
requireElement(`${supplierParty}/cac:Contact`, 'cbc:ElectronicMail', 'BR-DE-7', 'The seller contact email address (BT-43) is stated');
|
|
80
|
+
|
|
81
|
+
// BR-DE-3, BR-DE-4: the seller city (BT-37) and post code (BT-38)
|
|
82
|
+
requireElement(`${supplierParty}/cac:PostalAddress`, 'cbc:CityName', 'BR-DE-3', 'The seller city (BT-37) is stated');
|
|
83
|
+
requireElement(`${supplierParty}/cac:PostalAddress`, 'cbc:PostalZone', 'BR-DE-4', 'The seller post code (BT-38) is stated');
|
|
84
|
+
// BR-DE-8, BR-DE-9: the buyer city (BT-52) and post code (BT-53)
|
|
85
|
+
requireElement(`${customerParty}/cac:PostalAddress`, 'cbc:CityName', 'BR-DE-8', 'The buyer city (BT-52) is stated');
|
|
86
|
+
requireElement(`${customerParty}/cac:PostalAddress`, 'cbc:PostalZone', 'BR-DE-9', 'The buyer post code (BT-53) is stated');
|
|
87
|
+
|
|
88
|
+
// PEPPOL-EN16931-R010, part of the XRechnung rules: the buyer electronic address (BT-49)
|
|
89
|
+
for (const party of this.nodes(customerParty)) {
|
|
90
|
+
if (!this.exists('cbc:EndpointID', party)) {
|
|
91
|
+
this.addError('PEPPOL-EN16931-R010', 'The buyer electronic address (BT-49) is stated', `${customerParty}/cbc:EndpointID`);
|
|
92
|
+
valid = false;
|
|
93
|
+
}
|
|
140
94
|
}
|
|
141
95
|
|
|
142
|
-
// BR-
|
|
143
|
-
|
|
144
|
-
this.
|
|
145
|
-
'BR-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
);
|
|
149
|
-
valid = false;
|
|
96
|
+
// BR-21 (EN 16931): each invoice line (BG-25) has an invoice line identifier (BT-126)
|
|
97
|
+
for (const [index, line] of this.nodes('/*/cac:InvoiceLine | /*/cac:CreditNoteLine').entries()) {
|
|
98
|
+
if (!this.exists('cbc:ID[normalize-space(.)]', line)) {
|
|
99
|
+
this.addError('BR-21', `Invoice line ${index + 1} has an invoice line identifier (BT-126)`, `${this.locationOf(line as Element)}/cbc:ID`);
|
|
100
|
+
valid = false;
|
|
101
|
+
}
|
|
150
102
|
}
|
|
151
103
|
|
|
152
|
-
// BR-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
)
|
|
104
|
+
// BR-CO-25 (EN 16931): while the amount due for payment (BT-115) is positive, the payment due
|
|
105
|
+
// date (BT-9) or the payment terms (BT-20) are stated. The CEN artefacts for UBL and CII do not
|
|
106
|
+
// check this rule; EN 16931 states it.
|
|
107
|
+
const amountDue = Number(this.getText('/*/cac:LegalMonetaryTotal/cbc:PayableAmount').trim());
|
|
108
|
+
if (
|
|
109
|
+
amountDue > 0 &&
|
|
110
|
+
!this.exists('/*/cbc:DueDate[normalize-space(.)] | /*/cac:PaymentMeans/cbc:PaymentDueDate[normalize-space(.)]') &&
|
|
111
|
+
!this.exists('/*/cac:PaymentTerms/cbc:Note[normalize-space(.)]')
|
|
112
|
+
) {
|
|
113
|
+
this.addError('BR-CO-25', 'While an amount is due (BT-115), the payment due date (BT-9) or the payment terms (BT-20) are stated', '/*/cac:PaymentTerms');
|
|
159
114
|
valid = false;
|
|
160
115
|
}
|
|
161
116
|
|
|
162
|
-
//
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
this.addError(
|
|
168
|
-
'BR-DE-15',
|
|
169
|
-
`Invoice line ${i + 1} is missing identifier`,
|
|
170
|
-
`//cac:InvoiceLine[${i + 1}]`
|
|
171
|
-
);
|
|
117
|
+
// The package's own check of the German VAT identification number format (no official rule)
|
|
118
|
+
for (const vatId of this.nodes('/*/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme[cac:TaxScheme/cbc:ID="VAT"]/cbc:CompanyID')) {
|
|
119
|
+
const value = (vatId.textContent ?? '').trim();
|
|
120
|
+
if (isMalformedGermanVatId(value)) {
|
|
121
|
+
this.addError(GERMAN_VAT_ID_FORMAT_CODE, germanVatIdFormatMessage(value), '/*/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID');
|
|
172
122
|
valid = false;
|
|
173
123
|
}
|
|
174
124
|
}
|
|
175
125
|
|
|
176
|
-
// German VAT ID format validation
|
|
177
|
-
const supplierVatId = this.getText('//cac:AccountingSupplierParty//cbc:CompanyID[../cac:TaxScheme/cbc:ID="VAT"]');
|
|
178
|
-
if (supplierVatId && supplierVatId.startsWith('DE') && !/^DE[0-9]{9}$/.test(supplierVatId)) {
|
|
179
|
-
this.addError(
|
|
180
|
-
'BR-DE-VAT',
|
|
181
|
-
'German VAT ID format is invalid (must be DE followed by 9 digits)',
|
|
182
|
-
'//cac:AccountingSupplierParty//cbc:CompanyID'
|
|
183
|
-
);
|
|
184
|
-
valid = false;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
126
|
return valid;
|
|
188
127
|
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The nodes an XPath expression selects
|
|
131
|
+
* @param xpathExpr XPath expression
|
|
132
|
+
*/
|
|
133
|
+
private nodes(xpathExpr: string): Node[] {
|
|
134
|
+
const result = this.select(xpathExpr, this.doc);
|
|
135
|
+
return Array.isArray(result) ? (result as Node[]) : [];
|
|
136
|
+
}
|
|
189
137
|
}
|
|
@@ -50,3 +50,36 @@ export const getWritableDueDate = (issueTimestamp: unknown, dueInDays: unknown,
|
|
|
50
50
|
dueDate.setUTCDate(dueDate.getUTCDate() + dueInDays);
|
|
51
51
|
return dueDate;
|
|
52
52
|
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The calendar day a UBL `xsd:date` names (BT-2, BT-9, BT-26, BT-72, BT-73,
|
|
56
|
+
* BT-74), as the UTC midnight of that day; undefined when the value is no such
|
|
57
|
+
* date. The value is `YYYY-MM-DD`, optionally followed by a time zone: `Z`, or
|
|
58
|
+
* `+hh:mm` / `-hh:mm` of at most 14:00. The zone says where the day is; it
|
|
59
|
+
* does not name another day, so `2026-08-01+02:00` and `2026-08-01-12:00` both
|
|
60
|
+
* name 1 August 2026. EN 16931 dates are calendar days without a zone, and CII
|
|
61
|
+
* format 102 has none, so the day is what every format reads and writes.
|
|
62
|
+
* @param value The date as the document states it
|
|
63
|
+
*/
|
|
64
|
+
export const parseXsdDateDay = (value: string): number | undefined => {
|
|
65
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})(?:Z|[+-](\d{2}):(\d{2}))?$/.exec(value.trim());
|
|
66
|
+
if (!match) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
const [year, month, day] = [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
70
|
+
if (match[4] !== undefined) {
|
|
71
|
+
const [zoneHours, zoneMinutes] = [Number(match[4]), Number(match[5])];
|
|
72
|
+
if (zoneHours > 14 || zoneMinutes > 59 || (zoneHours === 14 && zoneMinutes !== 0)) {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (year < 1) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
const date = new Date(0);
|
|
80
|
+
date.setUTCFullYear(year, month - 1, day);
|
|
81
|
+
if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
return date.getTime();
|
|
85
|
+
};
|
|
@@ -96,10 +96,8 @@ export interface IDocumentTotals {
|
|
|
96
96
|
vatGroups: IDocumentVatGroup[];
|
|
97
97
|
/** Invoice total VAT amount (BT-110): the sum of the rounded VAT category tax amounts */
|
|
98
98
|
taxTotal: Decimal;
|
|
99
|
-
/** Invoice total amount with VAT (BT-112): BT-109 + BT-110 */
|
|
99
|
+
/** Invoice total amount with VAT (BT-112): BT-109 + BT-110; the paid amount and the amount due are `getPaymentTotals` */
|
|
100
100
|
grandTotal: Decimal;
|
|
101
|
-
/** Amount due for payment (BT-115): BT-112, as the envelope states no paid amount yet */
|
|
102
|
-
duePayable: Decimal;
|
|
103
101
|
}
|
|
104
102
|
|
|
105
103
|
/**
|
|
@@ -168,6 +166,5 @@ export const computeDocumentTotals = (
|
|
|
168
166
|
vatGroups,
|
|
169
167
|
taxTotal,
|
|
170
168
|
grandTotal,
|
|
171
|
-
duePayable: grandTotal,
|
|
172
169
|
};
|
|
173
170
|
};
|
|
@@ -97,12 +97,19 @@ export class FormatDetector {
|
|
|
97
97
|
if (/xrechnung/i.test(guidelineId)) {
|
|
98
98
|
return InvoiceFormat.XRECHNUNG;
|
|
99
99
|
}
|
|
100
|
-
|
|
100
|
+
// an identifier that names Factur-X or ZUGFeRD decides; ZUGFeRD 2.0 BASIC and EXTENDED name ZUGFeRD
|
|
101
|
+
// after the EN 16931 identifier they extend (urn:cen.eu:en16931:2017#...#urn:zugferd.de:2p0:...)
|
|
102
|
+
if (/factur-x/i.test(guidelineId)) {
|
|
101
103
|
return InvoiceFormat.FACTURX;
|
|
102
104
|
}
|
|
103
|
-
if (/zugferd/i.test(guidelineId) || /urn:ferd:/i.test(guidelineId)
|
|
105
|
+
if (/zugferd/i.test(guidelineId) || /urn:ferd:/i.test(guidelineId)) {
|
|
104
106
|
return InvoiceFormat.ZUGFERD;
|
|
105
107
|
}
|
|
108
|
+
// the EN 16931 profile identifier is shared by Factur-X 1.0 and ZUGFeRD 2.x, one standard with one
|
|
109
|
+
// XML: an XML-only document of it reads as Factur-X
|
|
110
|
+
if (/urn:cen\.eu:en16931:2017/i.test(guidelineId)) {
|
|
111
|
+
return InvoiceFormat.FACTURX;
|
|
112
|
+
}
|
|
106
113
|
return InvoiceFormat.CII;
|
|
107
114
|
}
|
|
108
115
|
|
|
@@ -287,13 +294,11 @@ export class FormatDetector {
|
|
|
287
294
|
for (const idNode of Array.from(idNodes)) {
|
|
288
295
|
const profileText = idNode.textContent || '';
|
|
289
296
|
|
|
290
|
-
// Check for ZUGFeRD profiles
|
|
297
|
+
// Check for ZUGFeRD profiles: v1, the ZUGFeRD 2.0 identifiers (urn:zugferd.de:2p0:...) and the
|
|
298
|
+
// urn:zugferd:... identifiers earlier releases of this library wrote
|
|
291
299
|
if (
|
|
292
300
|
profileText.includes('zugferd') ||
|
|
293
301
|
profileText.includes('urn:ferd:') ||
|
|
294
|
-
profileText === CII_PROFILE_IDS.ZUGFERD_BASIC ||
|
|
295
|
-
profileText === CII_PROFILE_IDS.ZUGFERD_COMFORT ||
|
|
296
|
-
profileText === CII_PROFILE_IDS.ZUGFERD_EXTENDED ||
|
|
297
302
|
profileText === CII_PROFILE_IDS.ZUGFERD_V1_BASIC ||
|
|
298
303
|
profileText === CII_PROFILE_IDS.ZUGFERD_V1_COMFORT ||
|
|
299
304
|
profileText === CII_PROFILE_IDS.ZUGFERD_V1_EXTENDED
|
|
@@ -301,12 +306,13 @@ export class FormatDetector {
|
|
|
301
306
|
return InvoiceFormat.ZUGFERD;
|
|
302
307
|
}
|
|
303
308
|
|
|
304
|
-
// Check for Factur-X profiles
|
|
309
|
+
// Check for Factur-X profiles; the EN 16931 profile of ZUGFeRD 2.x has the same identifier, and
|
|
310
|
+
// an XML-only document of it is Factur-X: ZUGFeRD 2.x and Factur-X 1.0 are one standard
|
|
305
311
|
if (
|
|
306
312
|
profileText.includes('factur-x') ||
|
|
307
313
|
profileText === CII_PROFILE_IDS.FACTURX_MINIMUM ||
|
|
308
|
-
profileText === CII_PROFILE_IDS.
|
|
309
|
-
profileText === CII_PROFILE_IDS.
|
|
314
|
+
profileText === CII_PROFILE_IDS.FACTURX_BASIC_WL ||
|
|
315
|
+
profileText === CII_PROFILE_IDS.EN16931
|
|
310
316
|
) {
|
|
311
317
|
return InvoiceFormat.FACTURX;
|
|
312
318
|
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import type { TAccountingDoc, TAdvancePayment } from '../../interfaces/common.js';
|
|
2
|
+
import { EInvoiceFormatError, EInvoiceParsingError } from '../../errors.js';
|
|
3
|
+
import { Decimal } from './decimal.js';
|
|
4
|
+
import { toPlainDecimalString } from './number.text.js';
|
|
5
|
+
import type { IDocumentTotals } from './document.totals.js';
|
|
6
|
+
|
|
7
|
+
/** The paid amount (BT-113) and the amount due for payment (BT-115) an encoder writes */
|
|
8
|
+
export interface IPaymentTotals {
|
|
9
|
+
/** Paid amount (BT-113) as the document states it; undefined when it states none or zero, and nothing is written */
|
|
10
|
+
paidAmount?: Decimal;
|
|
11
|
+
/** Amount due for payment (BT-115): BT-112 − BT-113 (BR-CO-16; the envelope has no rounding amount BT-114); zero or negative when paid in full or overpaid */
|
|
12
|
+
duePayable: Decimal;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Why a paid amount cannot be written: the rule it breaks, the field and what is wrong */
|
|
16
|
+
export interface IPaymentProblem {
|
|
17
|
+
ruleId: string;
|
|
18
|
+
field: string;
|
|
19
|
+
message: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Carries a payment problem out of the computation */
|
|
23
|
+
class PaymentProblemSignal extends Error {
|
|
24
|
+
constructor(public readonly problem: IPaymentProblem) {
|
|
25
|
+
super(problem.message);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const refuse = (field: string, message: string, ruleId = 'PAID-AMOUNT'): never => {
|
|
30
|
+
throw new PaymentProblemSignal({ ruleId, field, message });
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** An amount as the document states it, at its full precision; a value that is no finite number is refused */
|
|
34
|
+
const statedDecimal = (value: unknown, field: string): Decimal => {
|
|
35
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
36
|
+
return refuse(field, `${field} is no finite number: ${String(value)}`);
|
|
37
|
+
}
|
|
38
|
+
return new Decimal(toPlainDecimalString(value));
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** The number of decimals of a stated amount */
|
|
42
|
+
const decimalsOf = (value: number): number => (toPlainDecimalString(value).split('.')[1] ?? '').length;
|
|
43
|
+
|
|
44
|
+
/** The document types that can be a final invoice (Endrechnung) and deduct advance payments */
|
|
45
|
+
const finalInvoiceTypes: TAccountingDoc['accountingDocType'][] = ['invoice', 'corrected-invoice', 'self-billed-invoice'];
|
|
46
|
+
|
|
47
|
+
/** What the paid amount and the amount due are computed from */
|
|
48
|
+
export type TPaymentSource = Pick<TAccountingDoc, 'accountingDocType' | 'paidAmount'> & {
|
|
49
|
+
advancePayments?: TAdvancePayment[];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The paid amount and the amount due of a document, as an encoder writes them.
|
|
54
|
+
* - When the document lists `advancePayments`, its `paidAmount` equals their
|
|
55
|
+
* gross sum, the `net` plus the `vat` of every group of every payment, to
|
|
56
|
+
* the last decimal (the rule `@tsclass/tsclass` sets). A document that
|
|
57
|
+
* breaks it, or lists advance payments without a paid amount, is refused;
|
|
58
|
+
* neither value is recomputed from the other.
|
|
59
|
+
* - The paid amount (BT-113) has at most the currency's decimals, two at most
|
|
60
|
+
* (BR-DEC-16); one with more is refused, not rounded.
|
|
61
|
+
* - A paid amount of zero is nothing paid, as an absent one: BT-113 is not
|
|
62
|
+
* written and BT-115 is BT-112.
|
|
63
|
+
* @param accountingDoc The document
|
|
64
|
+
* @param totals Its totals (`computeDocumentTotals`)
|
|
65
|
+
*/
|
|
66
|
+
const computePaymentTotals = (accountingDoc: TPaymentSource, totals: IDocumentTotals): IPaymentTotals => {
|
|
67
|
+
const advancePayments = accountingDoc.advancePayments ?? [];
|
|
68
|
+
if (advancePayments.length > 0 && !finalInvoiceTypes.includes(accountingDoc.accountingDocType)) {
|
|
69
|
+
refuse(
|
|
70
|
+
'advancePayments',
|
|
71
|
+
`advancePayments: a ${accountingDoc.accountingDocType} deducts no advance payments; only an invoice, a corrected invoice or a self-billed invoice is a final invoice (§ 14 Abs. 5 Satz 2 UStG)`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
const statedPaid = accountingDoc.paidAmount;
|
|
75
|
+
if (advancePayments.length > 0) {
|
|
76
|
+
const grossSum = Decimal.sum(
|
|
77
|
+
advancePayments.flatMap((payment, paymentIndex) =>
|
|
78
|
+
payment.vatGroups.flatMap((group, groupIndex) => [
|
|
79
|
+
statedDecimal(group.net, `advancePayments[${paymentIndex}].vatGroups[${groupIndex}].net`),
|
|
80
|
+
statedDecimal(group.vat, `advancePayments[${paymentIndex}].vatGroups[${groupIndex}].vat`),
|
|
81
|
+
]),
|
|
82
|
+
),
|
|
83
|
+
);
|
|
84
|
+
if (statedPaid === undefined || !statedDecimal(statedPaid, 'paidAmount').equals(grossSum)) {
|
|
85
|
+
refuse(
|
|
86
|
+
'paidAmount',
|
|
87
|
+
`paidAmount (${statedPaid === undefined ? 'absent' : toPlainDecimalString(statedPaid)}) does not equal the gross sum of the advancePayments (${grossSum.toString()}), the net plus the vat of every group of every payment; neither value is recomputed from the other`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (statedPaid === undefined) {
|
|
92
|
+
return { duePayable: totals.grandTotal };
|
|
93
|
+
}
|
|
94
|
+
const paid = statedDecimal(statedPaid, 'paidAmount');
|
|
95
|
+
if (decimalsOf(statedPaid) > totals.minorUnits) {
|
|
96
|
+
refuse('paidAmount', `paidAmount: the paid amount (BT-113) has at most ${totals.minorUnits} decimals, it is ${toPlainDecimalString(statedPaid)}`, 'BR-DEC-16');
|
|
97
|
+
}
|
|
98
|
+
if (paid.isZero()) {
|
|
99
|
+
return { duePayable: totals.grandTotal };
|
|
100
|
+
}
|
|
101
|
+
return { paidAmount: paid, duePayable: totals.grandTotal.subtract(paid) };
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The paid amount and the amount due as an encoder writes them (see
|
|
106
|
+
* `computePaymentTotals` above for the rules); a document that breaks one is
|
|
107
|
+
* refused with an `EInvoiceFormatError` naming the field.
|
|
108
|
+
* @param accountingDoc The document
|
|
109
|
+
* @param totals Its totals (`computeDocumentTotals`)
|
|
110
|
+
*/
|
|
111
|
+
export const getPaymentTotals = (accountingDoc: TPaymentSource, totals: IDocumentTotals): IPaymentTotals => {
|
|
112
|
+
try {
|
|
113
|
+
return computePaymentTotals(accountingDoc, totals);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (error instanceof PaymentProblemSignal) {
|
|
116
|
+
throw new EInvoiceFormatError(`${error.problem.ruleId}: ${error.problem.message}`, {
|
|
117
|
+
unsupportedFeatures: [error.problem.field],
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* What keeps a document's paid amount from being written, for a validator to
|
|
126
|
+
* report instead of throwing; undefined when there is nothing
|
|
127
|
+
* @param accountingDoc The document
|
|
128
|
+
* @param totals Its totals (`computeDocumentTotals`)
|
|
129
|
+
*/
|
|
130
|
+
export const findPaymentProblem = (accountingDoc: TPaymentSource, totals: IDocumentTotals): IPaymentProblem | undefined => {
|
|
131
|
+
try {
|
|
132
|
+
computePaymentTotals(accountingDoc, totals);
|
|
133
|
+
return undefined;
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if (error instanceof PaymentProblemSignal) {
|
|
136
|
+
return error.problem;
|
|
137
|
+
}
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The paid amount (BT-113) a decoder reads: the number the document states,
|
|
144
|
+
* or undefined when it states none or zero (a stated zero is nothing paid, as
|
|
145
|
+
* an absent one). A paid amount that is no number is refused, since the amount
|
|
146
|
+
* due depends on it.
|
|
147
|
+
* @param text The text of the paid amount element, '' when absent
|
|
148
|
+
* @param format The format being read
|
|
149
|
+
*/
|
|
150
|
+
export const readPaidAmount = (text: string, format: string): number | undefined => {
|
|
151
|
+
const trimmed = text.trim();
|
|
152
|
+
if (!trimmed) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
const value = Number(trimmed);
|
|
156
|
+
if (!/^[+-]?(\d+\.?\d*|\.\d+)$/.test(trimmed) || !Number.isFinite(value)) {
|
|
157
|
+
throw new EInvoiceParsingError(`The paid amount (BT-113) is no decimal number: ${trimmed}`, { format });
|
|
158
|
+
}
|
|
159
|
+
return value === 0 ? undefined : value;
|
|
160
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { business } from '@tsclass/tsclass';
|
|
2
|
+
|
|
3
|
+
/** The contact of a party: the seller contact (BG-6) or the buyer contact (BG-9) */
|
|
4
|
+
export interface IPartyContact {
|
|
5
|
+
/** Contact point (BT-41, BT-56) */
|
|
6
|
+
name?: string;
|
|
7
|
+
/** Telephone number (BT-42, BT-57) */
|
|
8
|
+
phone?: string;
|
|
9
|
+
/** Email address (BT-43, BT-58) */
|
|
10
|
+
email?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** A party with the contact metadata the decoders read back (`metadata.contactInformation`) */
|
|
14
|
+
type TPartyWithContactMetadata = business.TContact & { metadata?: { contactInformation?: IPartyContact } };
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The contact a party states, as the encoders write it: the contact metadata first, otherwise
|
|
18
|
+
* the party's own name, telephone number and email address; none when the party states neither.
|
|
19
|
+
* @param party The seller or the buyer
|
|
20
|
+
*/
|
|
21
|
+
export const getPartyContact = (party: business.TContact | undefined): IPartyContact | undefined => {
|
|
22
|
+
const explicit = (party as TPartyWithContactMetadata | undefined)?.metadata?.contactInformation;
|
|
23
|
+
if (explicit && (explicit.name || explicit.phone || explicit.email)) {
|
|
24
|
+
return explicit;
|
|
25
|
+
}
|
|
26
|
+
if (party?.email || party?.phone) {
|
|
27
|
+
return { name: party.name, phone: party.phone, email: party.email };
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { TAccountingDoc } from '../../interfaces/common.js';
|
|
2
|
+
import { EInvoiceFormatError } from '../../errors.js';
|
|
3
|
+
|
|
4
|
+
/** A party identifier: the seller identifier (BT-29) or the buyer identifier (BT-46), with its scheme */
|
|
5
|
+
export interface IPartyIdentifier {
|
|
6
|
+
value: string;
|
|
7
|
+
/** The identification scheme (BT-29-1, BT-46-1): an ISO/IEC 6523 ICD code, or SEPA for a creditor identifier */
|
|
8
|
+
scheme?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The party identifiers (BT-29 seller, BT-46 buyer) a party carries in
|
|
13
|
+
* `additionalIdentifiers`, where the decoders keep the ones a document states;
|
|
14
|
+
* the envelope has no typed field for them. Entries without a value are
|
|
15
|
+
* skipped, a blank scheme is none.
|
|
16
|
+
* @param party The seller or the buyer
|
|
17
|
+
*/
|
|
18
|
+
export const getPartyIdentifiers = (party: unknown): IPartyIdentifier[] => {
|
|
19
|
+
const entries: unknown = (party as { additionalIdentifiers?: unknown } | undefined)?.additionalIdentifiers;
|
|
20
|
+
if (!Array.isArray(entries)) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
const identifiers: IPartyIdentifier[] = [];
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
const { value, scheme } = (entry ?? {}) as { value?: unknown; scheme?: unknown };
|
|
26
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const trimmedScheme = typeof scheme === 'string' ? scheme.trim() : '';
|
|
30
|
+
identifiers.push({ value: value.trim(), ...(trimmedScheme ? { scheme: trimmedScheme } : {}) });
|
|
31
|
+
}
|
|
32
|
+
return identifiers;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Refuses a document whose buyer carries an identifier (BT-46) with the scheme
|
|
37
|
+
* SEPA, naming the rule. The SEPA scheme is the creditor identifier (BT-90) of
|
|
38
|
+
* the seller or the payee. The CEN artefacts 1.3.16 report it on a buyer as
|
|
39
|
+
* BR-CL-10, "Any identifier identification scheme identifier MUST be coded
|
|
40
|
+
* using one of the ISO 6523 ICD list.": UBL admits it in a party identification
|
|
41
|
+
* of the seller and the payee only, and CII in no `ram:GlobalID` at all, which
|
|
42
|
+
* is where CII writes an identifier with a scheme. Nothing is left out
|
|
43
|
+
* silently.
|
|
44
|
+
* @param accountingDoc The document
|
|
45
|
+
* @param targetFormat The format being written
|
|
46
|
+
*/
|
|
47
|
+
export const assertPartyIdentifiersWritable = (accountingDoc: Pick<TAccountingDoc, 'to'>, targetFormat: string): void => {
|
|
48
|
+
const entries: unknown = (accountingDoc.to as { additionalIdentifiers?: unknown } | undefined)?.additionalIdentifiers;
|
|
49
|
+
if (!Array.isArray(entries)) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
entries.forEach((entry: unknown, index: number) => {
|
|
53
|
+
const { scheme } = (entry ?? {}) as { scheme?: unknown };
|
|
54
|
+
if (typeof scheme === 'string' && scheme.trim().toUpperCase() === 'SEPA') {
|
|
55
|
+
throw new EInvoiceFormatError(
|
|
56
|
+
`BR-CL-10: Any identifier identification scheme identifier MUST be coded using one of the ISO 6523 ICD list. to.additionalIdentifiers[${index}] states the scheme SEPA, the creditor identifier (BT-90) of a seller or payee, which a buyer identifier (BT-46) cannot have`,
|
|
57
|
+
{ targetFormat, unsupportedFeatures: ['BR-CL-10'] },
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { UBL_CUSTOMIZATION_IDS } from '../ubl/ubl.types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Whether a specification identifier (BT-24) is that of PEPPOL BIS Billing 3.0,
|
|
5
|
+
* `urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0`,
|
|
6
|
+
* the one identifier the PEPPOL BIS Billing 3.0 rules apply to. PINT, the
|
|
7
|
+
* A-NZ, Singapore and other PEPPOL derivatives, and the PEPPOL business process
|
|
8
|
+
* type (BT-23, `urn:fdc:peppol.eu:2017:poacc:billing:01:1.0`), which an
|
|
9
|
+
* EN 16931 document may state as well, do not make a document one.
|
|
10
|
+
* @param customizationId The specification identifier
|
|
11
|
+
*/
|
|
12
|
+
export const isPeppolBisBilling3 = (customizationId: string | undefined): boolean =>
|
|
13
|
+
(customizationId ?? '').trim() === UBL_CUSTOMIZATION_IDS.PEPPOL_BIS;
|