@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
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* Validates invoices against EN16931 Business Terms and Business Groups
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { findInvalidItemAmounts, getTotalsSkippedResult } from '../utils/document.totals.js';
|
|
6
|
+
import { computeDocumentTotals, findInvalidItemAmounts, getTotalsSkippedResult } from '../utils/document.totals.js';
|
|
7
|
+
import { findPaymentProblem } from '../utils/paid.amount.js';
|
|
7
8
|
import type { ValidationResult } from '../validation/validation.types.js';
|
|
8
9
|
import type { EN16931SemanticModel, BusinessTerms, BusinessGroups } from './bt-bg.model.js';
|
|
9
10
|
import type { EInvoice } from '../../einvoice.js';
|
|
@@ -57,6 +58,21 @@ export class SemanticModelValidator {
|
|
|
57
58
|
return results;
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
// The amount due (BT-115) follows from the paid amount; a paid amount that cannot be written
|
|
62
|
+
// (more decimals than the currency has, or advance payments that do not add up to it) is
|
|
63
|
+
// reported and the model rules are skipped, as for a line amount that is no number
|
|
64
|
+
const paymentProblem = findPaymentProblem(invoice, computeDocumentTotals(invoice));
|
|
65
|
+
if (paymentProblem) {
|
|
66
|
+
results.push({
|
|
67
|
+
ruleId: paymentProblem.ruleId,
|
|
68
|
+
source: 'SEMANTIC',
|
|
69
|
+
severity: 'error',
|
|
70
|
+
message: paymentProblem.message,
|
|
71
|
+
field: paymentProblem.field,
|
|
72
|
+
});
|
|
73
|
+
return results;
|
|
74
|
+
}
|
|
75
|
+
|
|
60
76
|
// Convert to semantic model
|
|
61
77
|
const model = this.adapter.toSemanticModel(invoice);
|
|
62
78
|
|
|
@@ -148,76 +148,17 @@ export class EN16931UBLValidator extends UBLBaseValidator {
|
|
|
148
148
|
valid = false;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
// BR-CO-26: the seller states a seller, legal registration or VAT identifier
|
|
152
|
+
valid = this.validateSellerIdentifier() && valid;
|
|
153
|
+
|
|
151
154
|
// BR-23: every line has a unit code
|
|
152
155
|
valid = this.validateLineUnitCodePresence() && valid;
|
|
153
156
|
|
|
154
157
|
// BR-CL-23: unit codes of UN/ECE Recommendation 20 with the Recommendation 21 extension
|
|
155
158
|
valid = this.validateUnitCodes() && valid;
|
|
156
159
|
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
valid = this.validateCalculationRules() && valid;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return valid;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Validates calculation rules (BR-CO-*)
|
|
167
|
-
*/
|
|
168
|
-
private validateCalculationRules(): boolean {
|
|
169
|
-
let valid = true;
|
|
170
|
-
|
|
171
|
-
// BR-CO-10: Sum of Invoice line net amount = Σ Invoice line net amount.
|
|
172
|
-
const lineExtensionAmount = this.getNumber('//cac:LegalMonetaryTotal/cbc:LineExtensionAmount');
|
|
173
|
-
const lines = this.select('//cac:InvoiceLine | //cac:CreditNoteLine', this.doc) as Node[];
|
|
174
|
-
|
|
175
|
-
let calculatedSum = 0;
|
|
176
|
-
for (const line of lines) {
|
|
177
|
-
const lineAmount = this.getNumber('.//cbc:LineExtensionAmount', line);
|
|
178
|
-
calculatedSum += lineAmount;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Allow for small rounding differences (0.01)
|
|
182
|
-
if (Math.abs(lineExtensionAmount - calculatedSum) > 0.01) {
|
|
183
|
-
this.addError(
|
|
184
|
-
'BR-CO-10',
|
|
185
|
-
`Sum of Invoice line net amount (${lineExtensionAmount}) must equal sum of line amounts (${calculatedSum})`,
|
|
186
|
-
'//cac:LegalMonetaryTotal/cbc:LineExtensionAmount'
|
|
187
|
-
);
|
|
188
|
-
valid = false;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// BR-CO-13: Invoice total amount without VAT = Σ Invoice line net amount - Sum of allowances on document level + Sum of charges on document level.
|
|
192
|
-
const taxExclusiveAmount = this.getNumber('//cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount');
|
|
193
|
-
const allowanceTotal = this.getNumber('//cac:LegalMonetaryTotal/cbc:AllowanceTotalAmount') || 0;
|
|
194
|
-
const chargeTotal = this.getNumber('//cac:LegalMonetaryTotal/cbc:ChargeTotalAmount') || 0;
|
|
195
|
-
|
|
196
|
-
const calculatedTaxExclusive = lineExtensionAmount - allowanceTotal + chargeTotal;
|
|
197
|
-
|
|
198
|
-
if (Math.abs(taxExclusiveAmount - calculatedTaxExclusive) > 0.01) {
|
|
199
|
-
this.addError(
|
|
200
|
-
'BR-CO-13',
|
|
201
|
-
`Invoice total amount without VAT (${taxExclusiveAmount}) must equal calculated amount (${calculatedTaxExclusive})`,
|
|
202
|
-
'//cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount'
|
|
203
|
-
);
|
|
204
|
-
valid = false;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// BR-CO-15: Invoice total amount with VAT = Invoice total amount without VAT + Invoice total VAT amount.
|
|
208
|
-
const taxInclusiveAmount = this.getNumber('//cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount');
|
|
209
|
-
const totalTaxAmount = this.getNumber('//cac:TaxTotal/cbc:TaxAmount') || 0;
|
|
210
|
-
|
|
211
|
-
const calculatedTaxInclusive = taxExclusiveAmount + totalTaxAmount;
|
|
212
|
-
|
|
213
|
-
if (Math.abs(taxInclusiveAmount - calculatedTaxInclusive) > 0.01) {
|
|
214
|
-
this.addError(
|
|
215
|
-
'BR-CO-15',
|
|
216
|
-
`Invoice total amount with VAT (${taxInclusiveAmount}) must equal calculated amount (${calculatedTaxInclusive})`,
|
|
217
|
-
'//cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount'
|
|
218
|
-
);
|
|
219
|
-
valid = false;
|
|
220
|
-
}
|
|
160
|
+
// BR-CO-10 to BR-CO-17: the stated totals, VAT breakdown and line amounts agree with each other
|
|
161
|
+
valid = this.validateStatedTotals() && valid;
|
|
221
162
|
|
|
222
163
|
return valid;
|
|
223
164
|
}
|
|
@@ -7,8 +7,10 @@ import { getPrecedingInvoiceReferences } from '../../utils/preceding.invoice.js'
|
|
|
7
7
|
import { getWritableDueDate } from '../../utils/date.value.js';
|
|
8
8
|
import { getPaymentTermsNote } from '../../utils/payment.terms.js';
|
|
9
9
|
import { computeDocumentTotals } from '../../utils/document.totals.js';
|
|
10
|
+
import { getPaymentTotals } from '../../utils/paid.amount.js';
|
|
10
11
|
import { toPlainDecimalString } from '../../utils/number.text.js';
|
|
11
12
|
import { resolveCountryCode } from '../../utils/country.code.js';
|
|
13
|
+
import { getPartyIdentifiers } from '../../utils/party.identifier.js';
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* UBL Encoder implementation
|
|
@@ -113,6 +115,12 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
113
115
|
// Document Currency Code
|
|
114
116
|
this.appendElement(doc, root, 'cbc:DocumentCurrencyCode', invoice.currency);
|
|
115
117
|
|
|
118
|
+
// Buyer reference (BT-10), as the buyer gave it
|
|
119
|
+
const buyerReference = invoice.buyerReference?.trim();
|
|
120
|
+
if (buyerReference) {
|
|
121
|
+
this.appendElement(doc, root, 'cbc:BuyerReference', buyerReference);
|
|
122
|
+
}
|
|
123
|
+
|
|
116
124
|
// Invoicing period (BG-14), in schema order before the document references
|
|
117
125
|
this.addInvoicePeriod(doc, root, invoice);
|
|
118
126
|
|
|
@@ -228,6 +236,18 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
228
236
|
|
|
229
237
|
const partyNode = doc.createElement('cac:Party');
|
|
230
238
|
partyElement.appendChild(partyNode);
|
|
239
|
+
|
|
240
|
+
// Party identifiers (BT-29 seller, BT-46 buyer), before the name in the schema order
|
|
241
|
+
for (const identifier of getPartyIdentifiers(party)) {
|
|
242
|
+
const partyIdentificationNode = doc.createElement('cac:PartyIdentification');
|
|
243
|
+
partyNode.appendChild(partyIdentificationNode);
|
|
244
|
+
const idNode = doc.createElement('cbc:ID');
|
|
245
|
+
if (identifier.scheme) {
|
|
246
|
+
idNode.setAttribute('schemeID', identifier.scheme);
|
|
247
|
+
}
|
|
248
|
+
idNode.textContent = identifier.value;
|
|
249
|
+
partyIdentificationNode.appendChild(idNode);
|
|
250
|
+
}
|
|
231
251
|
|
|
232
252
|
// Party name
|
|
233
253
|
const partyNameNode = doc.createElement('cac:PartyName');
|
|
@@ -481,10 +501,19 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
481
501
|
taxInclusiveAmountElement.textContent = amount(totals.grandTotal);
|
|
482
502
|
legalMonetaryTotalNode.appendChild(taxInclusiveAmountElement);
|
|
483
503
|
|
|
484
|
-
//
|
|
504
|
+
// Paid amount (BT-113), before the payable amount in the schema order
|
|
505
|
+
const payment = getPaymentTotals(invoice, totals);
|
|
506
|
+
if (payment.paidAmount) {
|
|
507
|
+
const prepaidAmountElement = doc.createElement('cbc:PrepaidAmount');
|
|
508
|
+
prepaidAmountElement.setAttribute('currencyID', invoice.currency);
|
|
509
|
+
prepaidAmountElement.textContent = amount(payment.paidAmount);
|
|
510
|
+
legalMonetaryTotalNode.appendChild(prepaidAmountElement);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Payable amount (BT-115)
|
|
485
514
|
const payableAmountElement = doc.createElement('cbc:PayableAmount');
|
|
486
515
|
payableAmountElement.setAttribute('currencyID', invoice.currency);
|
|
487
|
-
payableAmountElement.textContent = amount(
|
|
516
|
+
payableAmountElement.textContent = amount(payment.duePayable);
|
|
488
517
|
legalMonetaryTotalNode.appendChild(payableAmountElement);
|
|
489
518
|
}
|
|
490
519
|
|
|
@@ -892,29 +921,6 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
892
921
|
}
|
|
893
922
|
}
|
|
894
923
|
|
|
895
|
-
// Add additional identifiers
|
|
896
|
-
if (partyData.additionalIdentifiers && Array.isArray(partyData.additionalIdentifiers)) {
|
|
897
|
-
for (const identifier of partyData.additionalIdentifiers) {
|
|
898
|
-
const partyId = doc.createElement('cac:PartyIdentification');
|
|
899
|
-
const id = doc.createElement('cbc:ID');
|
|
900
|
-
if (identifier.scheme) {
|
|
901
|
-
id.setAttribute('schemeID', identifier.scheme);
|
|
902
|
-
}
|
|
903
|
-
id.textContent = identifier.value;
|
|
904
|
-
partyId.appendChild(id);
|
|
905
|
-
|
|
906
|
-
// Insert after EndpointID or at beginning
|
|
907
|
-
const endpoint = party.getElementsByTagName('cbc:EndpointID')[0];
|
|
908
|
-
if (endpoint && endpoint.nextSibling) {
|
|
909
|
-
party.insertBefore(partyId, endpoint.nextSibling);
|
|
910
|
-
} else if (party.firstChild) {
|
|
911
|
-
party.insertBefore(partyId, party.firstChild);
|
|
912
|
-
} else {
|
|
913
|
-
party.appendChild(partyId);
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
|
|
918
924
|
// Add contact information from metadata if not already present
|
|
919
925
|
const contactInfo = partyData.metadata?.contactInformation;
|
|
920
926
|
if (contactInfo) {
|
|
@@ -4,6 +4,8 @@ import { UBLDocumentType, UBL_NAMESPACES } from './ubl.types.js';
|
|
|
4
4
|
import { DOMParser, xpath } from '../../plugins.js';
|
|
5
5
|
import { EInvoiceParsingError } from '../../errors.js';
|
|
6
6
|
import { getAccountingDocType } from '../utils/document.typecode.js';
|
|
7
|
+
import { readUblStatedValues } from '../utils/stated.values.js';
|
|
8
|
+
import type { IEInvoiceStatedValues } from '../../interfaces/stated.values.js';
|
|
7
9
|
|
|
8
10
|
const ublParser = new DOMParser();
|
|
9
11
|
const ublNamespaces = {
|
|
@@ -37,11 +39,21 @@ export abstract class UBLBaseDecoder extends BaseDecoder {
|
|
|
37
39
|
return this.doc;
|
|
38
40
|
}
|
|
39
41
|
|
|
42
|
+
/** The values the document states, read by `decode` */
|
|
43
|
+
private statedValues?: IEInvoiceStatedValues;
|
|
44
|
+
|
|
45
|
+
public override getStatedValues(): IEInvoiceStatedValues | undefined {
|
|
46
|
+
return this.statedValues;
|
|
47
|
+
}
|
|
48
|
+
|
|
40
49
|
/**
|
|
41
50
|
* Decodes UBL XML into an accounting document
|
|
42
51
|
* @returns Promise resolving to the accounting document
|
|
43
52
|
*/
|
|
44
53
|
public async decode(): Promise<TAccountingDoc> {
|
|
54
|
+
// the values the document states, read first: an amount that is no number is refused
|
|
55
|
+
this.statedValues = this.assertStatedValuesReadable(readUblStatedValues(this.doc), 'ubl');
|
|
56
|
+
|
|
45
57
|
// a CreditNote root is a credit note whatever its type code says
|
|
46
58
|
if (this.getDocumentType() === UBLDocumentType.CREDIT_NOTE) {
|
|
47
59
|
return this.decodeCreditNote();
|
|
@@ -3,6 +3,10 @@ import type { TAccountingDoc, TCreditNote, TInvoiceDocument } from '../../interf
|
|
|
3
3
|
import { UBLDocumentType, UBL_NAMESPACES } from './ubl.types.js';
|
|
4
4
|
import { getWritableDate } from '../utils/date.value.js';
|
|
5
5
|
import { assertVatCategoryWritable } from '../utils/vat.category.js';
|
|
6
|
+
import { assertSellerIdentified } from '../utils/seller.identifier.js';
|
|
7
|
+
import { assertPartyIdentifiersWritable } from '../utils/party.identifier.js';
|
|
8
|
+
import { computeDocumentTotals } from '../utils/document.totals.js';
|
|
9
|
+
import { getPaymentTotals } from '../utils/paid.amount.js';
|
|
6
10
|
|
|
7
11
|
/**
|
|
8
12
|
* The children of the document root in the order the UBL 2.1 schema requires
|
|
@@ -51,11 +55,15 @@ export abstract class UBLBaseEncoder extends BaseEncoder {
|
|
|
51
55
|
public async encode(invoice: TAccountingDoc): Promise<string> {
|
|
52
56
|
// a reverse charge document that lacks what EN 16931 requires of one is refused (BR-AE-02, BR-AE-05)
|
|
53
57
|
assertVatCategoryWritable(invoice, 'ubl');
|
|
58
|
+
// a buyer identifier with the SEPA scheme is refused (BR-CL-10)
|
|
59
|
+
assertPartyIdentifiersWritable(invoice, 'ubl');
|
|
60
|
+
// a paid amount the advance payments do not add up to, or with more decimals than the currency has, is refused
|
|
61
|
+
getPaymentTotals(invoice, computeDocumentTotals(invoice));
|
|
54
62
|
// 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
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return
|
|
63
|
+
const xml = invoice.accountingDocType === 'creditnote' ? await this.encodeCreditNote(invoice) : await this.encodeInvoice(invoice);
|
|
64
|
+
// a seller the written document does not identify is refused (BR-CO-26), never written silently
|
|
65
|
+
assertSellerIdentified(xml, 'ubl', 'ubl');
|
|
66
|
+
return xml;
|
|
59
67
|
}
|
|
60
68
|
|
|
61
69
|
/**
|
|
@@ -4,6 +4,8 @@ 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
6
|
import { meetsUnitCodeRule } from '../utils/unit.codes.js';
|
|
7
|
+
import { BR_CO_26_TEXT, ublSellerMeetsBrCo26 } from '../utils/seller.identifier.js';
|
|
8
|
+
import { findStatedValueDisagreements, readUblStatedValues } from '../utils/stated.values.js';
|
|
7
9
|
|
|
8
10
|
const ublValidatorParser = new DOMParser();
|
|
9
11
|
const ublValidatorNamespaces = {
|
|
@@ -105,6 +107,33 @@ export abstract class UBLBaseValidator extends BaseValidator {
|
|
|
105
107
|
*/
|
|
106
108
|
protected abstract validateStructure(): boolean;
|
|
107
109
|
|
|
110
|
+
/**
|
|
111
|
+
* BR-CO-10 to BR-CO-17: the document totals, the VAT breakdown, the line
|
|
112
|
+
* amounts and the document level allowances and charges the document states
|
|
113
|
+
* agree with each other, as the CEN artefacts check them
|
|
114
|
+
* @returns True if they do
|
|
115
|
+
*/
|
|
116
|
+
protected validateStatedTotals(): boolean {
|
|
117
|
+
const findings = findStatedValueDisagreements(readUblStatedValues(this.doc), 'ubl');
|
|
118
|
+
for (const finding of findings) {
|
|
119
|
+
this.addError(finding.ruleId, finding.message, finding.location);
|
|
120
|
+
}
|
|
121
|
+
return findings.length === 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* BR-CO-26: the seller states a seller identifier (BT-29), a legal
|
|
126
|
+
* registration identifier (BT-30) or a VAT identifier (BT-31).
|
|
127
|
+
* @returns True if it does
|
|
128
|
+
*/
|
|
129
|
+
protected validateSellerIdentifier(): boolean {
|
|
130
|
+
if (ublSellerMeetsBrCo26(this.doc)) {
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
this.addError('BR-CO-26', BR_CO_26_TEXT, '/*/cac:AccountingSupplierParty');
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
108
137
|
/**
|
|
109
138
|
* BR-23: every invoice or credit note line has a unit code on its invoiced or credited quantity.
|
|
110
139
|
* @returns True if every line has one
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
getPrecedingInvoiceFields,
|
|
13
13
|
type TPrecedingInvoiceReference,
|
|
14
14
|
} from '../../utils/preceding.invoice.js';
|
|
15
|
+
import { readPaidAmount } from '../../utils/paid.amount.js';
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Decoder for XRechnung (UBL) format
|
|
@@ -240,6 +241,9 @@ export class XRechnungDecoder extends UBLBaseDecoder {
|
|
|
240
241
|
const hasPaymentInformation = Object.values(paymentInformation).some(value => Boolean(value));
|
|
241
242
|
const hasDateInformation = Object.values(dateInformation).some(value => Boolean(value));
|
|
242
243
|
|
|
244
|
+
// the paid amount (BT-113); the amount due (BT-115) follows from it and the totals
|
|
245
|
+
const paidAmount = readPaidAmount(this.getText('/*/cac:LegalMonetaryTotal/cbc:PrepaidAmount'), 'ubl');
|
|
246
|
+
|
|
243
247
|
// Create the common invoice data with metadata only when the source XML actually contains it.
|
|
244
248
|
const invoiceData: any = {
|
|
245
249
|
type: 'accounting-doc' as const,
|
|
@@ -267,6 +271,8 @@ export class XRechnungDecoder extends UBLBaseDecoder {
|
|
|
267
271
|
currency: currencyCode as finance.TCurrency,
|
|
268
272
|
notes: notes,
|
|
269
273
|
objectActions: [],
|
|
274
|
+
// the buyer reference (BT-10), when stated
|
|
275
|
+
...(buyerReference.trim() ? { buyerReference: buyerReference.trim() } : {}),
|
|
270
276
|
// the day and the period of supply as the document states them (BT-72, BG-14)
|
|
271
277
|
...(deliveryDate ? { deliveryDate: this.parseRequiredUblDate(deliveryDate) } : {}),
|
|
272
278
|
...(periodStart && periodEnd
|
|
@@ -278,18 +284,25 @@ export class XRechnungDecoder extends UBLBaseDecoder {
|
|
|
278
284
|
}
|
|
279
285
|
: {}),
|
|
280
286
|
...getPrecedingInvoiceFields(accountingDocType, this.extractPrecedingInvoiceReferences()),
|
|
287
|
+
...(paidAmount === undefined ? {} : { paidAmount }),
|
|
281
288
|
};
|
|
282
289
|
|
|
290
|
+
// the specification identifier (BT-24), which the validators read to tell an XRechnung, a
|
|
291
|
+
// PEPPOL or a Factur-X document
|
|
292
|
+
const customizationId = this.getText('/*/cbc:CustomizationID', this.doc).trim();
|
|
283
293
|
if (hasBusinessReferences || hasPaymentInformation || hasDateInformation) {
|
|
284
294
|
invoiceData.metadata = {
|
|
285
295
|
format: 'xrechnung' as any,
|
|
286
296
|
version: '1.0.0',
|
|
297
|
+
...(customizationId ? { customizationId } : {}),
|
|
287
298
|
extensions: {
|
|
288
299
|
...(hasBusinessReferences ? { businessReferences } : {}),
|
|
289
300
|
...(hasPaymentInformation ? { paymentInformation } : {}),
|
|
290
301
|
...(hasDateInformation ? { dateInformation } : {}),
|
|
291
302
|
}
|
|
292
303
|
};
|
|
304
|
+
} else if (customizationId) {
|
|
305
|
+
invoiceData.metadata = { customizationId };
|
|
293
306
|
}
|
|
294
307
|
|
|
295
308
|
// Validate mandatory EN16931 fields unless validation is skipped
|
|
@@ -2,6 +2,32 @@ import { UBLEncoder } from '../generic/ubl.encoder.js';
|
|
|
2
2
|
import type { TAccountingDoc, TCreditNote, TInvoiceDocument } from '../../../interfaces/common.js';
|
|
3
3
|
import { DOMParser, XMLSerializer } from '../../../plugins.js';
|
|
4
4
|
import { UBL_CUSTOMIZATION_IDS } from '../ubl.types.js';
|
|
5
|
+
import { getPartyContact } from '../../utils/party.contact.js';
|
|
6
|
+
import { EInvoiceFormatError } from '../../../errors.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The buyer reference (BT-10) an XRechnung document states: the envelope's
|
|
10
|
+
* `buyerReference`, or the one a decoded UBL document keeps in
|
|
11
|
+
* `metadata.extensions.businessReferences`. XRechnung requires it (BR-DE-15,
|
|
12
|
+
* "Das Element \"Buyer reference\" (BT-10) muss übermittelt werden."), and it is
|
|
13
|
+
* the buyer's: the buyer gives the seller an identifier of its choice, a
|
|
14
|
+
* Leitweg-ID for a public buyer. Without one the export is refused; a document
|
|
15
|
+
* for a buyer that gave none can be written as EN 16931 UBL (`ubl`), where
|
|
16
|
+
* BT-10 is optional.
|
|
17
|
+
* @param invoice The document
|
|
18
|
+
*/
|
|
19
|
+
const getXRechnungBuyerReference = (invoice: TAccountingDoc): string => {
|
|
20
|
+
const kept: unknown = (invoice as { metadata?: { extensions?: { businessReferences?: { buyerReference?: unknown } } } }).metadata
|
|
21
|
+
?.extensions?.businessReferences?.buyerReference;
|
|
22
|
+
const buyerReference = invoice.buyerReference?.trim() || (typeof kept === 'string' ? kept.trim() : '');
|
|
23
|
+
if (!buyerReference) {
|
|
24
|
+
throw new EInvoiceFormatError(
|
|
25
|
+
"BR-DE-15: an XRechnung document states the buyer reference (BT-10), buyerReference is missing. It is the identifier the buyer gave for routing the invoice (a Leitweg-ID for a public buyer) and cannot be put in place by the seller; without one, export the document as EN 16931 UBL ('ubl'), where BT-10 is optional",
|
|
26
|
+
{ targetFormat: 'xrechnung', unsupportedFeatures: ['BR-DE-15'] },
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
return buyerReference;
|
|
30
|
+
};
|
|
5
31
|
|
|
6
32
|
/**
|
|
7
33
|
* Encoder for XRechnung (UBL) format
|
|
@@ -59,9 +85,11 @@ export class XRechnungEncoder extends UBLEncoder {
|
|
|
59
85
|
customizationId.textContent = UBL_CUSTOMIZATION_IDS.XRECHNUNG;
|
|
60
86
|
}
|
|
61
87
|
|
|
62
|
-
//
|
|
88
|
+
// The buyer reference (BT-10), which XRechnung requires (BR-DE-15): the identifier the buyer
|
|
89
|
+
// gave the seller for routing the invoice, a Leitweg-ID for a public buyer. It comes from the
|
|
90
|
+
// buyer, so there is none to put in place of a missing one.
|
|
91
|
+
const buyerReferenceValue = getXRechnungBuyerReference(invoice);
|
|
63
92
|
let buyerRef = root.getElementsByTagName('cbc:BuyerReference')[0];
|
|
64
|
-
const buyerReferenceValue = (invoice as any).buyerReference || metadata?.businessReferences?.buyerReference || invoice.id;
|
|
65
93
|
if (!buyerRef) {
|
|
66
94
|
// Find where to insert it (after DocumentCurrencyCode)
|
|
67
95
|
const currencyCode = root.getElementsByTagName('cbc:DocumentCurrencyCode')[0];
|
|
@@ -70,7 +98,7 @@ export class XRechnungEncoder extends UBLEncoder {
|
|
|
70
98
|
buyerRef.textContent = buyerReferenceValue;
|
|
71
99
|
currencyCode.parentNode!.insertBefore(buyerRef, currencyCode.nextSibling);
|
|
72
100
|
}
|
|
73
|
-
} else
|
|
101
|
+
} else {
|
|
74
102
|
buyerRef.textContent = buyerReferenceValue;
|
|
75
103
|
}
|
|
76
104
|
|
|
@@ -151,29 +179,6 @@ export class XRechnungEncoder extends UBLEncoder {
|
|
|
151
179
|
}
|
|
152
180
|
}
|
|
153
181
|
|
|
154
|
-
// Add PartyIdentification for additional identifiers
|
|
155
|
-
if (party.additionalIdentifiers) {
|
|
156
|
-
for (const identifier of party.additionalIdentifiers) {
|
|
157
|
-
const partyId = doc.createElement('cac:PartyIdentification');
|
|
158
|
-
const id = doc.createElement('cbc:ID');
|
|
159
|
-
if (identifier.scheme) {
|
|
160
|
-
id.setAttribute('schemeID', identifier.scheme);
|
|
161
|
-
}
|
|
162
|
-
id.textContent = identifier.value;
|
|
163
|
-
partyId.appendChild(id);
|
|
164
|
-
|
|
165
|
-
// Insert after EndpointID or at beginning
|
|
166
|
-
const endpoint = partyElement.getElementsByTagName('cbc:EndpointID')[0];
|
|
167
|
-
if (endpoint && endpoint.nextSibling) {
|
|
168
|
-
partyElement.insertBefore(partyId, endpoint.nextSibling);
|
|
169
|
-
} else if (partyElement.firstChild) {
|
|
170
|
-
partyElement.insertBefore(partyId, partyElement.firstChild);
|
|
171
|
-
} else {
|
|
172
|
-
partyElement.appendChild(partyId);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
182
|
// Add company registration number to PartyLegalEntity
|
|
178
183
|
if (party.registrationDetails?.registrationId) {
|
|
179
184
|
let legalEntity = partyElement.getElementsByTagName('cac:PartyLegalEntity')[0];
|
|
@@ -444,27 +449,10 @@ export class XRechnungEncoder extends UBLEncoder {
|
|
|
444
449
|
*/
|
|
445
450
|
private enhancePartyInformation(doc: Document, invoice: TAccountingDoc): void {
|
|
446
451
|
// Enhance supplier party
|
|
447
|
-
this.addContactToParty(doc, 'cac:AccountingSupplierParty',
|
|
452
|
+
this.addContactToParty(doc, 'cac:AccountingSupplierParty', getPartyContact(invoice.from));
|
|
448
453
|
|
|
449
454
|
// Enhance customer party
|
|
450
|
-
this.addContactToParty(doc, 'cac:AccountingCustomerParty',
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
/**
|
|
454
|
-
* The contact block of a party: explicit contact metadata first, otherwise
|
|
455
|
-
* the party's own email and phone (BR-DE-11 and BR-DE-12 require both for
|
|
456
|
-
* the seller).
|
|
457
|
-
* @param party Party data
|
|
458
|
-
*/
|
|
459
|
-
private contactInformationOf(party: any): { name?: string; phone?: string; email?: string } | undefined {
|
|
460
|
-
const explicit = party?.metadata?.contactInformation;
|
|
461
|
-
if (explicit && (explicit.name || explicit.phone || explicit.email)) {
|
|
462
|
-
return explicit;
|
|
463
|
-
}
|
|
464
|
-
if (party?.email || party?.phone) {
|
|
465
|
-
return { name: party.name, phone: party.phone, email: party.email };
|
|
466
|
-
}
|
|
467
|
-
return undefined;
|
|
455
|
+
this.addContactToParty(doc, 'cac:AccountingCustomerParty', getPartyContact(invoice.to));
|
|
468
456
|
}
|
|
469
457
|
|
|
470
458
|
/**
|