@fin.cx/einvoice 9.0.0 → 10.0.1
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 +12 -0
- package/dist_ts/einvoice.js +45 -3
- package/dist_ts/formats/base/base.decoder.d.ts +27 -2
- package/dist_ts/formats/base/base.decoder.js +52 -27
- 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 +191 -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 +15 -3
- package/dist_ts/formats/cii/facturx/facturx.encoder.d.ts +0 -6
- package/dist_ts/formats/cii/facturx/facturx.encoder.js +11 -25
- 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 +15 -2
- package/dist_ts/formats/cii/zugferd/zugferd.encoder.d.ts +0 -7
- package/dist_ts/formats/cii/zugferd/zugferd.encoder.js +9 -67
- 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 +43 -1
- package/dist_ts/formats/cii/zugferd/zugferd.validator.js +6 -2
- package/dist_ts/formats/semantic/semantic.adapter.js +20 -18
- 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 +18 -24
- 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 +9 -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 +10 -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 +35 -0
- package/dist_ts/formats/utils/date.value.js +98 -1
- package/dist_ts/formats/utils/format.detector.js +16 -10
- 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/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 -21
- 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/en16931-metadata.d.ts +0 -4
- package/dist_ts/interfaces/stated.values.d.ts +93 -0
- package/dist_ts/interfaces/stated.values.js +2 -0
- package/package.json +4 -4
- package/readme.md +152 -7
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/einvoice.ts +52 -2
- package/ts/formats/base/base.decoder.ts +55 -45
- package/ts/formats/cii/cii.decoder.ts +159 -32
- package/ts/formats/cii/cii.encoder.ts +206 -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 +16 -2
- package/ts/formats/cii/facturx/facturx.encoder.ts +13 -25
- 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 +16 -1
- package/ts/formats/cii/zugferd/zugferd.encoder.ts +10 -72
- package/ts/formats/cii/zugferd/zugferd.types.ts +0 -9
- package/ts/formats/cii/zugferd/zugferd.v1.decoder.ts +56 -0
- package/ts/formats/cii/zugferd/zugferd.validator.ts +5 -1
- package/ts/formats/semantic/semantic.adapter.ts +19 -17
- package/ts/formats/ubl/en16931.ubl.validator.ts +5 -64
- package/ts/formats/ubl/generic/ubl.encoder.ts +19 -23
- package/ts/formats/ubl/ubl.decoder.ts +12 -0
- package/ts/formats/ubl/ubl.encoder.ts +8 -4
- package/ts/formats/ubl/ubl.validator.ts +29 -0
- package/ts/formats/ubl/xrechnung/xrechnung.decoder.ts +8 -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 +106 -0
- package/ts/formats/utils/format.detector.ts +15 -9
- 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/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 -28
- 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/en16931-metadata.ts +3 -7
- 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,13 +1,12 @@
|
|
|
1
1
|
import { CIIBaseEncoder } from '../cii.encoder.js';
|
|
2
2
|
import type { TAccountingDoc, TCreditNote, TInvoiceDocument } from '../../../interfaces/common.js';
|
|
3
|
-
import { FACTURX_PROFILE_IDS } from './facturx.types.js';
|
|
4
3
|
import { DOMParser, XMLSerializer } from '../../../plugins.js';
|
|
5
4
|
import { getDocumentTypeCode } from '../../utils/document.typecode.js';
|
|
6
5
|
import { computeDocumentTotals } from '../../utils/document.totals.js';
|
|
7
6
|
import { getPaymentTotals } from '../../utils/paid.amount.js';
|
|
8
7
|
import { toPlainDecimalString } from '../../utils/number.text.js';
|
|
9
8
|
import { resolveCountryCode } from '../../utils/country.code.js';
|
|
10
|
-
import {
|
|
9
|
+
import { getWritableDueDate } from '../../utils/date.value.js';
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* Encoder for Factur-X invoice format
|
|
@@ -94,15 +93,7 @@ export class FacturXEncoder extends CIIBaseEncoder {
|
|
|
94
93
|
// Add ID element with profile
|
|
95
94
|
const idElement = doc.createElement('ram:ID');
|
|
96
95
|
|
|
97
|
-
|
|
98
|
-
let profileId = FACTURX_PROFILE_IDS.EN16931;
|
|
99
|
-
if (this.profile === 'BASIC') {
|
|
100
|
-
profileId = FACTURX_PROFILE_IDS.BASIC;
|
|
101
|
-
} else if (this.profile === 'MINIMUM') {
|
|
102
|
-
profileId = FACTURX_PROFILE_IDS.MINIMUM;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
idElement.textContent = profileId;
|
|
96
|
+
idElement.textContent = this.getProfileId();
|
|
106
97
|
guidelineElement.appendChild(idElement);
|
|
107
98
|
}
|
|
108
99
|
|
|
@@ -209,9 +200,14 @@ export class FacturXEncoder extends CIIBaseEncoder {
|
|
|
209
200
|
const agreementElement = doc.createElement('ram:ApplicableHeaderTradeAgreement');
|
|
210
201
|
transactionElement.appendChild(agreementElement);
|
|
211
202
|
|
|
203
|
+
// Buyer reference (BT-10)
|
|
204
|
+
this.addBuyerReference(doc, agreementElement, invoice);
|
|
205
|
+
|
|
212
206
|
// Add seller
|
|
213
207
|
const sellerElement = doc.createElement('ram:SellerTradeParty');
|
|
214
208
|
this.addPartyInfo(doc, sellerElement, invoice.from, 'seller');
|
|
209
|
+
// Seller electronic address (BT-34)
|
|
210
|
+
this.addSellerElectronicAddress(doc, sellerElement, invoice);
|
|
215
211
|
agreementElement.appendChild(sellerElement);
|
|
216
212
|
|
|
217
213
|
// Add buyer
|
|
@@ -228,6 +224,9 @@ export class FacturXEncoder extends CIIBaseEncoder {
|
|
|
228
224
|
* @param partyRole Whether the party is the seller or the buyer
|
|
229
225
|
*/
|
|
230
226
|
private addPartyInfo(doc: Document, partyElement: Element, party: any, partyRole: 'seller' | 'buyer'): void {
|
|
227
|
+
// Party identifiers (BT-29 seller, BT-46 buyer)
|
|
228
|
+
this.addPartyIdentifiers(doc, partyElement, party);
|
|
229
|
+
|
|
231
230
|
// Add name
|
|
232
231
|
const nameElement = doc.createElement('ram:Name');
|
|
233
232
|
nameElement.textContent = party.name;
|
|
@@ -381,6 +380,9 @@ export class FacturXEncoder extends CIIBaseEncoder {
|
|
|
381
380
|
|
|
382
381
|
settlementElement.appendChild(paymentTermsElement);
|
|
383
382
|
|
|
383
|
+
// Invoicing period (BG-14)
|
|
384
|
+
this.addInvoicingPeriod(doc, settlementElement, invoice);
|
|
385
|
+
|
|
384
386
|
// Add totals, in the order of the CII schema
|
|
385
387
|
const monetarySummationElement = doc.createElement('ram:SpecifiedTradeSettlementHeaderMonetarySummation');
|
|
386
388
|
const appendAmount = (name: string, value: { toFixed(decimalPlaces: number): string }, withCurrency = false) => {
|
|
@@ -505,18 +507,4 @@ export class FacturXEncoder extends CIIBaseEncoder {
|
|
|
505
507
|
}
|
|
506
508
|
}
|
|
507
509
|
}
|
|
508
|
-
|
|
509
|
-
/**
|
|
510
|
-
* Formats a date as YYYYMMDD
|
|
511
|
-
* @param timestamp Timestamp to format
|
|
512
|
-
* @returns Formatted date string
|
|
513
|
-
*/
|
|
514
|
-
private formatDateYYYYMMDD(timestamp: number, field: string): string {
|
|
515
|
-
const date = getWritableDate(timestamp, field, 'cii');
|
|
516
|
-
// the UTC fields: a calendar day is its UTC midnight, whatever the server's zone
|
|
517
|
-
const year = date.getUTCFullYear();
|
|
518
|
-
const month = (date.getUTCMonth() + 1).toString().padStart(2, '0');
|
|
519
|
-
const day = date.getUTCDate().toString().padStart(2, '0');
|
|
520
|
-
return `${year}${month}${day}`;
|
|
521
|
-
}
|
|
522
510
|
}
|
|
@@ -1,16 +1,7 @@
|
|
|
1
|
-
import { CIIProfile, CII_PROFILE_IDS } from '../cii.types.js';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Factur-X specific constants and types
|
|
5
3
|
*/
|
|
6
4
|
|
|
7
|
-
// Factur-X profile IDs
|
|
8
|
-
export const FACTURX_PROFILE_IDS = {
|
|
9
|
-
MINIMUM: CII_PROFILE_IDS.FACTURX_MINIMUM,
|
|
10
|
-
BASIC: CII_PROFILE_IDS.FACTURX_BASIC,
|
|
11
|
-
EN16931: CII_PROFILE_IDS.FACTURX_EN16931
|
|
12
|
-
};
|
|
13
|
-
|
|
14
5
|
// Factur-X PDF attachment filename
|
|
15
6
|
export const FACTURX_ATTACHMENT_FILENAME = 'factur-x.xml';
|
|
16
7
|
|
|
@@ -59,8 +59,8 @@ export class FacturXValidator extends CIIBaseValidator {
|
|
|
59
59
|
|
|
60
60
|
let valid = true;
|
|
61
61
|
|
|
62
|
-
// BR-
|
|
63
|
-
valid = this.
|
|
62
|
+
// BR-CO-10 to BR-CO-17: the stated totals, VAT breakdown and line amounts agree with each other
|
|
63
|
+
valid = this.validateStatedTotals() && valid;
|
|
64
64
|
|
|
65
65
|
// BR-CO-3: Value added tax point date (BT-7) and Value added tax point date code (BT-8) are mutually exclusive
|
|
66
66
|
valid = this.validateMutuallyExclusiveFields() && valid;
|
|
@@ -70,6 +70,9 @@ 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-CO-26: the seller states a seller, legal registration or VAT identifier
|
|
74
|
+
valid = this.validateSellerIdentifier() && valid;
|
|
75
|
+
|
|
73
76
|
// BR-23: every line has a unit code
|
|
74
77
|
valid = this.validateLineUnitCodePresence() && valid;
|
|
75
78
|
|
|
@@ -79,47 +82,6 @@ export class FacturXValidator extends CIIBaseValidator {
|
|
|
79
82
|
return valid;
|
|
80
83
|
}
|
|
81
84
|
|
|
82
|
-
/**
|
|
83
|
-
* Validates amount calculations in the invoice
|
|
84
|
-
* @returns True if amount validation passed
|
|
85
|
-
*/
|
|
86
|
-
private validateAmounts(): boolean {
|
|
87
|
-
if (!this.doc) return false;
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
// Extract amounts
|
|
91
|
-
const totalAmount = this.getNumber(
|
|
92
|
-
'//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:GrandTotalAmount'
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
const paidAmount = this.getNumber(
|
|
96
|
-
'//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:TotalPrepaidAmount'
|
|
97
|
-
) || 0;
|
|
98
|
-
|
|
99
|
-
const dueAmount = this.getNumber(
|
|
100
|
-
'//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:DuePayableAmount'
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
// Calculate expected due amount
|
|
104
|
-
const expectedDueAmount = totalAmount - paidAmount;
|
|
105
|
-
|
|
106
|
-
// Compare with a small tolerance for rounding errors
|
|
107
|
-
if (Math.abs(dueAmount - expectedDueAmount) > 0.01) {
|
|
108
|
-
this.addError(
|
|
109
|
-
'BR-16',
|
|
110
|
-
`Amount due for payment (${dueAmount}) must equal Invoice total amount with VAT (${totalAmount}) - Paid amount (${paidAmount})`,
|
|
111
|
-
'//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation'
|
|
112
|
-
);
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return true;
|
|
117
|
-
} catch (error) {
|
|
118
|
-
this.addError('FX-AMOUNT', `Error validating amounts: ${error}`, '/');
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
85
|
/**
|
|
124
86
|
* Validates mutually exclusive fields
|
|
125
87
|
* @returns True if validation passed
|
|
@@ -88,6 +88,12 @@ export class ZUGFeRDDecoder extends CIIBaseDecoder {
|
|
|
88
88
|
// Reverse charge: every line has the VAT category AE
|
|
89
89
|
const reverseCharge = this.isReverseChargeDocument();
|
|
90
90
|
|
|
91
|
+
// the invoicing period (BG-14) and the seller electronic address (BT-34), when stated
|
|
92
|
+
const invoicingPeriod = this.invoicingPeriodFields(this.extractInvoicingPeriod());
|
|
93
|
+
// the buyer reference (BT-10), when stated
|
|
94
|
+
const buyerReference = this.extractBuyerReference();
|
|
95
|
+
const electronicAddress = this.extractSellerElectronicAddress();
|
|
96
|
+
|
|
91
97
|
const paidAmount = readPaidAmount(this.getText('//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:TotalPrepaidAmount'), 'zugferd');
|
|
92
98
|
|
|
93
99
|
// Create the common invoice data
|
|
@@ -116,6 +122,11 @@ export class ZUGFeRDDecoder extends CIIBaseDecoder {
|
|
|
116
122
|
notes: notes,
|
|
117
123
|
// the day of supply as the document states it (BT-72)
|
|
118
124
|
...(deliveryDate === undefined ? {} : { deliveryDate }),
|
|
125
|
+
...invoicingPeriod,
|
|
126
|
+
// the specification identifier (BT-24), with the metadata of a one-date period
|
|
127
|
+
...this.metadataFields(invoicingPeriod),
|
|
128
|
+
...(buyerReference === undefined ? {} : { buyerReference }),
|
|
129
|
+
...(electronicAddress === undefined ? {} : { electronicAddress }),
|
|
119
130
|
objectActions: [],
|
|
120
131
|
...getPrecedingInvoiceFields(accountingDocType, this.extractPrecedingInvoiceReferences()),
|
|
121
132
|
// the paid amount (BT-113); the amount due (BT-115) follows from it and the totals
|
|
@@ -171,6 +182,9 @@ export class ZUGFeRDDecoder extends CIIBaseDecoder {
|
|
|
171
182
|
// the tax registration identifier (BT-32 for the seller), the Steuernummer: scheme FC
|
|
172
183
|
const taxNumber = this.getText(`${partyXPath}/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="FC"]`) || '';
|
|
173
184
|
|
|
185
|
+
// the party identifiers (BT-29 seller, BT-46 buyer)
|
|
186
|
+
const additionalIdentifiers = this.extractPartyIdentifiers(partyXPath);
|
|
187
|
+
|
|
174
188
|
// Create contact object
|
|
175
189
|
return {
|
|
176
190
|
type: 'company',
|
|
@@ -184,7 +198,8 @@ export class ZUGFeRDDecoder extends CIIBaseDecoder {
|
|
|
184
198
|
registrationId: registrationId,
|
|
185
199
|
registrationName: '',
|
|
186
200
|
...(taxNumber ? { taxNumber } : {})
|
|
187
|
-
}
|
|
201
|
+
},
|
|
202
|
+
...(additionalIdentifiers.length > 0 ? { additionalIdentifiers } : {})
|
|
188
203
|
} as business.TContact;
|
|
189
204
|
}
|
|
190
205
|
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { CIIBaseEncoder } from '../cii.encoder.js';
|
|
2
2
|
import type { TAccountingDoc, TCreditNote, TInvoiceDocument } from '../../../interfaces/common.js';
|
|
3
|
-
import { ZUGFERD_PROFILE_IDS } from './zugferd.types.js';
|
|
4
|
-
import { CIIProfile } from '../cii.types.js';
|
|
5
3
|
import { DOMParser, XMLSerializer } from '../../../plugins.js';
|
|
6
4
|
import { getDocumentTypeCode } from '../../utils/document.typecode.js';
|
|
7
|
-
import {
|
|
5
|
+
import { getWritableDueDate } from '../../utils/date.value.js';
|
|
8
6
|
import { getPaymentTermsNote } from '../../utils/payment.terms.js';
|
|
9
7
|
import { computeDocumentTotals } from '../../utils/document.totals.js';
|
|
10
8
|
import { getPaymentTotals } from '../../utils/paid.amount.js';
|
|
@@ -15,12 +13,6 @@ import { resolveCountryCode } from '../../utils/country.code.js';
|
|
|
15
13
|
* Encoder for ZUGFeRD invoice format
|
|
16
14
|
*/
|
|
17
15
|
export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
18
|
-
constructor() {
|
|
19
|
-
super();
|
|
20
|
-
// Set default profile to BASIC
|
|
21
|
-
this.profile = CIIProfile.BASIC;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
16
|
/**
|
|
25
17
|
* Encodes a credit note into ZUGFeRD XML
|
|
26
18
|
* @param creditNote Credit note to encode
|
|
@@ -102,15 +94,7 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
102
94
|
// Add ID element with profile
|
|
103
95
|
const idElement = doc.createElement('ram:ID');
|
|
104
96
|
|
|
105
|
-
|
|
106
|
-
let profileId = ZUGFERD_PROFILE_IDS.BASIC;
|
|
107
|
-
if (this.profile === CIIProfile.COMFORT) {
|
|
108
|
-
profileId = ZUGFERD_PROFILE_IDS.COMFORT;
|
|
109
|
-
} else if (this.profile === CIIProfile.EXTENDED) {
|
|
110
|
-
profileId = ZUGFERD_PROFILE_IDS.EXTENDED;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
idElement.textContent = profileId;
|
|
97
|
+
idElement.textContent = this.getProfileId();
|
|
114
98
|
guidelineElement.appendChild(idElement);
|
|
115
99
|
}
|
|
116
100
|
|
|
@@ -217,12 +201,8 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
217
201
|
const agreementElement = doc.createElement('ram:ApplicableHeaderTradeAgreement');
|
|
218
202
|
transactionElement.appendChild(agreementElement);
|
|
219
203
|
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
const buyerRefElement = doc.createElement('ram:BuyerReference');
|
|
223
|
-
buyerRefElement.textContent = invoice.buyerReference;
|
|
224
|
-
agreementElement.appendChild(buyerRefElement);
|
|
225
|
-
}
|
|
204
|
+
// Buyer reference (BT-10)
|
|
205
|
+
this.addBuyerReference(doc, agreementElement, invoice);
|
|
226
206
|
|
|
227
207
|
// Add seller
|
|
228
208
|
const sellerElement = doc.createElement('ram:SellerTradeParty');
|
|
@@ -230,14 +210,7 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
230
210
|
|
|
231
211
|
// Seller electronic address (BT-34): ram:URIUniversalCommunication/ram:URIID of the party; a
|
|
232
212
|
// trade contact (ram:DefinedTradeContact) has no URIID
|
|
233
|
-
|
|
234
|
-
const communicationElement = doc.createElement('ram:URIUniversalCommunication');
|
|
235
|
-
const uriElement = doc.createElement('ram:URIID');
|
|
236
|
-
uriElement.setAttribute('schemeID', invoice.electronicAddress.scheme);
|
|
237
|
-
uriElement.textContent = invoice.electronicAddress.value;
|
|
238
|
-
communicationElement.appendChild(uriElement);
|
|
239
|
-
sellerElement.appendChild(communicationElement);
|
|
240
|
-
}
|
|
213
|
+
this.addSellerElectronicAddress(doc, sellerElement, invoice);
|
|
241
214
|
|
|
242
215
|
agreementElement.appendChild(sellerElement);
|
|
243
216
|
|
|
@@ -255,6 +228,9 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
255
228
|
* @param partyRole Whether the party is the seller or the buyer
|
|
256
229
|
*/
|
|
257
230
|
private addPartyInfo(doc: Document, partyElement: Element, party: any, partyRole: 'seller' | 'buyer'): void {
|
|
231
|
+
// Party identifiers (BT-29 seller, BT-46 buyer)
|
|
232
|
+
this.addPartyIdentifiers(doc, partyElement, party);
|
|
233
|
+
|
|
258
234
|
// Add name
|
|
259
235
|
const nameElement = doc.createElement('ram:Name');
|
|
260
236
|
nameElement.textContent = party.name;
|
|
@@ -443,31 +419,7 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
443
419
|
this.addTaxDetails(doc, settlementElement, invoice);
|
|
444
420
|
|
|
445
421
|
// Invoicing period (BG-14): in the header settlement, where the CII schema puts it
|
|
446
|
-
|
|
447
|
-
const periodElement = doc.createElement('ram:BillingSpecifiedPeriod');
|
|
448
|
-
|
|
449
|
-
// Start date
|
|
450
|
-
if (invoice.periodOfPerformance.from !== undefined) {
|
|
451
|
-
const startDateElement = doc.createElement('ram:StartDateTime');
|
|
452
|
-
const startDateStringElement = doc.createElement('udt:DateTimeString');
|
|
453
|
-
startDateStringElement.setAttribute('format', '102'); // YYYYMMDD format
|
|
454
|
-
startDateStringElement.textContent = this.formatDateYYYYMMDD(invoice.periodOfPerformance.from, 'BT-73 invoicing period start date');
|
|
455
|
-
startDateElement.appendChild(startDateStringElement);
|
|
456
|
-
periodElement.appendChild(startDateElement);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// End date
|
|
460
|
-
if (invoice.periodOfPerformance.to !== undefined) {
|
|
461
|
-
const endDateElement = doc.createElement('ram:EndDateTime');
|
|
462
|
-
const endDateStringElement = doc.createElement('udt:DateTimeString');
|
|
463
|
-
endDateStringElement.setAttribute('format', '102'); // YYYYMMDD format
|
|
464
|
-
endDateStringElement.textContent = this.formatDateYYYYMMDD(invoice.periodOfPerformance.to, 'BT-74 invoicing period end date');
|
|
465
|
-
endDateElement.appendChild(endDateStringElement);
|
|
466
|
-
periodElement.appendChild(endDateElement);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
settlementElement.appendChild(periodElement);
|
|
470
|
-
}
|
|
422
|
+
this.addInvoicingPeriod(doc, settlementElement, invoice);
|
|
471
423
|
|
|
472
424
|
// Add totals
|
|
473
425
|
this.addMonetarySummation(doc, settlementElement, invoice);
|
|
@@ -663,18 +615,4 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
663
615
|
}
|
|
664
616
|
}
|
|
665
617
|
}
|
|
666
|
-
|
|
667
|
-
/**
|
|
668
|
-
* Formats a date as YYYYMMDD
|
|
669
|
-
* @param timestamp Timestamp to format
|
|
670
|
-
* @returns Formatted date string
|
|
671
|
-
*/
|
|
672
|
-
private formatDateYYYYMMDD(timestamp: number, field: string): string {
|
|
673
|
-
const date = getWritableDate(timestamp, field, 'cii');
|
|
674
|
-
// the UTC fields: a calendar day is its UTC midnight, whatever the server's zone
|
|
675
|
-
const year = date.getUTCFullYear();
|
|
676
|
-
const month = (date.getUTCMonth() + 1).toString().padStart(2, '0');
|
|
677
|
-
const day = date.getUTCDate().toString().padStart(2, '0');
|
|
678
|
-
return `${year}${month}${day}`;
|
|
679
|
-
}
|
|
680
|
-
}
|
|
618
|
+
}
|
|
@@ -1,16 +1,7 @@
|
|
|
1
|
-
import { CIIProfile, CII_PROFILE_IDS } from '../cii.types.js';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* ZUGFeRD specific constants and types
|
|
5
3
|
*/
|
|
6
4
|
|
|
7
|
-
// ZUGFeRD profile IDs
|
|
8
|
-
export const ZUGFERD_PROFILE_IDS = {
|
|
9
|
-
BASIC: CII_PROFILE_IDS.ZUGFERD_BASIC,
|
|
10
|
-
COMFORT: CII_PROFILE_IDS.ZUGFERD_COMFORT,
|
|
11
|
-
EXTENDED: CII_PROFILE_IDS.ZUGFERD_EXTENDED
|
|
12
|
-
};
|
|
13
|
-
|
|
14
5
|
// ZUGFeRD PDF attachment filename
|
|
15
6
|
export const ZUGFERD_ATTACHMENT_FILENAME = 'zugferd-invoice.xml';
|
|
16
7
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CIIBaseDecoder } from '../cii.decoder.js';
|
|
2
|
+
import type { ICIIInvoicingPeriod } from '../cii.decoder.js';
|
|
2
3
|
import type {
|
|
3
4
|
TAccountingDocEnvelope,
|
|
4
5
|
TAccountingDocType,
|
|
@@ -9,6 +10,7 @@ import { ZUGFERD_V1_NAMESPACES } from '../cii.types.js';
|
|
|
9
10
|
import { business, finance, xpath } from '../../../plugins.js';
|
|
10
11
|
import { EN16931Validator } from '../../validation/en16931.validator.js';
|
|
11
12
|
import { readPaidAmount } from '../../utils/paid.amount.js';
|
|
13
|
+
import type { IEInvoiceStatedValues } from '../../../interfaces/stated.values.js';
|
|
12
14
|
|
|
13
15
|
/** XPath selection with the ZUGFeRD v1 namespaces */
|
|
14
16
|
const zugferdV1Select = xpath.useNamespaces({
|
|
@@ -69,6 +71,38 @@ export class ZUGFeRDV1Decoder extends CIIBaseDecoder {
|
|
|
69
71
|
return dueDate ? this.parseRequiredCIIDate(dueDate, this.v1Text(`${dueDatePath}/@format`)) : undefined;
|
|
70
72
|
}
|
|
71
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Reads the buyer reference of the v1 header trade agreement
|
|
76
|
+
* (`ram:ApplicableSupplyChainTradeAgreement/ram:BuyerReference`); undefined
|
|
77
|
+
* when the document states none.
|
|
78
|
+
*/
|
|
79
|
+
protected override extractBuyerReference(): string | undefined {
|
|
80
|
+
return (
|
|
81
|
+
this.v1Text('/rsm:CrossIndustryDocument/rsm:SpecifiedSupplyChainTradeTransaction/ram:ApplicableSupplyChainTradeAgreement/ram:BuyerReference') ||
|
|
82
|
+
undefined
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Reads the identifier of the v1 document context
|
|
88
|
+
* (`rsm:SpecifiedExchangedDocumentContext/ram:GuidelineSpecifiedDocumentContextParameter/ram:ID`);
|
|
89
|
+
* undefined when the document states none.
|
|
90
|
+
*/
|
|
91
|
+
protected override extractSpecificationIdentifier(): string | undefined {
|
|
92
|
+
return (
|
|
93
|
+
this.v1Text('/rsm:CrossIndustryDocument/rsm:SpecifiedExchangedDocumentContext/ram:GuidelineSpecifiedDocumentContextParameter/ram:ID') ||
|
|
94
|
+
undefined
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* ZUGFeRD 1.0 predates EN 16931 and has no business terms to read the stated
|
|
100
|
+
* values by; a v1 document has none
|
|
101
|
+
*/
|
|
102
|
+
protected override readStatedValues(): IEInvoiceStatedValues | undefined {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
72
106
|
/** The trimmed text of the first node a v1 path selects, or '' */
|
|
73
107
|
private v1Text(path: string): string {
|
|
74
108
|
return String(zugferdV1Select(`string(${path})`, this.doc)).trim();
|
|
@@ -90,6 +124,19 @@ export class ZUGFeRDV1Decoder extends CIIBaseDecoder {
|
|
|
90
124
|
return this.parseRequiredCIIDate(deliveryDate, format);
|
|
91
125
|
}
|
|
92
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Reads the invoicing period of the v1 header settlement
|
|
129
|
+
* (`ram:ApplicableSupplyChainTradeSettlement/ram:BillingSpecifiedPeriod`).
|
|
130
|
+
* A ZUGFeRD 1.0 trade party has no electronic address, so a v1 document has
|
|
131
|
+
* no seller electronic address (BT-34).
|
|
132
|
+
*/
|
|
133
|
+
protected override extractInvoicingPeriod(): ICIIInvoicingPeriod {
|
|
134
|
+
return this.readInvoicingPeriod(
|
|
135
|
+
'/rsm:CrossIndustryDocument/rsm:SpecifiedSupplyChainTradeTransaction/ram:ApplicableSupplyChainTradeSettlement/ram:BillingSpecifiedPeriod',
|
|
136
|
+
(path) => this.v1Text(path),
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
93
140
|
/**
|
|
94
141
|
* Decodes a ZUGFeRD v1 credit note
|
|
95
142
|
* @returns Promise resolving to a TCreditNote object
|
|
@@ -164,6 +211,11 @@ export class ZUGFeRDV1Decoder extends CIIBaseDecoder {
|
|
|
164
211
|
// Reverse charge: every line has the VAT category AE
|
|
165
212
|
const reverseCharge = this.isReverseChargeDocument();
|
|
166
213
|
|
|
214
|
+
// the invoicing period (BG-14), when stated
|
|
215
|
+
const invoicingPeriod = this.invoicingPeriodFields(this.extractInvoicingPeriod());
|
|
216
|
+
// the buyer reference (BT-10), when stated
|
|
217
|
+
const buyerReference = this.extractBuyerReference();
|
|
218
|
+
|
|
167
219
|
// the paid amount (BT-113) of the v1 header settlement, read with the v1 namespaces
|
|
168
220
|
const paidAmount = readPaidAmount(
|
|
169
221
|
this.v1Text(
|
|
@@ -198,6 +250,10 @@ export class ZUGFeRDV1Decoder extends CIIBaseDecoder {
|
|
|
198
250
|
notes: notes,
|
|
199
251
|
// the day of supply as the document states it (BT-72), never the issue date in its place
|
|
200
252
|
...(deliveryDate === undefined ? {} : { deliveryDate }),
|
|
253
|
+
...invoicingPeriod,
|
|
254
|
+
// the specification identifier (BT-24), with the metadata of a one-date period
|
|
255
|
+
...this.metadataFields(invoicingPeriod),
|
|
256
|
+
...(buyerReference === undefined ? {} : { buyerReference }),
|
|
201
257
|
...(paidAmount === undefined ? {} : { paidAmount }),
|
|
202
258
|
objectActions: []
|
|
203
259
|
};
|
|
@@ -24,9 +24,13 @@ export class ZUGFeRDValidator extends CIIBaseValidator {
|
|
|
24
24
|
* @returns True if business validation passed
|
|
25
25
|
*/
|
|
26
26
|
protected validateBusinessRules(): boolean {
|
|
27
|
+
// BR-CO-10 to BR-CO-17: the stated totals, VAT breakdown and line amounts agree with each other
|
|
28
|
+
const totalsAgree = this.validateStatedTotals();
|
|
29
|
+
// BR-CO-26: the seller states a seller, legal registration or VAT identifier
|
|
30
|
+
const sellerIdentified = this.validateSellerIdentifier();
|
|
27
31
|
// BR-23: every line has a unit code
|
|
28
32
|
const linesHaveUnits = this.validateLineUnitCodePresence();
|
|
29
33
|
// BR-CL-23: unit codes of UN/ECE Recommendation 20 with the Recommendation 21 extension
|
|
30
|
-
return this.validateUnitCodes() && linesHaveUnits;
|
|
34
|
+
return this.validateUnitCodes() && linesHaveUnits && sellerIdentified && totalsAgree;
|
|
31
35
|
}
|
|
32
36
|
}
|
|
@@ -45,14 +45,15 @@ export class SemanticModelAdapter {
|
|
|
45
45
|
},
|
|
46
46
|
|
|
47
47
|
// Process metadata
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
// the specification identifier (BT-24) is customizationId, the business process type (BT-23) profileId
|
|
49
|
+
processControl: invoice.metadata?.customizationId ? {
|
|
50
|
+
...(invoice.metadata.profileId ? { businessProcessType: invoice.metadata.profileId } : {}),
|
|
51
|
+
specificationIdentifier: invoice.metadata.customizationId
|
|
51
52
|
} : undefined,
|
|
52
53
|
|
|
53
54
|
// References
|
|
54
55
|
references: {
|
|
55
|
-
buyerReference: invoice.
|
|
56
|
+
buyerReference: invoice.buyerReference,
|
|
56
57
|
projectReference: invoice.metadata?.extensions?.projectReference,
|
|
57
58
|
contractReference: invoice.metadata?.extensions?.contractReference,
|
|
58
59
|
purchaseOrderReference: invoice.metadata?.extensions?.purchaseOrderReference,
|
|
@@ -143,19 +144,18 @@ export class SemanticModelAdapter {
|
|
|
143
144
|
if (model.processControl) {
|
|
144
145
|
invoice.metadata = {
|
|
145
146
|
...invoice.metadata,
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
...invoice.metadata?.extensions,
|
|
149
|
-
businessProcessId: model.processControl.businessProcessType
|
|
150
|
-
}
|
|
147
|
+
customizationId: model.processControl.specificationIdentifier,
|
|
148
|
+
...(model.processControl.businessProcessType ? { profileId: model.processControl.businessProcessType } : {}),
|
|
151
149
|
};
|
|
152
150
|
}
|
|
153
151
|
|
|
154
|
-
// Set references
|
|
152
|
+
// Set references; the buyer reference (BT-10) is the envelope's
|
|
155
153
|
if (model.references) {
|
|
154
|
+
if (model.references.buyerReference) {
|
|
155
|
+
invoice.buyerReference = model.references.buyerReference;
|
|
156
|
+
}
|
|
156
157
|
invoice.metadata = {
|
|
157
158
|
...invoice.metadata,
|
|
158
|
-
buyerReference: model.references.buyerReference,
|
|
159
159
|
extensions: {
|
|
160
160
|
...invoice.metadata?.extensions,
|
|
161
161
|
contractReference: model.references.contractReference,
|
|
@@ -257,8 +257,8 @@ export class SemanticModelAdapter {
|
|
|
257
257
|
tradingName: contact.tradingName,
|
|
258
258
|
identifier: contact.registrationDetails?.registrationId,
|
|
259
259
|
legalRegistrationIdentifier: contact.registrationDetails?.registrationId,
|
|
260
|
-
vatIdentifier: contact.registrationDetails?.vatId ||
|
|
261
|
-
taxRegistrationIdentifier: contact.
|
|
260
|
+
vatIdentifier: contact.registrationDetails?.vatId || undefined,
|
|
261
|
+
taxRegistrationIdentifier: contact.registrationDetails?.taxNumber || undefined,
|
|
262
262
|
additionalLegalInfo: contact.description,
|
|
263
263
|
electronicAddress: contact.email || contact.contact?.email
|
|
264
264
|
};
|
|
@@ -266,7 +266,8 @@ export class SemanticModelAdapter {
|
|
|
266
266
|
return {
|
|
267
267
|
name: contact.name || `${contact.firstName || ''} ${contact.lastName || ''}`.trim(),
|
|
268
268
|
identifier: contact.registrationDetails?.registrationId,
|
|
269
|
-
vatIdentifier: contact.registrationDetails?.vatId ||
|
|
269
|
+
vatIdentifier: contact.registrationDetails?.vatId || undefined,
|
|
270
|
+
taxRegistrationIdentifier: contact.registrationDetails?.taxNumber || undefined,
|
|
270
271
|
electronicAddress: contact.email
|
|
271
272
|
};
|
|
272
273
|
}
|
|
@@ -283,14 +284,14 @@ export class SemanticModelAdapter {
|
|
|
283
284
|
tradingName: contact.tradingName,
|
|
284
285
|
identifier: contact.registrationDetails?.registrationId,
|
|
285
286
|
legalRegistrationIdentifier: contact.registrationDetails?.registrationId,
|
|
286
|
-
vatIdentifier: contact.registrationDetails?.vatId ||
|
|
287
|
+
vatIdentifier: contact.registrationDetails?.vatId || undefined,
|
|
287
288
|
electronicAddress: contact.email || contact.contact?.email
|
|
288
289
|
};
|
|
289
290
|
} else {
|
|
290
291
|
return {
|
|
291
292
|
name: contact.name || `${contact.firstName || ''} ${contact.lastName || ''}`.trim(),
|
|
292
293
|
identifier: contact.registrationDetails?.registrationId,
|
|
293
|
-
vatIdentifier: contact.registrationDetails?.vatId ||
|
|
294
|
+
vatIdentifier: contact.registrationDetails?.vatId || undefined,
|
|
294
295
|
electronicAddress: contact.email
|
|
295
296
|
};
|
|
296
297
|
}
|
|
@@ -509,7 +510,8 @@ export class SemanticModelAdapter {
|
|
|
509
510
|
registrationDetails: {
|
|
510
511
|
vatId: seller.vatIdentifier || '',
|
|
511
512
|
registrationId: seller.identifier || seller.legalRegistrationIdentifier || '',
|
|
512
|
-
registrationName: seller.name
|
|
513
|
+
registrationName: seller.name,
|
|
514
|
+
...(seller.taxRegistrationIdentifier ? { taxNumber: seller.taxRegistrationIdentifier } : {})
|
|
513
515
|
},
|
|
514
516
|
status: 'active',
|
|
515
517
|
foundedDate: {
|
|
@@ -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
|
}
|