@fin.cx/einvoice 8.1.0 → 8.2.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 +1 -1
- package/dist_ts/einvoice.d.ts +14 -1
- package/dist_ts/einvoice.js +17 -2
- package/dist_ts/formats/base/base.decoder.js +4 -3
- package/dist_ts/formats/base/base.encoder.d.ts +1 -1
- package/dist_ts/formats/cii/cii.decoder.d.ts +27 -1
- package/dist_ts/formats/cii/cii.decoder.js +61 -1
- package/dist_ts/formats/cii/cii.encoder.d.ts +14 -3
- package/dist_ts/formats/cii/cii.encoder.js +58 -6
- package/dist_ts/formats/cii/cii.types.d.ts +1 -0
- package/dist_ts/formats/cii/cii.types.js +3 -2
- package/dist_ts/formats/cii/facturx/facturx.decoder.d.ts +1 -1
- package/dist_ts/formats/cii/facturx/facturx.decoder.js +11 -8
- package/dist_ts/formats/cii/facturx/facturx.encoder.d.ts +2 -2
- package/dist_ts/formats/cii/facturx/facturx.encoder.js +18 -15
- package/dist_ts/formats/cii/zugferd/zugferd.decoder.d.ts +1 -1
- package/dist_ts/formats/cii/zugferd/zugferd.decoder.js +12 -10
- package/dist_ts/formats/cii/zugferd/zugferd.encoder.d.ts +1 -1
- package/dist_ts/formats/cii/zugferd/zugferd.encoder.js +26 -19
- package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.d.ts +19 -1
- package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.js +51 -10
- package/dist_ts/formats/ubl/generic/ubl.encoder.d.ts +23 -1
- package/dist_ts/formats/ubl/generic/ubl.encoder.js +69 -14
- package/dist_ts/formats/ubl/ubl.decoder.d.ts +1 -1
- package/dist_ts/formats/ubl/ubl.encoder.d.ts +16 -3
- package/dist_ts/formats/ubl/ubl.encoder.js +75 -13
- package/dist_ts/formats/ubl/ubl.types.d.ts +6 -0
- package/dist_ts/formats/ubl/ubl.types.js +9 -3
- package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.d.ts +6 -1
- package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.js +46 -14
- package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.d.ts +1 -1
- package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.js +7 -12
- package/dist_ts/formats/utils/date.value.d.ts +28 -0
- package/dist_ts/formats/utils/date.value.js +50 -0
- package/dist_ts/formats/utils/document.typecode.d.ts +3 -3
- package/dist_ts/formats/utils/document.typecode.js +8 -4
- package/dist_ts/formats/utils/format.detector.js +6 -4
- package/dist_ts/formats/utils/payment.terms.d.ts +18 -0
- package/dist_ts/formats/utils/payment.terms.js +32 -0
- package/dist_ts/formats/utils/preceding.invoice.d.ts +26 -0
- package/dist_ts/formats/utils/preceding.invoice.js +52 -0
- package/dist_ts/formats/validation/integrated.validator.js +6 -3
- package/dist_ts/formats/validation/xrechnung.validator.js +6 -4
- package/dist_ts/index.d.ts +1 -1
- package/dist_ts/index.js +1 -1
- package/dist_ts/interfaces/common.d.ts +6 -2
- package/package.json +3 -3
- package/readme.md +77 -10
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/einvoice.ts +18 -1
- package/ts/formats/base/base.decoder.ts +3 -2
- package/ts/formats/base/base.encoder.ts +1 -1
- package/ts/formats/cii/cii.decoder.ts +74 -1
- package/ts/formats/cii/cii.encoder.ts +62 -6
- package/ts/formats/cii/cii.types.ts +2 -1
- package/ts/formats/cii/facturx/facturx.decoder.ts +11 -7
- package/ts/formats/cii/facturx/facturx.encoder.ts +18 -14
- package/ts/formats/cii/zugferd/zugferd.decoder.ts +12 -9
- package/ts/formats/cii/zugferd/zugferd.encoder.ts +26 -18
- package/ts/formats/cii/zugferd/zugferd.v1.decoder.ts +58 -9
- package/ts/formats/ubl/generic/ubl.encoder.ts +76 -13
- package/ts/formats/ubl/ubl.decoder.ts +1 -1
- package/ts/formats/ubl/ubl.encoder.ts +77 -13
- package/ts/formats/ubl/ubl.types.ts +8 -2
- package/ts/formats/ubl/xrechnung/xrechnung.decoder.ts +56 -13
- package/ts/formats/ubl/xrechnung/xrechnung.encoder.ts +7 -12
- package/ts/formats/utils/date.value.ts +52 -0
- package/ts/formats/utils/document.typecode.ts +7 -3
- package/ts/formats/utils/format.detector.ts +5 -5
- package/ts/formats/utils/payment.terms.ts +37 -0
- package/ts/formats/utils/preceding.invoice.ts +74 -0
- package/ts/formats/validation/integrated.validator.ts +5 -2
- package/ts/formats/validation/xrechnung.validator.ts +5 -3
- package/ts/index.ts +3 -0
- package/ts/interfaces/common.ts +6 -1
|
@@ -4,12 +4,14 @@ import { CII_NAMESPACES, CIIProfile } 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
|
+
import type { TPrecedingInvoiceReference } from '../utils/preceding.invoice.js';
|
|
7
8
|
|
|
8
9
|
const ciiParser = new DOMParser();
|
|
9
10
|
const ciiNamespaces = {
|
|
10
11
|
rsm: CII_NAMESPACES.RSM,
|
|
11
12
|
ram: CII_NAMESPACES.RAM,
|
|
12
13
|
udt: CII_NAMESPACES.UDT,
|
|
14
|
+
qdt: CII_NAMESPACES.QDT,
|
|
13
15
|
};
|
|
14
16
|
const ciiSelect = xpath.useNamespaces(ciiNamespaces);
|
|
15
17
|
|
|
@@ -91,7 +93,7 @@ export abstract class CIIBaseDecoder extends BaseDecoder {
|
|
|
91
93
|
protected abstract decodeCreditNote(): Promise<TCreditNote>;
|
|
92
94
|
|
|
93
95
|
/**
|
|
94
|
-
* Decodes a CII invoice document: an invoice, a debit note or a self-billed invoice
|
|
96
|
+
* Decodes a CII invoice document: an invoice, a corrected invoice, a debit note or a self-billed invoice
|
|
95
97
|
* @param accountingDocType The type the document type code stands for
|
|
96
98
|
* @returns Promise resolving to the invoice document
|
|
97
99
|
*/
|
|
@@ -99,6 +101,77 @@ export abstract class CIIBaseDecoder extends BaseDecoder {
|
|
|
99
101
|
accountingDocType: TInvoiceDocument['accountingDocType'],
|
|
100
102
|
): Promise<TInvoiceDocument>;
|
|
101
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Reads the preceding invoice references (BG-3) of the header settlement:
|
|
106
|
+
* the number (BT-25) and, when stated, the issue date (BT-26) of each.
|
|
107
|
+
*/
|
|
108
|
+
protected extractPrecedingInvoiceReferences(): TPrecedingInvoiceReference[] {
|
|
109
|
+
const referenceNodes = this.select(
|
|
110
|
+
'/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument',
|
|
111
|
+
this.doc,
|
|
112
|
+
);
|
|
113
|
+
const references: TPrecedingInvoiceReference[] = [];
|
|
114
|
+
for (const referenceNode of Array.isArray(referenceNodes) ? referenceNodes : []) {
|
|
115
|
+
const documentId = this.getText('./ram:IssuerAssignedID', referenceNode as Node).trim();
|
|
116
|
+
if (!documentId) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const issueDate = this.getText('./ram:FormattedIssueDateTime/qdt:DateTimeString', referenceNode as Node).trim();
|
|
120
|
+
const issueDateFormat = this.getText('./ram:FormattedIssueDateTime/qdt:DateTimeString/@format', referenceNode as Node).trim();
|
|
121
|
+
references.push(
|
|
122
|
+
issueDate
|
|
123
|
+
? { documentId, issueDate: this.parseRequiredCIIDate(issueDate, issueDateFormat) }
|
|
124
|
+
: { documentId },
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
return references;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Reads the invoice issue date (BT-2); a missing or invalid one is refused
|
|
132
|
+
* with an `EInvoiceParsingError`, as no other date can stand in for it.
|
|
133
|
+
*/
|
|
134
|
+
protected extractIssueDate(): number {
|
|
135
|
+
const issueDatePath = '/rsm:CrossIndustryInvoice/rsm:ExchangedDocument/ram:IssueDateTime/udt:DateTimeString';
|
|
136
|
+
return this.parseRequiredCIIDate(this.getText(issueDatePath), this.getText(`${issueDatePath}/@format`).trim());
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Reads the payment due date (BT-9) of the header payment terms; undefined
|
|
141
|
+
* when the document states none.
|
|
142
|
+
*/
|
|
143
|
+
protected extractDueDate(): number | undefined {
|
|
144
|
+
const dueDatePath =
|
|
145
|
+
'/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradePaymentTerms/ram:DueDateDateTime/udt:DateTimeString';
|
|
146
|
+
const dueDate = this.getText(dueDatePath).trim();
|
|
147
|
+
if (!dueDate) {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
return this.parseRequiredCIIDate(dueDate, this.getText(`${dueDatePath}/@format`).trim());
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* The payment term in days from the issue date to the stated due date, or
|
|
155
|
+
* `absentDueInDays` when the document states no due date.
|
|
156
|
+
*/
|
|
157
|
+
protected dueInDaysOf(issueDate: number, dueDate: number | undefined, absentDueInDays: number): number {
|
|
158
|
+
return dueDate === undefined ? absentDueInDays : Math.round((dueDate - issueDate) / (1000 * 60 * 60 * 24));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Reads the actual delivery date (BT-72) of the header delivery; undefined
|
|
163
|
+
* when the document does not state one.
|
|
164
|
+
*/
|
|
165
|
+
protected extractDeliveryDate(): number | undefined {
|
|
166
|
+
const deliveryDatePath =
|
|
167
|
+
'/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeDelivery/ram:ActualDeliverySupplyChainEvent/ram:OccurrenceDateTime/udt:DateTimeString';
|
|
168
|
+
const deliveryDate = this.getText(deliveryDatePath).trim();
|
|
169
|
+
if (!deliveryDate) {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
return this.parseRequiredCIIDate(deliveryDate, this.getText(`${deliveryDatePath}/@format`).trim());
|
|
173
|
+
}
|
|
174
|
+
|
|
102
175
|
/**
|
|
103
176
|
* Gets a text value from an XPath expression
|
|
104
177
|
* @param xpath XPath expression
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { BaseEncoder } from '../base/base.encoder.js';
|
|
2
2
|
import type { TAccountingDoc, TCreditNote, TInvoiceDocument } from '../../interfaces/common.js';
|
|
3
|
+
import { EInvoiceFormatError } from '../../errors.js';
|
|
4
|
+
import { getPrecedingInvoiceReferences } from '../utils/preceding.invoice.js';
|
|
5
|
+
import { getWritableDate } from '../utils/date.value.js';
|
|
3
6
|
import { CII_NAMESPACES, CIIProfile } from './cii.types.js';
|
|
4
7
|
|
|
5
8
|
/**
|
|
@@ -22,6 +25,13 @@ export abstract class CIIBaseEncoder extends BaseEncoder {
|
|
|
22
25
|
* @returns CII XML string
|
|
23
26
|
*/
|
|
24
27
|
public async encode(invoice: TAccountingDoc): Promise<string> {
|
|
28
|
+
// MINIMUM has no preceding invoice reference (BG-3): a reference the document states cannot be written there
|
|
29
|
+
if (this.profile === CIIProfile.MINIMUM && getPrecedingInvoiceReferences(invoice).length > 0) {
|
|
30
|
+
throw new EInvoiceFormatError(
|
|
31
|
+
'The MINIMUM profile has no preceding invoice reference (BG-3); a document that refers to a preceding invoice needs a profile that carries it',
|
|
32
|
+
{ targetFormat: 'cii', unsupportedFeatures: ['BG-3 in profile MINIMUM'] },
|
|
33
|
+
);
|
|
34
|
+
}
|
|
25
35
|
// CII has one root for every document; the two paths differ in the type code they write
|
|
26
36
|
if (invoice.accountingDocType === 'creditnote') {
|
|
27
37
|
return this.encodeCreditNote(invoice);
|
|
@@ -37,7 +47,7 @@ export abstract class CIIBaseEncoder extends BaseEncoder {
|
|
|
37
47
|
protected abstract encodeCreditNote(creditNote: TCreditNote): Promise<string>;
|
|
38
48
|
|
|
39
49
|
/**
|
|
40
|
-
* Encodes an invoice document (invoice, debit note or self-billed invoice) into CII XML
|
|
50
|
+
* Encodes an invoice document (invoice, corrected invoice, debit note or self-billed invoice) into CII XML
|
|
41
51
|
* @param invoice Invoice document to encode
|
|
42
52
|
* @returns CII XML string
|
|
43
53
|
*/
|
|
@@ -51,17 +61,63 @@ export abstract class CIIBaseEncoder extends BaseEncoder {
|
|
|
51
61
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
52
62
|
<rsm:CrossIndustryInvoice xmlns:rsm="${CII_NAMESPACES.RSM}"
|
|
53
63
|
xmlns:ram="${CII_NAMESPACES.RAM}"
|
|
54
|
-
xmlns:udt="${CII_NAMESPACES.UDT}"
|
|
64
|
+
xmlns:udt="${CII_NAMESPACES.UDT}"
|
|
65
|
+
xmlns:qdt="${CII_NAMESPACES.QDT}">
|
|
55
66
|
</rsm:CrossIndustryInvoice>`;
|
|
56
67
|
}
|
|
57
68
|
|
|
58
69
|
/**
|
|
59
|
-
*
|
|
70
|
+
* Writes one preceding invoice reference (BG-3) per invoice the document
|
|
71
|
+
* states it corrects: the number (BT-25) and, when stated, the issue date
|
|
72
|
+
* (BT-26). In the header settlement they follow the monetary summation, as
|
|
73
|
+
* the CII schema orders them.
|
|
74
|
+
* @param doc XML document
|
|
75
|
+
* @param invoice Accounting document
|
|
76
|
+
*/
|
|
77
|
+
protected addPrecedingInvoiceReferences(doc: Document, invoice: TAccountingDoc): void {
|
|
78
|
+
const references = getPrecedingInvoiceReferences(invoice);
|
|
79
|
+
if (references.length === 0) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const settlement = doc.getElementsByTagName('ram:ApplicableHeaderTradeSettlement')[0];
|
|
83
|
+
if (!settlement) {
|
|
84
|
+
throw new EInvoiceFormatError('The document has no header trade settlement to carry its preceding invoice reference', {
|
|
85
|
+
targetFormat: 'cii',
|
|
86
|
+
unsupportedFeatures: ['BG-3'],
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
let summation: Element | undefined;
|
|
90
|
+
for (let node = settlement.firstChild; node; node = node.nextSibling) {
|
|
91
|
+
if (node.nodeType === 1 && node.nodeName === 'ram:SpecifiedTradeSettlementHeaderMonetarySummation') {
|
|
92
|
+
summation = node as Element;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const anchor = summation ? summation.nextSibling : null;
|
|
96
|
+
for (const reference of references) {
|
|
97
|
+
const referencedDocument = doc.createElement('ram:InvoiceReferencedDocument');
|
|
98
|
+
const issuerAssignedId = doc.createElement('ram:IssuerAssignedID');
|
|
99
|
+
issuerAssignedId.textContent = reference.documentId;
|
|
100
|
+
referencedDocument.appendChild(issuerAssignedId);
|
|
101
|
+
if (reference.issueDate !== undefined) {
|
|
102
|
+
const formattedIssueDateTime = doc.createElement('ram:FormattedIssueDateTime');
|
|
103
|
+
const dateTimeString = doc.createElement('qdt:DateTimeString');
|
|
104
|
+
dateTimeString.setAttribute('format', '102');
|
|
105
|
+
dateTimeString.textContent = this.formatDate(reference.issueDate, 'BT-26 preceding invoice issue date').replace(/-/g, '');
|
|
106
|
+
formattedIssueDateTime.appendChild(dateTimeString);
|
|
107
|
+
referencedDocument.appendChild(formattedIssueDateTime);
|
|
108
|
+
}
|
|
109
|
+
settlement.insertBefore(referencedDocument, anchor);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Formats a date as an ISO string (YYYY-MM-DD, UTC). A value that is no
|
|
115
|
+
* valid timestamp is refused with an `EInvoiceFormatError` naming the field.
|
|
60
116
|
* @param timestamp Timestamp to format
|
|
117
|
+
* @param field The business term the date is written as
|
|
61
118
|
* @returns Formatted date string
|
|
62
119
|
*/
|
|
63
|
-
protected formatDate(timestamp: number): string {
|
|
64
|
-
|
|
65
|
-
return date.toISOString().split('T')[0];
|
|
120
|
+
protected formatDate(timestamp: number, field: string): string {
|
|
121
|
+
return getWritableDate(timestamp, field, 'cii').toISOString().split('T')[0];
|
|
66
122
|
}
|
|
67
123
|
}
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
export const CII_NAMESPACES = {
|
|
7
7
|
RSM: 'urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100',
|
|
8
8
|
RAM: 'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100',
|
|
9
|
-
UDT: 'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100'
|
|
9
|
+
UDT: 'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100',
|
|
10
|
+
QDT: 'urn:un:unece:uncefact:data:standard:QualifiedDataType:100'
|
|
10
11
|
};
|
|
11
12
|
|
|
12
13
|
// ZUGFeRD v1 namespaces
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
import { FACTURX_PROFILE_IDS } from './facturx.types.js';
|
|
9
9
|
import { business, finance, general } from '../../../plugins.js';
|
|
10
10
|
import { EN16931Validator } from '../../validation/en16931.validator.js';
|
|
11
|
+
import { getPrecedingInvoiceFields } from '../../utils/preceding.invoice.js';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Decoder for Factur-X invoice format
|
|
@@ -22,7 +23,7 @@ export class FacturXDecoder extends CIIBaseDecoder {
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
|
-
* Decodes a Factur-X invoice document: an invoice, a debit note or a self-billed invoice
|
|
26
|
+
* Decodes a Factur-X invoice document: an invoice, a corrected invoice, a debit note or a self-billed invoice
|
|
26
27
|
* @param accountingDocType The type the document type code stands for
|
|
27
28
|
* @returns Promise resolving to the invoice document
|
|
28
29
|
*/
|
|
@@ -66,10 +67,8 @@ export class FacturXDecoder extends CIIBaseDecoder {
|
|
|
66
67
|
const items = this.extractItems();
|
|
67
68
|
|
|
68
69
|
// Extract due date
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
const dueDate = dueDateStr ? this.parseCIIDate(dueDateStr, dueDateFormat) : issueDate;
|
|
72
|
-
const dueInDays = Math.round((dueDate - issueDate) / (1000 * 60 * 60 * 24));
|
|
70
|
+
// no due date stated: `dueInDays` cannot express it yet (required field in @tsclass/tsclass); value unchanged: 0 days
|
|
71
|
+
const dueInDays = this.dueInDaysOf(issueDate, this.extractDueDate(), 0);
|
|
73
72
|
|
|
74
73
|
// Extract currency
|
|
75
74
|
const currencyCode = this.getText('//ram:InvoiceCurrencyCode') || 'EUR';
|
|
@@ -92,6 +91,9 @@ export class FacturXDecoder extends CIIBaseDecoder {
|
|
|
92
91
|
notes = allNotes.slice(1); // Remove subject from notes
|
|
93
92
|
}
|
|
94
93
|
|
|
94
|
+
// Extract the actual delivery date, if stated
|
|
95
|
+
const deliveryDate = this.extractDeliveryDate();
|
|
96
|
+
|
|
95
97
|
// Check for reverse charge
|
|
96
98
|
const reverseCharge = this.exists('//ram:SpecifiedTradeAllowanceCharge/ram:ReasonCode[text()="62"]');
|
|
97
99
|
|
|
@@ -119,8 +121,10 @@ export class FacturXDecoder extends CIIBaseDecoder {
|
|
|
119
121
|
reverseCharge: reverseCharge,
|
|
120
122
|
currency: currencyCode as finance.TCurrency,
|
|
121
123
|
notes: notes,
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
// the day of supply as the document states it (BT-72)
|
|
125
|
+
...(deliveryDate === undefined ? {} : { deliveryDate }),
|
|
126
|
+
objectActions: [],
|
|
127
|
+
...getPrecedingInvoiceFields(accountingDocType, this.extractPrecedingInvoiceReferences()),
|
|
124
128
|
};
|
|
125
129
|
|
|
126
130
|
// Validate mandatory EN16931 fields unless validation is skipped
|
|
@@ -6,6 +6,7 @@ import { Decimal } from '../../utils/decimal.js';
|
|
|
6
6
|
import { DecimalCurrencyCalculator } from '../../utils/currency.calculator.decimal.js';
|
|
7
7
|
import { getCurrencyMinorUnits } from '../../utils/currency.utils.js';
|
|
8
8
|
import { getDocumentTypeCode } from '../../utils/document.typecode.js';
|
|
9
|
+
import { getWritableDate, getWritableDueDate } from '../../utils/date.value.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Encoder for Factur-X invoice format
|
|
@@ -31,7 +32,7 @@ export class FacturXEncoder extends CIIBaseEncoder {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
|
-
* Encodes an invoice document (invoice, debit note or self-billed invoice) into Factur-X XML
|
|
35
|
+
* Encodes an invoice document (invoice, corrected invoice, debit note or self-billed invoice) into Factur-X XML
|
|
35
36
|
* @param invoice Invoice document to encode
|
|
36
37
|
* @returns Factur-X XML string
|
|
37
38
|
*/
|
|
@@ -39,7 +40,7 @@ export class FacturXEncoder extends CIIBaseEncoder {
|
|
|
39
40
|
// Create base XML
|
|
40
41
|
const xmlDoc = this.createBaseXml();
|
|
41
42
|
|
|
42
|
-
// Set document type code (380 invoice, 383 debit note, 389 self-billed invoice)
|
|
43
|
+
// Set document type code (380 invoice, 383 debit note, 384 corrected invoice, 389 self-billed invoice)
|
|
43
44
|
this.setDocumentTypeCode(xmlDoc, getDocumentTypeCode(invoice.accountingDocType));
|
|
44
45
|
|
|
45
46
|
// Add common invoice data
|
|
@@ -101,7 +102,7 @@ export class FacturXEncoder extends CIIBaseEncoder {
|
|
|
101
102
|
/**
|
|
102
103
|
* Sets the document type code in the XML document
|
|
103
104
|
* @param doc XML document
|
|
104
|
-
* @param typeCode Document type code (380 invoice, 381 credit note, 383 debit note, 389 self-billed invoice)
|
|
105
|
+
* @param typeCode Document type code (380 invoice, 381 credit note, 383 debit note, 384 corrected invoice, 389 self-billed invoice)
|
|
105
106
|
*/
|
|
106
107
|
private setDocumentTypeCode(doc: Document, typeCode: string): void {
|
|
107
108
|
// Get root element
|
|
@@ -145,7 +146,7 @@ export class FacturXEncoder extends CIIBaseEncoder {
|
|
|
145
146
|
const issueDateElement = doc.createElement('ram:IssueDateTime');
|
|
146
147
|
const dateStringElement = doc.createElement('udt:DateTimeString');
|
|
147
148
|
dateStringElement.setAttribute('format', '102'); // YYYYMMDD format
|
|
148
|
-
dateStringElement.textContent = this.formatDateYYYYMMDD(invoice.date);
|
|
149
|
+
dateStringElement.textContent = this.formatDateYYYYMMDD(invoice.date, 'BT-2 invoice issue date');
|
|
149
150
|
issueDateElement.appendChild(dateStringElement);
|
|
150
151
|
documentElement.appendChild(issueDateElement);
|
|
151
152
|
|
|
@@ -183,6 +184,9 @@ export class FacturXEncoder extends CIIBaseEncoder {
|
|
|
183
184
|
// Add settlement section with payment terms and totals
|
|
184
185
|
this.addSettlementSection(doc, transactionElement, invoice);
|
|
185
186
|
|
|
187
|
+
// Add the preceding invoice references (BG-3) the document states
|
|
188
|
+
this.addPrecedingInvoiceReferences(doc, invoice);
|
|
189
|
+
|
|
186
190
|
// Add line items
|
|
187
191
|
this.addLineItems(doc, transactionElement, invoice);
|
|
188
192
|
}
|
|
@@ -284,12 +288,12 @@ export class FacturXEncoder extends CIIBaseEncoder {
|
|
|
284
288
|
transactionElement.appendChild(deliveryElement);
|
|
285
289
|
|
|
286
290
|
// Add delivery date if available
|
|
287
|
-
if (invoice.deliveryDate) {
|
|
291
|
+
if (invoice.deliveryDate !== undefined) {
|
|
288
292
|
const deliveryDateElement = doc.createElement('ram:ActualDeliverySupplyChainEvent');
|
|
289
293
|
const occurrenceDateElement = doc.createElement('ram:OccurrenceDateTime');
|
|
290
294
|
const dateStringElement = doc.createElement('udt:DateTimeString');
|
|
291
295
|
dateStringElement.setAttribute('format', '102'); // YYYYMMDD format
|
|
292
|
-
dateStringElement.textContent = this.formatDateYYYYMMDD(invoice.deliveryDate);
|
|
296
|
+
dateStringElement.textContent = this.formatDateYYYYMMDD(invoice.deliveryDate, 'BT-72 actual delivery date');
|
|
293
297
|
occurrenceDateElement.appendChild(dateStringElement);
|
|
294
298
|
deliveryDateElement.appendChild(occurrenceDateElement);
|
|
295
299
|
deliveryElement.appendChild(deliveryDateElement);
|
|
@@ -321,10 +325,9 @@ export class FacturXEncoder extends CIIBaseEncoder {
|
|
|
321
325
|
dateStringElement.setAttribute('format', '102'); // YYYYMMDD format
|
|
322
326
|
|
|
323
327
|
// Calculate due date
|
|
324
|
-
const dueDate =
|
|
325
|
-
dueDate.setDate(dueDate.getDate() + invoice.dueInDays);
|
|
328
|
+
const dueDate = getWritableDueDate(invoice.date, invoice.dueInDays, 'cii');
|
|
326
329
|
|
|
327
|
-
dateStringElement.textContent = this.formatDateYYYYMMDD(dueDate.getTime());
|
|
330
|
+
dateStringElement.textContent = this.formatDateYYYYMMDD(dueDate.getTime(), 'BT-9 payment due date');
|
|
328
331
|
dueDateElement.appendChild(dateStringElement);
|
|
329
332
|
paymentTermsElement.appendChild(dueDateElement);
|
|
330
333
|
|
|
@@ -487,11 +490,12 @@ export class FacturXEncoder extends CIIBaseEncoder {
|
|
|
487
490
|
* @param timestamp Timestamp to format
|
|
488
491
|
* @returns Formatted date string
|
|
489
492
|
*/
|
|
490
|
-
private formatDateYYYYMMDD(timestamp: number): string {
|
|
491
|
-
const date =
|
|
492
|
-
|
|
493
|
-
const
|
|
494
|
-
const
|
|
493
|
+
private formatDateYYYYMMDD(timestamp: number, field: string): string {
|
|
494
|
+
const date = getWritableDate(timestamp, field, 'cii');
|
|
495
|
+
// the UTC fields: a calendar day is its UTC midnight, whatever the server's zone
|
|
496
|
+
const year = date.getUTCFullYear();
|
|
497
|
+
const month = (date.getUTCMonth() + 1).toString().padStart(2, '0');
|
|
498
|
+
const day = date.getUTCDate().toString().padStart(2, '0');
|
|
495
499
|
return `${year}${month}${day}`;
|
|
496
500
|
}
|
|
497
501
|
}
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
} from '../../../interfaces/common.js';
|
|
8
8
|
import { business, finance } from '../../../plugins.js';
|
|
9
9
|
import { EN16931Validator } from '../../validation/en16931.validator.js';
|
|
10
|
+
import { getPrecedingInvoiceFields } from '../../utils/preceding.invoice.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Decoder for ZUGFeRD invoice format
|
|
@@ -21,7 +22,7 @@ export class ZUGFeRDDecoder extends CIIBaseDecoder {
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
|
-
* Decodes a ZUGFeRD invoice document: an invoice, a debit note or a self-billed invoice
|
|
25
|
+
* Decodes a ZUGFeRD invoice document: an invoice, a corrected invoice, a debit note or a self-billed invoice
|
|
25
26
|
* @param accountingDocType The type the document type code stands for
|
|
26
27
|
* @returns Promise resolving to the invoice document
|
|
27
28
|
*/
|
|
@@ -43,9 +44,7 @@ export class ZUGFeRDDecoder extends CIIBaseDecoder {
|
|
|
43
44
|
const invoiceId = this.getText('//rsm:ExchangedDocument/ram:ID');
|
|
44
45
|
|
|
45
46
|
// Extract issue date
|
|
46
|
-
const
|
|
47
|
-
const issueDateFormat = this.getText('//ram:IssueDateTime/udt:DateTimeString/@format');
|
|
48
|
-
const issueDate = this.parseCIIDate(issueDateStr, issueDateFormat);
|
|
47
|
+
const issueDate = this.extractIssueDate();
|
|
49
48
|
|
|
50
49
|
// Extract seller information
|
|
51
50
|
const seller = this.extractParty('//ram:SellerTradeParty');
|
|
@@ -57,9 +56,8 @@ export class ZUGFeRDDecoder extends CIIBaseDecoder {
|
|
|
57
56
|
const items = this.extractItems();
|
|
58
57
|
|
|
59
58
|
// Extract due date
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
const dueInDays = Math.round((dueDate - issueDate) / (1000 * 60 * 60 * 24));
|
|
59
|
+
// no due date stated: `dueInDays` cannot express it yet (required field in @tsclass/tsclass); value unchanged: the days from the issue date to the moment of decoding
|
|
60
|
+
const dueInDays = this.dueInDaysOf(issueDate, this.extractDueDate(), Math.round((Date.now() - issueDate) / (1000 * 60 * 60 * 24)));
|
|
63
61
|
|
|
64
62
|
// Extract currency
|
|
65
63
|
const currencyCode = this.getText('//ram:InvoiceCurrencyCode') || 'EUR';
|
|
@@ -83,6 +81,9 @@ export class ZUGFeRDDecoder extends CIIBaseDecoder {
|
|
|
83
81
|
notes = allNotes.slice(1); // Remove subject from notes
|
|
84
82
|
}
|
|
85
83
|
|
|
84
|
+
// Extract the actual delivery date, if stated
|
|
85
|
+
const deliveryDate = this.extractDeliveryDate();
|
|
86
|
+
|
|
86
87
|
// Check for reverse charge
|
|
87
88
|
const reverseCharge = this.exists('//ram:SpecifiedTradeAllowanceCharge/ram:ReasonCode[text()="62"]');
|
|
88
89
|
|
|
@@ -110,8 +111,10 @@ export class ZUGFeRDDecoder extends CIIBaseDecoder {
|
|
|
110
111
|
reverseCharge: reverseCharge,
|
|
111
112
|
currency: currencyCode as finance.TCurrency,
|
|
112
113
|
notes: notes,
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
// the day of supply as the document states it (BT-72)
|
|
115
|
+
...(deliveryDate === undefined ? {} : { deliveryDate }),
|
|
116
|
+
objectActions: [],
|
|
117
|
+
...getPrecedingInvoiceFields(accountingDocType, this.extractPrecedingInvoiceReferences()),
|
|
115
118
|
};
|
|
116
119
|
|
|
117
120
|
// Validate mandatory EN16931 fields unless validation is skipped
|
|
@@ -4,6 +4,8 @@ import { ZUGFERD_PROFILE_IDS } from './zugferd.types.js';
|
|
|
4
4
|
import { CIIProfile } from '../cii.types.js';
|
|
5
5
|
import { DOMParser, XMLSerializer } from '../../../plugins.js';
|
|
6
6
|
import { getDocumentTypeCode } from '../../utils/document.typecode.js';
|
|
7
|
+
import { getWritableDate, getWritableDueDate } from '../../utils/date.value.js';
|
|
8
|
+
import { getPaymentTermsNote } from '../../utils/payment.terms.js';
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* Encoder for ZUGFeRD invoice format
|
|
@@ -35,7 +37,7 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
/**
|
|
38
|
-
* Encodes an invoice document (invoice, debit note or self-billed invoice) into ZUGFeRD XML
|
|
40
|
+
* Encodes an invoice document (invoice, corrected invoice, debit note or self-billed invoice) into ZUGFeRD XML
|
|
39
41
|
* @param invoice Invoice document to encode
|
|
40
42
|
* @returns ZUGFeRD XML string
|
|
41
43
|
*/
|
|
@@ -43,7 +45,7 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
43
45
|
// Create base XML
|
|
44
46
|
const xmlDoc = this.createBaseXml();
|
|
45
47
|
|
|
46
|
-
// Set document type code (380 invoice, 383 debit note, 389 self-billed invoice)
|
|
48
|
+
// Set document type code (380 invoice, 383 debit note, 384 corrected invoice, 389 self-billed invoice)
|
|
47
49
|
this.setDocumentTypeCode(xmlDoc, getDocumentTypeCode(invoice.accountingDocType));
|
|
48
50
|
|
|
49
51
|
// Add common invoice data
|
|
@@ -149,7 +151,7 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
149
151
|
const issueDateElement = doc.createElement('ram:IssueDateTime');
|
|
150
152
|
const dateStringElement = doc.createElement('udt:DateTimeString');
|
|
151
153
|
dateStringElement.setAttribute('format', '102'); // YYYYMMDD format
|
|
152
|
-
dateStringElement.textContent = this.formatDateYYYYMMDD(invoice.date);
|
|
154
|
+
dateStringElement.textContent = this.formatDateYYYYMMDD(invoice.date, 'BT-2 invoice issue date');
|
|
153
155
|
issueDateElement.appendChild(dateStringElement);
|
|
154
156
|
documentElement.appendChild(issueDateElement);
|
|
155
157
|
|
|
@@ -187,6 +189,9 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
187
189
|
// Add settlement section with payment terms and totals
|
|
188
190
|
this.addSettlementSection(doc, transactionElement, invoice);
|
|
189
191
|
|
|
192
|
+
// Add the preceding invoice references (BG-3) the document states
|
|
193
|
+
this.addPrecedingInvoiceReferences(doc, invoice);
|
|
194
|
+
|
|
190
195
|
// Add line items
|
|
191
196
|
this.addLineItems(doc, transactionElement, invoice);
|
|
192
197
|
}
|
|
@@ -316,12 +321,12 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
316
321
|
transactionElement.appendChild(deliveryElement);
|
|
317
322
|
|
|
318
323
|
// Add delivery date if available
|
|
319
|
-
if (invoice.deliveryDate) {
|
|
324
|
+
if (invoice.deliveryDate !== undefined) {
|
|
320
325
|
const deliveryDateElement = doc.createElement('ram:ActualDeliverySupplyChainEvent');
|
|
321
326
|
const occurrenceDateElement = doc.createElement('ram:OccurrenceDateTime');
|
|
322
327
|
const dateStringElement = doc.createElement('udt:DateTimeString');
|
|
323
328
|
dateStringElement.setAttribute('format', '102'); // YYYYMMDD format
|
|
324
|
-
dateStringElement.textContent = this.formatDateYYYYMMDD(invoice.deliveryDate);
|
|
329
|
+
dateStringElement.textContent = this.formatDateYYYYMMDD(invoice.deliveryDate, 'BT-72 actual delivery date');
|
|
325
330
|
occurrenceDateElement.appendChild(dateStringElement);
|
|
326
331
|
deliveryDateElement.appendChild(occurrenceDateElement);
|
|
327
332
|
deliveryElement.appendChild(deliveryDateElement);
|
|
@@ -332,21 +337,21 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
332
337
|
const periodElement = doc.createElement('ram:BillingSpecifiedPeriod');
|
|
333
338
|
|
|
334
339
|
// Start date
|
|
335
|
-
if (invoice.periodOfPerformance.from) {
|
|
340
|
+
if (invoice.periodOfPerformance.from !== undefined) {
|
|
336
341
|
const startDateElement = doc.createElement('ram:StartDateTime');
|
|
337
342
|
const startDateStringElement = doc.createElement('udt:DateTimeString');
|
|
338
343
|
startDateStringElement.setAttribute('format', '102'); // YYYYMMDD format
|
|
339
|
-
startDateStringElement.textContent = this.formatDateYYYYMMDD(invoice.periodOfPerformance.from);
|
|
344
|
+
startDateStringElement.textContent = this.formatDateYYYYMMDD(invoice.periodOfPerformance.from, 'BT-73 invoicing period start date');
|
|
340
345
|
startDateElement.appendChild(startDateStringElement);
|
|
341
346
|
periodElement.appendChild(startDateElement);
|
|
342
347
|
}
|
|
343
348
|
|
|
344
349
|
// End date
|
|
345
|
-
if (invoice.periodOfPerformance.to) {
|
|
350
|
+
if (invoice.periodOfPerformance.to !== undefined) {
|
|
346
351
|
const endDateElement = doc.createElement('ram:EndDateTime');
|
|
347
352
|
const endDateStringElement = doc.createElement('udt:DateTimeString');
|
|
348
353
|
endDateStringElement.setAttribute('format', '102'); // YYYYMMDD format
|
|
349
|
-
endDateStringElement.textContent = this.formatDateYYYYMMDD(invoice.periodOfPerformance.to);
|
|
354
|
+
endDateStringElement.textContent = this.formatDateYYYYMMDD(invoice.periodOfPerformance.to, 'BT-74 invoicing period end date');
|
|
350
355
|
endDateElement.appendChild(endDateStringElement);
|
|
351
356
|
periodElement.appendChild(endDateElement);
|
|
352
357
|
}
|
|
@@ -378,7 +383,10 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
378
383
|
if (invoice.paymentOptions) {
|
|
379
384
|
// Add payment instructions as description - this is generic enough to work with any payment type
|
|
380
385
|
const descriptionElement = doc.createElement('ram:Description');
|
|
381
|
-
descriptionElement.textContent =
|
|
386
|
+
descriptionElement.textContent = [
|
|
387
|
+
getPaymentTermsNote(invoice.date, invoice.dueInDays, invoice.language, 'cii'),
|
|
388
|
+
invoice.paymentOptions.description,
|
|
389
|
+
].filter(Boolean).join('. ');
|
|
382
390
|
paymentTermsElement.appendChild(descriptionElement);
|
|
383
391
|
}
|
|
384
392
|
|
|
@@ -388,10 +396,9 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
388
396
|
dateStringElement.setAttribute('format', '102'); // YYYYMMDD format
|
|
389
397
|
|
|
390
398
|
// Calculate due date
|
|
391
|
-
const dueDate =
|
|
392
|
-
dueDate.setDate(dueDate.getDate() + invoice.dueInDays);
|
|
399
|
+
const dueDate = getWritableDueDate(invoice.date, invoice.dueInDays, 'cii');
|
|
393
400
|
|
|
394
|
-
dateStringElement.textContent = this.formatDateYYYYMMDD(dueDate.getTime());
|
|
401
|
+
dateStringElement.textContent = this.formatDateYYYYMMDD(dueDate.getTime(), 'BT-9 payment due date');
|
|
395
402
|
dueDateElement.appendChild(dateStringElement);
|
|
396
403
|
paymentTermsElement.appendChild(dueDateElement);
|
|
397
404
|
|
|
@@ -655,11 +662,12 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
655
662
|
* @param timestamp Timestamp to format
|
|
656
663
|
* @returns Formatted date string
|
|
657
664
|
*/
|
|
658
|
-
private formatDateYYYYMMDD(timestamp: number): string {
|
|
659
|
-
const date =
|
|
660
|
-
|
|
661
|
-
const
|
|
662
|
-
const
|
|
665
|
+
private formatDateYYYYMMDD(timestamp: number, field: string): string {
|
|
666
|
+
const date = getWritableDate(timestamp, field, 'cii');
|
|
667
|
+
// the UTC fields: a calendar day is its UTC midnight, whatever the server's zone
|
|
668
|
+
const year = date.getUTCFullYear();
|
|
669
|
+
const month = (date.getUTCMonth() + 1).toString().padStart(2, '0');
|
|
670
|
+
const day = date.getUTCDate().toString().padStart(2, '0');
|
|
663
671
|
return `${year}${month}${day}`;
|
|
664
672
|
}
|
|
665
673
|
}
|