@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,10 +1,22 @@
|
|
|
1
1
|
import { BaseDecoder } from '../base/base.decoder.js';
|
|
2
2
|
import type { TAccountingDoc, TCreditNote, TInvoiceDocument } from '../../interfaces/common.js';
|
|
3
|
-
import { CII_NAMESPACES
|
|
3
|
+
import { CII_NAMESPACES } from './cii.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
7
|
import type { TPrecedingInvoiceReference } from '../utils/preceding.invoice.js';
|
|
8
|
+
import type { IEInvoiceMetadata } from '../../interfaces/en16931-metadata.js';
|
|
9
|
+
import type { IEInvoiceStatedValues } from '../../interfaces/stated.values.js';
|
|
10
|
+
import { readCiiStatedValues } from '../utils/stated.values.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The invoicing period (BG-14) a CII document states: its start date (BT-73),
|
|
14
|
+
* its end date (BT-74), or both (BR-CO-19), as the UTC timestamps of the days
|
|
15
|
+
*/
|
|
16
|
+
export interface ICIIInvoicingPeriod {
|
|
17
|
+
start?: number;
|
|
18
|
+
end?: number;
|
|
19
|
+
}
|
|
8
20
|
|
|
9
21
|
const ciiParser = new DOMParser();
|
|
10
22
|
const ciiNamespaces = {
|
|
@@ -22,7 +34,6 @@ export abstract class CIIBaseDecoder extends BaseDecoder {
|
|
|
22
34
|
protected doc: Document;
|
|
23
35
|
protected namespaces: Record<string, string>;
|
|
24
36
|
protected select: xpath.XPathSelect;
|
|
25
|
-
protected profile: CIIProfile = CIIProfile.EN16931;
|
|
26
37
|
|
|
27
38
|
constructor(xml: string, skipValidation: boolean = false) {
|
|
28
39
|
super(xml, skipValidation);
|
|
@@ -35,20 +46,35 @@ export abstract class CIIBaseDecoder extends BaseDecoder {
|
|
|
35
46
|
|
|
36
47
|
// Create XPath selector with namespaces
|
|
37
48
|
this.select = ciiSelect;
|
|
38
|
-
|
|
39
|
-
// Detect profile
|
|
40
|
-
this.detectProfile();
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
public override getParsedDocument(): Document {
|
|
44
52
|
return this.doc;
|
|
45
53
|
}
|
|
46
54
|
|
|
55
|
+
/** The values the document states, read by `decode` */
|
|
56
|
+
private statedValues?: IEInvoiceStatedValues;
|
|
57
|
+
|
|
58
|
+
public override getStatedValues(): IEInvoiceStatedValues | undefined {
|
|
59
|
+
return this.statedValues;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Reads the values a CII (D16B) document states, by their EN 16931 business
|
|
64
|
+
* terms
|
|
65
|
+
*/
|
|
66
|
+
protected readStatedValues(): IEInvoiceStatedValues | undefined {
|
|
67
|
+
return this.assertStatedValuesReadable(readCiiStatedValues(this.doc), 'cii');
|
|
68
|
+
}
|
|
69
|
+
|
|
47
70
|
/**
|
|
48
71
|
* Decodes CII XML into an accounting document
|
|
49
72
|
* @returns Promise resolving to the accounting document
|
|
50
73
|
*/
|
|
51
74
|
public async decode(): Promise<TAccountingDoc> {
|
|
75
|
+
// the values the document states, read first: an amount that is no number is refused
|
|
76
|
+
this.statedValues = this.readStatedValues();
|
|
77
|
+
|
|
52
78
|
// CII has one root for every document; the document type code (BT-3) alone tells them apart
|
|
53
79
|
const accountingDocType = getAccountingDocType(
|
|
54
80
|
this.getText('/rsm:CrossIndustryInvoice/rsm:ExchangedDocument/ram:TypeCode'),
|
|
@@ -59,33 +85,6 @@ export abstract class CIIBaseDecoder extends BaseDecoder {
|
|
|
59
85
|
return this.decodeInvoice(accountingDocType);
|
|
60
86
|
}
|
|
61
87
|
|
|
62
|
-
/**
|
|
63
|
-
* Detects the CII profile from the XML
|
|
64
|
-
*/
|
|
65
|
-
protected detectProfile(): void {
|
|
66
|
-
// Look for profile identifier
|
|
67
|
-
const profileNode = this.select(
|
|
68
|
-
'string(//rsm:ExchangedDocumentContext/ram:GuidelineSpecifiedDocumentContextParameter/ram:ID)',
|
|
69
|
-
this.doc
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
if (profileNode) {
|
|
73
|
-
const profileText = profileNode.toString();
|
|
74
|
-
|
|
75
|
-
if (profileText.includes('BASIC')) {
|
|
76
|
-
this.profile = CIIProfile.BASIC;
|
|
77
|
-
} else if (profileText.includes('EN16931')) {
|
|
78
|
-
this.profile = CIIProfile.EN16931;
|
|
79
|
-
} else if (profileText.includes('EXTENDED')) {
|
|
80
|
-
this.profile = CIIProfile.EXTENDED;
|
|
81
|
-
} else if (profileText.includes('MINIMUM')) {
|
|
82
|
-
this.profile = CIIProfile.MINIMUM;
|
|
83
|
-
} else if (profileText.includes('COMFORT')) {
|
|
84
|
-
this.profile = CIIProfile.COMFORT;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
88
|
/**
|
|
90
89
|
* Decodes a CII credit note
|
|
91
90
|
* @returns Promise resolving to a TCreditNote object
|
|
@@ -186,6 +185,134 @@ export abstract class CIIBaseDecoder extends BaseDecoder {
|
|
|
186
185
|
return this.parseRequiredCIIDate(deliveryDate, this.getText(`${deliveryDatePath}/@format`).trim());
|
|
187
186
|
}
|
|
188
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Reads the invoicing period (BG-14) of the header settlement: the start date
|
|
190
|
+
* (BT-73), the end date (BT-74), or both (BR-CO-19). A line's own period
|
|
191
|
+
* (BG-26) is not the document's.
|
|
192
|
+
*/
|
|
193
|
+
protected extractInvoicingPeriod(): ICIIInvoicingPeriod {
|
|
194
|
+
return this.readInvoicingPeriod(
|
|
195
|
+
'/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:BillingSpecifiedPeriod',
|
|
196
|
+
(path) => this.getText(path).trim(),
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Reads an invoicing period from its start and end date strings; each end is
|
|
202
|
+
* read when the document states it
|
|
203
|
+
* @param periodPath The path of the period element
|
|
204
|
+
* @param text The trimmed text a path selects, or ''
|
|
205
|
+
*/
|
|
206
|
+
protected readInvoicingPeriod(periodPath: string, text: (path: string) => string): ICIIInvoicingPeriod {
|
|
207
|
+
const startPath = `${periodPath}/ram:StartDateTime/udt:DateTimeString`;
|
|
208
|
+
const endPath = `${periodPath}/ram:EndDateTime/udt:DateTimeString`;
|
|
209
|
+
const start = text(startPath);
|
|
210
|
+
const end = text(endPath);
|
|
211
|
+
return {
|
|
212
|
+
...(start ? { start: this.parseRequiredCIIDate(start, text(`${startPath}/@format`)) } : {}),
|
|
213
|
+
...(end ? { end: this.parseRequiredCIIDate(end, text(`${endPath}/@format`)) } : {}),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* The accounting document fields of a stated invoicing period. With both
|
|
219
|
+
* dates it is `periodOfPerformance`. The envelope's period has both dates, so
|
|
220
|
+
* a period that states only its start or only its end keeps that date where
|
|
221
|
+
* the UBL decoder keeps it, in `metadata.extensions.dateInformation`
|
|
222
|
+
* (`periodStart` or `periodEnd`, the calendar day as YYYY-MM-DD); the CII and
|
|
223
|
+
* UBL encoders write it back from there.
|
|
224
|
+
* @param period The invoicing period the document states
|
|
225
|
+
*/
|
|
226
|
+
protected invoicingPeriodFields(
|
|
227
|
+
period: ICIIInvoicingPeriod,
|
|
228
|
+
): { periodOfPerformance: { from: number; to: number } } | { metadata: IEInvoiceMetadata } | Record<string, never> {
|
|
229
|
+
if (period.start !== undefined && period.end !== undefined) {
|
|
230
|
+
return { periodOfPerformance: { from: period.start, to: period.end } };
|
|
231
|
+
}
|
|
232
|
+
// the calendar day of the UTC timestamp the decoders return for a date
|
|
233
|
+
const dayOf = (timestamp: number) => new Date(timestamp).toISOString().slice(0, 10);
|
|
234
|
+
if (period.start !== undefined) {
|
|
235
|
+
return { metadata: { extensions: { dateInformation: { periodStart: dayOf(period.start) } } } };
|
|
236
|
+
}
|
|
237
|
+
if (period.end !== undefined) {
|
|
238
|
+
return { metadata: { extensions: { dateInformation: { periodEnd: dayOf(period.end) } } } };
|
|
239
|
+
}
|
|
240
|
+
return {};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Reads the seller electronic address (BT-34),
|
|
245
|
+
* `ram:URIUniversalCommunication/ram:URIID` of the seller, with its scheme;
|
|
246
|
+
* undefined when the document states none.
|
|
247
|
+
*/
|
|
248
|
+
protected extractSellerElectronicAddress(): { scheme: string; value: string } | undefined {
|
|
249
|
+
const uriPath =
|
|
250
|
+
'/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeAgreement/ram:SellerTradeParty/ram:URIUniversalCommunication/ram:URIID';
|
|
251
|
+
const value = this.getText(uriPath).trim();
|
|
252
|
+
if (!value) {
|
|
253
|
+
return undefined;
|
|
254
|
+
}
|
|
255
|
+
return { scheme: this.getText(`${uriPath}/@schemeID`).trim(), value };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Reads the specification identifier (BT-24),
|
|
260
|
+
* `ram:GuidelineSpecifiedDocumentContextParameter/ram:ID` of the document
|
|
261
|
+
* context; undefined when the document states none.
|
|
262
|
+
*/
|
|
263
|
+
protected extractSpecificationIdentifier(): string | undefined {
|
|
264
|
+
return (
|
|
265
|
+
this.getText('/rsm:CrossIndustryInvoice/rsm:ExchangedDocumentContext/ram:GuidelineSpecifiedDocumentContextParameter/ram:ID').trim() ||
|
|
266
|
+
undefined
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* The accounting document's `metadata`: the specification identifier (BT-24)
|
|
272
|
+
* as `customizationId`, which the validators read to tell an XRechnung, a
|
|
273
|
+
* PEPPOL or a Factur-X document, merged with the metadata of the invoicing
|
|
274
|
+
* period fields (`invoicingPeriodFields`), which it takes the place of in
|
|
275
|
+
* the decoded document.
|
|
276
|
+
* @param invoicingPeriod The invoicing period fields of the document
|
|
277
|
+
*/
|
|
278
|
+
protected metadataFields(
|
|
279
|
+
invoicingPeriod: ReturnType<CIIBaseDecoder['invoicingPeriodFields']>,
|
|
280
|
+
): { metadata: IEInvoiceMetadata } | Record<string, never> {
|
|
281
|
+
const customizationId = this.extractSpecificationIdentifier();
|
|
282
|
+
const periodMetadata = 'metadata' in invoicingPeriod ? invoicingPeriod.metadata : undefined;
|
|
283
|
+
if (customizationId === undefined && periodMetadata === undefined) {
|
|
284
|
+
return {};
|
|
285
|
+
}
|
|
286
|
+
return { metadata: { ...periodMetadata, ...(customizationId === undefined ? {} : { customizationId }) } };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Reads the party identifiers (BT-29 seller, BT-46 buyer) of a trade party:
|
|
291
|
+
* `ram:ID` without a scheme and `ram:GlobalID` with its scheme, kept where the
|
|
292
|
+
* UBL decoder keeps them, as the party's `additionalIdentifiers`
|
|
293
|
+
* @param partyXPath XPath to the trade party
|
|
294
|
+
*/
|
|
295
|
+
protected extractPartyIdentifiers(partyXPath: string): Array<{ value: string; scheme: string }> {
|
|
296
|
+
const nodes = this.select(`${partyXPath}/ram:ID | ${partyXPath}/ram:GlobalID`, this.doc);
|
|
297
|
+
return (Array.isArray(nodes) ? nodes : [])
|
|
298
|
+
.map((node) => {
|
|
299
|
+
const element = node as Element;
|
|
300
|
+
return { value: (element.textContent ?? '').trim(), scheme: element.localName === 'GlobalID' ? (element.getAttribute('schemeID') ?? '').trim() : '' };
|
|
301
|
+
})
|
|
302
|
+
.filter((identifier) => identifier.value !== '');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Reads the buyer reference (BT-10), `ram:BuyerReference` of the header trade
|
|
307
|
+
* agreement; undefined when the document states none.
|
|
308
|
+
*/
|
|
309
|
+
protected extractBuyerReference(): string | undefined {
|
|
310
|
+
return (
|
|
311
|
+
this.getText('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeAgreement/ram:BuyerReference').trim() ||
|
|
312
|
+
undefined
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
189
316
|
/**
|
|
190
317
|
* Gets a text value from an XPath expression
|
|
191
318
|
* @param xpath XPath expression
|
|
@@ -2,9 +2,43 @@ import { BaseEncoder } from '../base/base.encoder.js';
|
|
|
2
2
|
import type { TAccountingDoc, TCreditNote, TInvoiceDocument } from '../../interfaces/common.js';
|
|
3
3
|
import { EInvoiceFormatError } from '../../errors.js';
|
|
4
4
|
import { getPrecedingInvoiceReferences } from '../utils/preceding.invoice.js';
|
|
5
|
-
import { getWritableDate } from '../utils/date.value.js';
|
|
5
|
+
import { getWritableDate, parseXsdDateDay } from '../utils/date.value.js';
|
|
6
6
|
import { assertVatCategoryWritable } from '../utils/vat.category.js';
|
|
7
|
-
import {
|
|
7
|
+
import { assertSellerIdentified } from '../utils/seller.identifier.js';
|
|
8
|
+
import { assertPartyIdentifiersWritable, getPartyIdentifiers } from '../utils/party.identifier.js';
|
|
9
|
+
import { computeDocumentTotals } from '../utils/document.totals.js';
|
|
10
|
+
import { getPaymentTotals } from '../utils/paid.amount.js';
|
|
11
|
+
import { CII_CHILD_SEQUENCES, CII_NAMESPACES, CII_PROFILE_IDS, CIIProfile } from './cii.types.js';
|
|
12
|
+
import type { IEInvoiceMetadata } from '../../interfaces/en16931-metadata.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Why a CII profile is not written. The CII encoders write the full EN 16931
|
|
16
|
+
* content, so the one profile they state is EN 16931 (`urn:cen.eu:en16931:2017`,
|
|
17
|
+
* named COMFORT in ZUGFeRD 2.x); stating another profile for that content would
|
|
18
|
+
* make a document that does not validate against the profile it names.
|
|
19
|
+
*/
|
|
20
|
+
const REFUSED_PROFILES: Partial<Record<CIIProfile, string>> = {
|
|
21
|
+
[CIIProfile.MINIMUM]:
|
|
22
|
+
'the content written is EN 16931, which the MINIMUM profile does not allow, and a MINIMUM document is not an e-invoice (BMF letter of 15 October 2024, Rn. 25 and 30)',
|
|
23
|
+
[CIIProfile.BASIC]:
|
|
24
|
+
'the content written is EN 16931, which neither the BASIC nor the BASIC WL profile allows, and a BASIC WL document is not an e-invoice (BMF letter of 15 October 2024, Rn. 25 and 30)',
|
|
25
|
+
[CIIProfile.EXTENDED]:
|
|
26
|
+
'the output has not been checked against the EXTENDED schema and rules',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Refuses a profile the CII encoders do not write, naming the profile and why
|
|
31
|
+
* @param profile CII profile
|
|
32
|
+
*/
|
|
33
|
+
const assertWritableProfile = (profile: CIIProfile): void => {
|
|
34
|
+
const reason = REFUSED_PROFILES[profile];
|
|
35
|
+
if (reason !== undefined) {
|
|
36
|
+
throw new EInvoiceFormatError(
|
|
37
|
+
`The CII encoders do not write the ${profile} profile: ${reason}; choose ${CIIProfile.EN16931}, the profile whose content they write`,
|
|
38
|
+
{ targetFormat: 'cii', unsupportedFeatures: [`profile ${profile}`] },
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
8
42
|
|
|
9
43
|
/**
|
|
10
44
|
* Base encoder for CII-based invoice formats
|
|
@@ -13,13 +47,26 @@ export abstract class CIIBaseEncoder extends BaseEncoder {
|
|
|
13
47
|
protected profile: CIIProfile = CIIProfile.EN16931;
|
|
14
48
|
|
|
15
49
|
/**
|
|
16
|
-
* Sets the CII profile to use for encoding
|
|
50
|
+
* Sets the CII profile to use for encoding. EN16931 and its ZUGFeRD name
|
|
51
|
+
* COMFORT are written; MINIMUM, BASIC (and with it BASIC WL) and EXTENDED are
|
|
52
|
+
* refused with an `EInvoiceFormatError`.
|
|
17
53
|
* @param profile CII profile
|
|
18
54
|
*/
|
|
19
55
|
public setProfile(profile: CIIProfile): void {
|
|
56
|
+
assertWritableProfile(profile);
|
|
20
57
|
this.profile = profile;
|
|
21
58
|
}
|
|
22
59
|
|
|
60
|
+
/**
|
|
61
|
+
* The specification identifier (BT-24) the document states: the EN 16931
|
|
62
|
+
* profile, the one profile whose content the encoders write. A refused
|
|
63
|
+
* profile a subclass assigned directly is refused here too.
|
|
64
|
+
*/
|
|
65
|
+
protected getProfileId(): string {
|
|
66
|
+
assertWritableProfile(this.profile);
|
|
67
|
+
return CII_PROFILE_IDS.EN16931;
|
|
68
|
+
}
|
|
69
|
+
|
|
23
70
|
/**
|
|
24
71
|
* Encodes an accounting document into CII XML
|
|
25
72
|
* @param invoice Accounting document to encode
|
|
@@ -28,18 +75,15 @@ export abstract class CIIBaseEncoder extends BaseEncoder {
|
|
|
28
75
|
public async encode(invoice: TAccountingDoc): Promise<string> {
|
|
29
76
|
// a reverse charge document that lacks what EN 16931 requires of one is refused (BR-AE-02, BR-AE-05)
|
|
30
77
|
assertVatCategoryWritable(invoice, 'cii');
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
{ targetFormat: 'cii', unsupportedFeatures: ['BG-3 in profile MINIMUM'] },
|
|
36
|
-
);
|
|
37
|
-
}
|
|
78
|
+
// a buyer identifier with the SEPA scheme is refused (BR-CL-10)
|
|
79
|
+
assertPartyIdentifiersWritable(invoice, 'cii');
|
|
80
|
+
// a paid amount the advance payments do not add up to, or with more decimals than the currency has, is refused
|
|
81
|
+
getPaymentTotals(invoice, computeDocumentTotals(invoice));
|
|
38
82
|
// CII has one root for every document; the two paths differ in the type code they write
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return
|
|
83
|
+
const xml = invoice.accountingDocType === 'creditnote' ? await this.encodeCreditNote(invoice) : await this.encodeInvoice(invoice);
|
|
84
|
+
// a seller the written document does not identify is refused (BR-CO-26), never written silently
|
|
85
|
+
assertSellerIdentified(xml, 'cii', 'cii');
|
|
86
|
+
return xml;
|
|
43
87
|
}
|
|
44
88
|
|
|
45
89
|
/**
|
|
@@ -161,4 +205,156 @@ export abstract class CIIBaseEncoder extends BaseEncoder {
|
|
|
161
205
|
protected formatDate(timestamp: number, field: string): string {
|
|
162
206
|
return getWritableDate(timestamp, field, 'cii').toISOString().split('T')[0];
|
|
163
207
|
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Formats a date as YYYYMMDD, the CII date format 102. A value that is no
|
|
211
|
+
* valid timestamp is refused with an `EInvoiceFormatError` naming the field.
|
|
212
|
+
* @param timestamp Timestamp to format
|
|
213
|
+
* @param field The business term the date is written as
|
|
214
|
+
* @returns Formatted date string
|
|
215
|
+
*/
|
|
216
|
+
protected formatDateYYYYMMDD(timestamp: number, field: string): string {
|
|
217
|
+
const date = getWritableDate(timestamp, field, 'cii');
|
|
218
|
+
// the UTC fields: a calendar day is its UTC midnight, whatever the server's zone
|
|
219
|
+
const year = date.getUTCFullYear();
|
|
220
|
+
const month = (date.getUTCMonth() + 1).toString().padStart(2, '0');
|
|
221
|
+
const day = date.getUTCDate().toString().padStart(2, '0');
|
|
222
|
+
return `${year}${month}${day}`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Adds the seller electronic address (BT-34), `electronicAddress`, as
|
|
227
|
+
* `ram:URIUniversalCommunication/ram:URIID` of the seller, with its scheme
|
|
228
|
+
* (BR-62). EN 16931 asks nothing of the seller for it, so a person states one
|
|
229
|
+
* as a company does.
|
|
230
|
+
* @param doc XML document
|
|
231
|
+
* @param sellerElement The seller trade party
|
|
232
|
+
* @param invoice Invoice data
|
|
233
|
+
*/
|
|
234
|
+
protected addSellerElectronicAddress(doc: Document, sellerElement: Element, invoice: TAccountingDoc): void {
|
|
235
|
+
if (!invoice.electronicAddress) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const communicationElement = doc.createElement('ram:URIUniversalCommunication');
|
|
239
|
+
const uriElement = doc.createElement('ram:URIID');
|
|
240
|
+
uriElement.setAttribute('schemeID', invoice.electronicAddress.scheme);
|
|
241
|
+
uriElement.textContent = invoice.electronicAddress.value;
|
|
242
|
+
communicationElement.appendChild(uriElement);
|
|
243
|
+
sellerElement.appendChild(communicationElement);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Adds the party identifiers (BT-29 seller, BT-46 buyer) the party carries:
|
|
248
|
+
* one with a scheme as `ram:GlobalID` with its scheme (BT-29-1, an ISO/IEC
|
|
249
|
+
* 6523 ICD code), one without as `ram:ID`, first in the trade party as the
|
|
250
|
+
* CII schema orders it. A SEPA creditor identifier, which UBL states as a
|
|
251
|
+
* seller identification with the scheme SEPA, is the bank assigned creditor
|
|
252
|
+
* identifier (BT-90) and no party identifier in CII; it is not written here.
|
|
253
|
+
* @param doc XML document
|
|
254
|
+
* @param partyElement The seller or buyer trade party
|
|
255
|
+
* @param party The seller or the buyer
|
|
256
|
+
*/
|
|
257
|
+
protected addPartyIdentifiers(doc: Document, partyElement: Element, party: unknown): void {
|
|
258
|
+
const identifiers = getPartyIdentifiers(party).filter((identifier) => identifier.scheme?.toUpperCase() !== 'SEPA');
|
|
259
|
+
for (const identifier of identifiers.reverse()) {
|
|
260
|
+
const idElement = doc.createElement(identifier.scheme ? 'ram:GlobalID' : 'ram:ID');
|
|
261
|
+
if (identifier.scheme) {
|
|
262
|
+
idElement.setAttribute('schemeID', identifier.scheme);
|
|
263
|
+
}
|
|
264
|
+
idElement.textContent = identifier.value;
|
|
265
|
+
partyElement.insertBefore(idElement, partyElement.firstChild);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Adds the buyer reference (BT-10) as `ram:BuyerReference` of the header
|
|
271
|
+
* trade agreement, its first child in the CII schema; nothing when the
|
|
272
|
+
* document has none.
|
|
273
|
+
* @param doc XML document
|
|
274
|
+
* @param agreementElement The header trade agreement
|
|
275
|
+
* @param invoice Invoice data
|
|
276
|
+
*/
|
|
277
|
+
protected addBuyerReference(doc: Document, agreementElement: Element, invoice: TAccountingDoc): void {
|
|
278
|
+
const buyerReference = invoice.buyerReference?.trim();
|
|
279
|
+
if (!buyerReference) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const buyerReferenceElement = doc.createElement('ram:BuyerReference');
|
|
283
|
+
buyerReferenceElement.textContent = buyerReference;
|
|
284
|
+
agreementElement.appendChild(buyerReferenceElement);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Adds the invoicing period (BG-14) as `ram:BillingSpecifiedPeriod` of the
|
|
289
|
+
* header settlement, where the CII schema puts it: the start date (BT-73) and
|
|
290
|
+
* the end date (BT-74) of `periodOfPerformance`. Without it, the date or
|
|
291
|
+
* dates a decoded document kept in `metadata.extensions.dateInformation`
|
|
292
|
+
* (`periodStart`, `periodEnd`) are written, as the UBL encoders write them:
|
|
293
|
+
* a period may state only one of its dates (BR-CO-19).
|
|
294
|
+
* @param doc XML document
|
|
295
|
+
* @param settlementElement The header trade settlement
|
|
296
|
+
* @param invoice Invoice data
|
|
297
|
+
*/
|
|
298
|
+
protected addInvoicingPeriod(doc: Document, settlementElement: Element, invoice: TAccountingDoc): void {
|
|
299
|
+
const { start, end } = this.getInvoicingPeriodDays(invoice);
|
|
300
|
+
if (start === undefined && end === undefined) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const periodElement = doc.createElement('ram:BillingSpecifiedPeriod');
|
|
304
|
+
const appendDate = (name: string, day: string) => {
|
|
305
|
+
const dateElement = doc.createElement(name);
|
|
306
|
+
const dateStringElement = doc.createElement('udt:DateTimeString');
|
|
307
|
+
dateStringElement.setAttribute('format', '102');
|
|
308
|
+
dateStringElement.textContent = day;
|
|
309
|
+
dateElement.appendChild(dateStringElement);
|
|
310
|
+
periodElement.appendChild(dateElement);
|
|
311
|
+
};
|
|
312
|
+
if (start !== undefined) {
|
|
313
|
+
appendDate('ram:StartDateTime', start);
|
|
314
|
+
}
|
|
315
|
+
if (end !== undefined) {
|
|
316
|
+
appendDate('ram:EndDateTime', end);
|
|
317
|
+
}
|
|
318
|
+
settlementElement.appendChild(periodElement);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* The start (BT-73) and end (BT-74) dates of the invoicing period, in CII
|
|
323
|
+
* format 102: from `periodOfPerformance`, or else from the dates kept in
|
|
324
|
+
* `metadata.extensions.dateInformation`. A kept date is an `xsd:date`, as
|
|
325
|
+
* the UBL decoder keeps it and the CII decoders write it (YYYY-MM-DD); a time
|
|
326
|
+
* zone does not move the day it names (`parseXsdDateDay`), so the stated day
|
|
327
|
+
* is written. '' is no date, as the UBL decoder writes it. Any other value is
|
|
328
|
+
* refused with an `EInvoiceFormatError` naming the date.
|
|
329
|
+
* @param invoice Invoice data
|
|
330
|
+
*/
|
|
331
|
+
private getInvoicingPeriodDays(invoice: TAccountingDoc): { start?: string; end?: string } {
|
|
332
|
+
if (invoice.periodOfPerformance) {
|
|
333
|
+
return {
|
|
334
|
+
start: this.formatDateYYYYMMDD(invoice.periodOfPerformance.from, 'BT-73 invoicing period start date'),
|
|
335
|
+
end: this.formatDateYYYYMMDD(invoice.periodOfPerformance.to, 'BT-74 invoicing period end date'),
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
const dateInformation: unknown = (invoice as TAccountingDoc & { metadata?: IEInvoiceMetadata }).metadata?.extensions?.dateInformation;
|
|
339
|
+
if (typeof dateInformation !== 'object' || dateInformation === null) {
|
|
340
|
+
return {};
|
|
341
|
+
}
|
|
342
|
+
const keptDay = (key: 'periodStart' | 'periodEnd', field: string): string | undefined => {
|
|
343
|
+
const value: unknown = (dateInformation as Record<string, unknown>)[key];
|
|
344
|
+
if (value === undefined || value === '') {
|
|
345
|
+
return undefined;
|
|
346
|
+
}
|
|
347
|
+
const day = typeof value === 'string' ? parseXsdDateDay(value) : undefined;
|
|
348
|
+
if (day === undefined) {
|
|
349
|
+
throw new EInvoiceFormatError(`${field} is no valid date: ${String(value)}`, {
|
|
350
|
+
targetFormat: 'cii',
|
|
351
|
+
unsupportedFeatures: [field],
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
return this.formatDateYYYYMMDD(day, field);
|
|
355
|
+
};
|
|
356
|
+
const start = keptDay('periodStart', 'BT-73 invoicing period start date');
|
|
357
|
+
const end = keptDay('periodEnd', 'BT-74 invoicing period end date');
|
|
358
|
+
return { ...(start === undefined ? {} : { start }), ...(end === undefined ? {} : { end }) };
|
|
359
|
+
}
|
|
164
360
|
}
|
|
@@ -26,17 +26,15 @@ export enum CIIProfile {
|
|
|
26
26
|
MINIMUM = 'MINIMUM'
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
// CII profile IDs
|
|
29
|
+
// CII profile IDs (BT-24, the specification identifier)
|
|
30
30
|
export const CII_PROFILE_IDS = {
|
|
31
|
-
// Factur-X
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
FACTURX_EN16931: 'urn:cen.eu:en16931:2017',
|
|
31
|
+
// the EN 16931 profile of Factur-X 1.0 and ZUGFeRD 2.x (named COMFORT in ZUGFeRD): one standard, one
|
|
32
|
+
// identifier, and the only profile the CII encoders write
|
|
33
|
+
EN16931: 'urn:cen.eu:en16931:2017',
|
|
35
34
|
|
|
36
|
-
// ZUGFeRD
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
ZUGFERD_EXTENDED: 'urn:zugferd:extended',
|
|
35
|
+
// Factur-X and ZUGFeRD 2.1 subset profiles, recognised on decode
|
|
36
|
+
FACTURX_MINIMUM: 'urn:factur-x.eu:1p0:minimum',
|
|
37
|
+
FACTURX_BASIC_WL: 'urn:factur-x.eu:1p0:basicwl',
|
|
40
38
|
|
|
41
39
|
// ZUGFeRD v1 profiles
|
|
42
40
|
ZUGFERD_V1_BASIC: 'urn:ferd:CrossIndustryDocument:invoice:1p0:basic',
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { BaseValidator } from '../base/base.validator.js';
|
|
2
2
|
import { ValidationLevel } from '../../interfaces/common.js';
|
|
3
3
|
import type { ValidationResult } from '../../interfaces/common.js';
|
|
4
|
-
import { CII_NAMESPACES
|
|
4
|
+
import { CII_NAMESPACES } from './cii.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, ciiSellerMeetsBrCo26 } from '../utils/seller.identifier.js';
|
|
8
|
+
import { findStatedValueDisagreements, readCiiStatedValues } from '../utils/stated.values.js';
|
|
7
9
|
|
|
8
10
|
const ciiValidatorParser = new DOMParser();
|
|
9
11
|
const ciiValidatorNamespaces = {
|
|
@@ -20,7 +22,6 @@ export abstract class CIIBaseValidator extends BaseValidator {
|
|
|
20
22
|
protected doc!: Document;
|
|
21
23
|
protected namespaces!: Record<string, string>;
|
|
22
24
|
protected select!: xpath.XPathSelect;
|
|
23
|
-
protected profile: CIIProfile = CIIProfile.EN16931;
|
|
24
25
|
|
|
25
26
|
constructor(xml: string, doc?: Document) {
|
|
26
27
|
super(xml);
|
|
@@ -34,9 +35,6 @@ export abstract class CIIBaseValidator extends BaseValidator {
|
|
|
34
35
|
|
|
35
36
|
// Create XPath selector with namespaces
|
|
36
37
|
this.select = ciiValidatorSelect;
|
|
37
|
-
|
|
38
|
-
// Detect profile
|
|
39
|
-
this.detectProfile();
|
|
40
38
|
} catch (error) {
|
|
41
39
|
this.addError('CII-PARSE', `Failed to parse XML: ${error}`, '/');
|
|
42
40
|
}
|
|
@@ -114,6 +112,33 @@ export abstract class CIIBaseValidator extends BaseValidator {
|
|
|
114
112
|
*/
|
|
115
113
|
protected abstract validateStructure(): boolean;
|
|
116
114
|
|
|
115
|
+
/**
|
|
116
|
+
* BR-CO-10 to BR-CO-17: the document totals, the VAT breakdown, the line
|
|
117
|
+
* amounts and the document level allowances and charges the document states
|
|
118
|
+
* agree with each other, as the CEN artefacts check them
|
|
119
|
+
* @returns True if they do
|
|
120
|
+
*/
|
|
121
|
+
protected validateStatedTotals(): boolean {
|
|
122
|
+
const findings = findStatedValueDisagreements(readCiiStatedValues(this.doc), 'cii');
|
|
123
|
+
for (const finding of findings) {
|
|
124
|
+
this.addError(finding.ruleId, finding.message, finding.location);
|
|
125
|
+
}
|
|
126
|
+
return findings.length === 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* BR-CO-26: the seller states a seller identifier (BT-29), a legal
|
|
131
|
+
* registration identifier (BT-30) or a VAT identifier (BT-31).
|
|
132
|
+
* @returns True if it does
|
|
133
|
+
*/
|
|
134
|
+
protected validateSellerIdentifier(): boolean {
|
|
135
|
+
if (ciiSellerMeetsBrCo26(this.doc)) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
this.addError('BR-CO-26', BR_CO_26_TEXT, '//ram:SellerTradeParty');
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
|
|
117
142
|
/**
|
|
118
143
|
* BR-23: every invoice line has a unit code on its billed quantity.
|
|
119
144
|
* @returns True if every line has one
|
|
@@ -160,33 +185,6 @@ export abstract class CIIBaseValidator extends BaseValidator {
|
|
|
160
185
|
return valid;
|
|
161
186
|
}
|
|
162
187
|
|
|
163
|
-
/**
|
|
164
|
-
* Detects the CII profile from the XML
|
|
165
|
-
*/
|
|
166
|
-
protected detectProfile(): void {
|
|
167
|
-
// Look for profile identifier
|
|
168
|
-
const profileNode = this.select(
|
|
169
|
-
'string(//rsm:ExchangedDocumentContext/ram:GuidelineSpecifiedDocumentContextParameter/ram:ID)',
|
|
170
|
-
this.doc
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
if (profileNode) {
|
|
174
|
-
const profileText = profileNode.toString();
|
|
175
|
-
|
|
176
|
-
if (profileText.includes('BASIC')) {
|
|
177
|
-
this.profile = CIIProfile.BASIC;
|
|
178
|
-
} else if (profileText.includes('EN16931')) {
|
|
179
|
-
this.profile = CIIProfile.EN16931;
|
|
180
|
-
} else if (profileText.includes('EXTENDED')) {
|
|
181
|
-
this.profile = CIIProfile.EXTENDED;
|
|
182
|
-
} else if (profileText.includes('MINIMUM')) {
|
|
183
|
-
this.profile = CIIProfile.MINIMUM;
|
|
184
|
-
} else if (profileText.includes('COMFORT')) {
|
|
185
|
-
this.profile = CIIProfile.COMFORT;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
188
|
/**
|
|
191
189
|
* Gets a text value from an XPath expression
|
|
192
190
|
* @param xpath XPath expression
|
|
@@ -5,10 +5,10 @@ import type {
|
|
|
5
5
|
TCreditNote,
|
|
6
6
|
TInvoiceDocument,
|
|
7
7
|
} from '../../../interfaces/common.js';
|
|
8
|
-
import { FACTURX_PROFILE_IDS } from './facturx.types.js';
|
|
9
8
|
import { business, finance, general } from '../../../plugins.js';
|
|
10
9
|
import { EN16931Validator } from '../../validation/en16931.validator.js';
|
|
11
10
|
import { getPrecedingInvoiceFields } from '../../utils/preceding.invoice.js';
|
|
11
|
+
import { readPaidAmount } from '../../utils/paid.amount.js';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Decoder for Factur-X invoice format
|
|
@@ -97,6 +97,14 @@ export class FacturXDecoder extends CIIBaseDecoder {
|
|
|
97
97
|
// Reverse charge: every line has the VAT category AE
|
|
98
98
|
const reverseCharge = this.isReverseChargeDocument();
|
|
99
99
|
|
|
100
|
+
// the invoicing period (BG-14) and the seller electronic address (BT-34), when stated
|
|
101
|
+
const invoicingPeriod = this.invoicingPeriodFields(this.extractInvoicingPeriod());
|
|
102
|
+
// the buyer reference (BT-10), when stated
|
|
103
|
+
const buyerReference = this.extractBuyerReference();
|
|
104
|
+
const electronicAddress = this.extractSellerElectronicAddress();
|
|
105
|
+
|
|
106
|
+
const paidAmount = readPaidAmount(this.getText('//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:TotalPrepaidAmount'), 'facturx');
|
|
107
|
+
|
|
100
108
|
// Create the common invoice data
|
|
101
109
|
const invoiceData = {
|
|
102
110
|
type: 'accounting-doc' as const,
|
|
@@ -123,8 +131,15 @@ export class FacturXDecoder extends CIIBaseDecoder {
|
|
|
123
131
|
notes: notes,
|
|
124
132
|
// the day of supply as the document states it (BT-72)
|
|
125
133
|
...(deliveryDate === undefined ? {} : { deliveryDate }),
|
|
134
|
+
...invoicingPeriod,
|
|
135
|
+
// the specification identifier (BT-24), with the metadata of a one-date period
|
|
136
|
+
...this.metadataFields(invoicingPeriod),
|
|
137
|
+
...(buyerReference === undefined ? {} : { buyerReference }),
|
|
138
|
+
...(electronicAddress === undefined ? {} : { electronicAddress }),
|
|
126
139
|
objectActions: [],
|
|
127
140
|
...getPrecedingInvoiceFields(accountingDocType, this.extractPrecedingInvoiceReferences()),
|
|
141
|
+
// the paid amount (BT-113); the amount due (BT-115) follows from it and the totals
|
|
142
|
+
...(paidAmount === undefined ? {} : { paidAmount }),
|
|
128
143
|
};
|
|
129
144
|
|
|
130
145
|
// Validate mandatory EN16931 fields unless validation is skipped
|
|
@@ -163,6 +178,9 @@ export class FacturXDecoder extends CIIBaseDecoder {
|
|
|
163
178
|
// the tax registration identifier (BT-32 for the seller), the Steuernummer: scheme FC
|
|
164
179
|
const taxNumber = this.getText(`${partyXPath}/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="FC"]`) || '';
|
|
165
180
|
|
|
181
|
+
// the party identifiers (BT-29 seller, BT-46 buyer)
|
|
182
|
+
const additionalIdentifiers = this.extractPartyIdentifiers(partyXPath);
|
|
183
|
+
|
|
166
184
|
// Create contact object
|
|
167
185
|
return {
|
|
168
186
|
type: 'company',
|
|
@@ -176,7 +194,8 @@ export class FacturXDecoder extends CIIBaseDecoder {
|
|
|
176
194
|
registrationId: registrationId,
|
|
177
195
|
registrationName: '',
|
|
178
196
|
...(taxNumber ? { taxNumber } : {})
|
|
179
|
-
}
|
|
197
|
+
},
|
|
198
|
+
...(additionalIdentifiers.length > 0 ? { additionalIdentifiers } : {})
|
|
180
199
|
} as business.TContact;
|
|
181
200
|
}
|
|
182
201
|
|