@fin.cx/einvoice 8.1.0 → 8.2.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 +1 -1
- package/dist_ts/einvoice.d.ts +14 -1
- package/dist_ts/einvoice.js +17 -2
- package/dist_ts/formats/base/base.encoder.d.ts +1 -1
- package/dist_ts/formats/cii/cii.decoder.d.ts +12 -1
- package/dist_ts/formats/cii/cii.decoder.js +34 -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 +9 -4
- package/dist_ts/formats/cii/facturx/facturx.encoder.d.ts +2 -2
- package/dist_ts/formats/cii/facturx/facturx.encoder.js +13 -10
- package/dist_ts/formats/cii/zugferd/zugferd.decoder.d.ts +1 -1
- package/dist_ts/formats/cii/zugferd/zugferd.decoder.js +9 -4
- package/dist_ts/formats/cii/zugferd/zugferd.encoder.d.ts +1 -1
- package/dist_ts/formats/cii/zugferd/zugferd.encoder.js +16 -13
- package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.d.ts +7 -1
- package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.js +27 -4
- package/dist_ts/formats/ubl/generic/ubl.encoder.d.ts +23 -1
- package/dist_ts/formats/ubl/generic/ubl.encoder.js +67 -11
- 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/xrechnung/xrechnung.decoder.d.ts +6 -1
- package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.js +37 -7
- package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.d.ts +1 -1
- package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.js +4 -2
- package/dist_ts/formats/utils/date.value.d.ts +10 -0
- package/dist_ts/formats/utils/date.value.js +21 -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/preceding.invoice.d.ts +26 -0
- package/dist_ts/formats/utils/preceding.invoice.js +52 -0
- 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 +60 -10
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/einvoice.ts +18 -1
- package/ts/formats/base/base.encoder.ts +1 -1
- package/ts/formats/cii/cii.decoder.ts +43 -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 +9 -3
- package/ts/formats/cii/facturx/facturx.encoder.ts +13 -9
- package/ts/formats/cii/zugferd/zugferd.decoder.ts +9 -3
- package/ts/formats/cii/zugferd/zugferd.encoder.ts +16 -12
- package/ts/formats/cii/zugferd/zugferd.v1.decoder.ts +30 -3
- package/ts/formats/ubl/generic/ubl.encoder.ts +74 -10
- package/ts/formats/ubl/ubl.decoder.ts +1 -1
- package/ts/formats/ubl/ubl.encoder.ts +77 -13
- package/ts/formats/ubl/xrechnung/xrechnung.decoder.ts +45 -6
- package/ts/formats/ubl/xrechnung/xrechnung.encoder.ts +4 -1
- package/ts/formats/utils/date.value.ts +21 -0
- package/ts/formats/utils/document.typecode.ts +7 -3
- package/ts/formats/utils/preceding.invoice.ts +74 -0
- package/ts/index.ts +3 -0
- package/ts/interfaces/common.ts +6 -1
|
@@ -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
|
*/
|
|
@@ -83,6 +84,9 @@ export class ZUGFeRDDecoder extends CIIBaseDecoder {
|
|
|
83
84
|
notes = allNotes.slice(1); // Remove subject from notes
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
// Extract the actual delivery date, if stated
|
|
88
|
+
const deliveryDate = this.extractDeliveryDate();
|
|
89
|
+
|
|
86
90
|
// Check for reverse charge
|
|
87
91
|
const reverseCharge = this.exists('//ram:SpecifiedTradeAllowanceCharge/ram:ReasonCode[text()="62"]');
|
|
88
92
|
|
|
@@ -110,8 +114,10 @@ export class ZUGFeRDDecoder extends CIIBaseDecoder {
|
|
|
110
114
|
reverseCharge: reverseCharge,
|
|
111
115
|
currency: currencyCode as finance.TCurrency,
|
|
112
116
|
notes: notes,
|
|
113
|
-
|
|
114
|
-
|
|
117
|
+
// the day of supply as the document states it (BT-72)
|
|
118
|
+
...(deliveryDate === undefined ? {} : { deliveryDate }),
|
|
119
|
+
objectActions: [],
|
|
120
|
+
...getPrecedingInvoiceFields(accountingDocType, this.extractPrecedingInvoiceReferences()),
|
|
115
121
|
};
|
|
116
122
|
|
|
117
123
|
// Validate mandatory EN16931 fields unless validation is skipped
|
|
@@ -4,6 +4,7 @@ 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 } from '../../utils/date.value.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Encoder for ZUGFeRD invoice format
|
|
@@ -35,7 +36,7 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
/**
|
|
38
|
-
* Encodes an invoice document (invoice, debit note or self-billed invoice) into ZUGFeRD XML
|
|
39
|
+
* Encodes an invoice document (invoice, corrected invoice, debit note or self-billed invoice) into ZUGFeRD XML
|
|
39
40
|
* @param invoice Invoice document to encode
|
|
40
41
|
* @returns ZUGFeRD XML string
|
|
41
42
|
*/
|
|
@@ -43,7 +44,7 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
43
44
|
// Create base XML
|
|
44
45
|
const xmlDoc = this.createBaseXml();
|
|
45
46
|
|
|
46
|
-
// Set document type code (380 invoice, 383 debit note, 389 self-billed invoice)
|
|
47
|
+
// Set document type code (380 invoice, 383 debit note, 384 corrected invoice, 389 self-billed invoice)
|
|
47
48
|
this.setDocumentTypeCode(xmlDoc, getDocumentTypeCode(invoice.accountingDocType));
|
|
48
49
|
|
|
49
50
|
// Add common invoice data
|
|
@@ -149,7 +150,7 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
149
150
|
const issueDateElement = doc.createElement('ram:IssueDateTime');
|
|
150
151
|
const dateStringElement = doc.createElement('udt:DateTimeString');
|
|
151
152
|
dateStringElement.setAttribute('format', '102'); // YYYYMMDD format
|
|
152
|
-
dateStringElement.textContent = this.formatDateYYYYMMDD(invoice.date);
|
|
153
|
+
dateStringElement.textContent = this.formatDateYYYYMMDD(invoice.date, 'BT-2 invoice issue date');
|
|
153
154
|
issueDateElement.appendChild(dateStringElement);
|
|
154
155
|
documentElement.appendChild(issueDateElement);
|
|
155
156
|
|
|
@@ -187,6 +188,9 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
187
188
|
// Add settlement section with payment terms and totals
|
|
188
189
|
this.addSettlementSection(doc, transactionElement, invoice);
|
|
189
190
|
|
|
191
|
+
// Add the preceding invoice references (BG-3) the document states
|
|
192
|
+
this.addPrecedingInvoiceReferences(doc, invoice);
|
|
193
|
+
|
|
190
194
|
// Add line items
|
|
191
195
|
this.addLineItems(doc, transactionElement, invoice);
|
|
192
196
|
}
|
|
@@ -316,12 +320,12 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
316
320
|
transactionElement.appendChild(deliveryElement);
|
|
317
321
|
|
|
318
322
|
// Add delivery date if available
|
|
319
|
-
if (invoice.deliveryDate) {
|
|
323
|
+
if (invoice.deliveryDate !== undefined) {
|
|
320
324
|
const deliveryDateElement = doc.createElement('ram:ActualDeliverySupplyChainEvent');
|
|
321
325
|
const occurrenceDateElement = doc.createElement('ram:OccurrenceDateTime');
|
|
322
326
|
const dateStringElement = doc.createElement('udt:DateTimeString');
|
|
323
327
|
dateStringElement.setAttribute('format', '102'); // YYYYMMDD format
|
|
324
|
-
dateStringElement.textContent = this.formatDateYYYYMMDD(invoice.deliveryDate);
|
|
328
|
+
dateStringElement.textContent = this.formatDateYYYYMMDD(invoice.deliveryDate, 'BT-72 actual delivery date');
|
|
325
329
|
occurrenceDateElement.appendChild(dateStringElement);
|
|
326
330
|
deliveryDateElement.appendChild(occurrenceDateElement);
|
|
327
331
|
deliveryElement.appendChild(deliveryDateElement);
|
|
@@ -332,21 +336,21 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
332
336
|
const periodElement = doc.createElement('ram:BillingSpecifiedPeriod');
|
|
333
337
|
|
|
334
338
|
// Start date
|
|
335
|
-
if (invoice.periodOfPerformance.from) {
|
|
339
|
+
if (invoice.periodOfPerformance.from !== undefined) {
|
|
336
340
|
const startDateElement = doc.createElement('ram:StartDateTime');
|
|
337
341
|
const startDateStringElement = doc.createElement('udt:DateTimeString');
|
|
338
342
|
startDateStringElement.setAttribute('format', '102'); // YYYYMMDD format
|
|
339
|
-
startDateStringElement.textContent = this.formatDateYYYYMMDD(invoice.periodOfPerformance.from);
|
|
343
|
+
startDateStringElement.textContent = this.formatDateYYYYMMDD(invoice.periodOfPerformance.from, 'BT-73 invoicing period start date');
|
|
340
344
|
startDateElement.appendChild(startDateStringElement);
|
|
341
345
|
periodElement.appendChild(startDateElement);
|
|
342
346
|
}
|
|
343
347
|
|
|
344
348
|
// End date
|
|
345
|
-
if (invoice.periodOfPerformance.to) {
|
|
349
|
+
if (invoice.periodOfPerformance.to !== undefined) {
|
|
346
350
|
const endDateElement = doc.createElement('ram:EndDateTime');
|
|
347
351
|
const endDateStringElement = doc.createElement('udt:DateTimeString');
|
|
348
352
|
endDateStringElement.setAttribute('format', '102'); // YYYYMMDD format
|
|
349
|
-
endDateStringElement.textContent = this.formatDateYYYYMMDD(invoice.periodOfPerformance.to);
|
|
353
|
+
endDateStringElement.textContent = this.formatDateYYYYMMDD(invoice.periodOfPerformance.to, 'BT-74 invoicing period end date');
|
|
350
354
|
endDateElement.appendChild(endDateStringElement);
|
|
351
355
|
periodElement.appendChild(endDateElement);
|
|
352
356
|
}
|
|
@@ -391,7 +395,7 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
391
395
|
const dueDate = new Date(invoice.date);
|
|
392
396
|
dueDate.setDate(dueDate.getDate() + invoice.dueInDays);
|
|
393
397
|
|
|
394
|
-
dateStringElement.textContent = this.formatDateYYYYMMDD(dueDate.getTime());
|
|
398
|
+
dateStringElement.textContent = this.formatDateYYYYMMDD(dueDate.getTime(), 'BT-9 payment due date');
|
|
395
399
|
dueDateElement.appendChild(dateStringElement);
|
|
396
400
|
paymentTermsElement.appendChild(dueDateElement);
|
|
397
401
|
|
|
@@ -655,8 +659,8 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
655
659
|
* @param timestamp Timestamp to format
|
|
656
660
|
* @returns Formatted date string
|
|
657
661
|
*/
|
|
658
|
-
private formatDateYYYYMMDD(timestamp: number): string {
|
|
659
|
-
const date =
|
|
662
|
+
private formatDateYYYYMMDD(timestamp: number, field: string): string {
|
|
663
|
+
const date = getWritableDate(timestamp, field, 'cii');
|
|
660
664
|
const year = date.getFullYear();
|
|
661
665
|
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
662
666
|
const day = date.getDate().toString().padStart(2, '0');
|
|
@@ -6,9 +6,16 @@ import type {
|
|
|
6
6
|
TInvoiceDocument,
|
|
7
7
|
} from '../../../interfaces/common.js';
|
|
8
8
|
import { ZUGFERD_V1_NAMESPACES } from '../cii.types.js';
|
|
9
|
-
import { business, finance } from '../../../plugins.js';
|
|
9
|
+
import { business, finance, xpath } from '../../../plugins.js';
|
|
10
10
|
import { EN16931Validator } from '../../validation/en16931.validator.js';
|
|
11
11
|
|
|
12
|
+
/** XPath selection with the ZUGFeRD v1 namespaces */
|
|
13
|
+
const zugferdV1Select = xpath.useNamespaces({
|
|
14
|
+
rsm: ZUGFERD_V1_NAMESPACES.RSM,
|
|
15
|
+
ram: ZUGFERD_V1_NAMESPACES.RAM,
|
|
16
|
+
udt: ZUGFERD_V1_NAMESPACES.UDT,
|
|
17
|
+
});
|
|
18
|
+
|
|
12
19
|
/**
|
|
13
20
|
* Decoder for ZUGFeRD v1 invoice format
|
|
14
21
|
*/
|
|
@@ -28,6 +35,22 @@ export class ZUGFeRDV1Decoder extends CIIBaseDecoder {
|
|
|
28
35
|
};
|
|
29
36
|
}
|
|
30
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Reads the actual delivery date of the v1 header delivery
|
|
40
|
+
* (`ram:ApplicableSupplyChainTradeDelivery`); undefined when the document
|
|
41
|
+
* does not state one.
|
|
42
|
+
*/
|
|
43
|
+
protected override extractDeliveryDate(): number | undefined {
|
|
44
|
+
const deliveryDatePath =
|
|
45
|
+
'/rsm:CrossIndustryDocument/rsm:SpecifiedSupplyChainTradeTransaction/ram:ApplicableSupplyChainTradeDelivery/ram:ActualDeliverySupplyChainEvent/ram:OccurrenceDateTime/udt:DateTimeString';
|
|
46
|
+
const deliveryDate = String(zugferdV1Select(`string(${deliveryDatePath})`, this.doc)).trim();
|
|
47
|
+
if (!deliveryDate) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
const format = String(zugferdV1Select(`string(${deliveryDatePath}/@format)`, this.doc)).trim();
|
|
51
|
+
return this.parseRequiredCIIDate(deliveryDate, format);
|
|
52
|
+
}
|
|
53
|
+
|
|
31
54
|
/**
|
|
32
55
|
* Decodes a ZUGFeRD v1 credit note
|
|
33
56
|
* @returns Promise resolving to a TCreditNote object
|
|
@@ -37,7 +60,7 @@ export class ZUGFeRDV1Decoder extends CIIBaseDecoder {
|
|
|
37
60
|
}
|
|
38
61
|
|
|
39
62
|
/**
|
|
40
|
-
* Decodes a ZUGFeRD v1 invoice document: an invoice, a debit note or a self-billed invoice
|
|
63
|
+
* Decodes a ZUGFeRD v1 invoice document: an invoice, a corrected invoice, a debit note or a self-billed invoice
|
|
41
64
|
* @param accountingDocType The type the document type code stands for
|
|
42
65
|
* @returns Promise resolving to the invoice document
|
|
43
66
|
*/
|
|
@@ -99,6 +122,9 @@ export class ZUGFeRDV1Decoder extends CIIBaseDecoder {
|
|
|
99
122
|
notes = allNotes.slice(1); // Remove subject from notes
|
|
100
123
|
}
|
|
101
124
|
|
|
125
|
+
// Extract the actual delivery date, if stated
|
|
126
|
+
const deliveryDate = this.extractDeliveryDate();
|
|
127
|
+
|
|
102
128
|
// Check for reverse charge
|
|
103
129
|
const reverseCharge = this.exists('//ram:SpecifiedTradeAllowanceCharge/ram:ReasonCode[text()="62"]');
|
|
104
130
|
|
|
@@ -126,7 +152,8 @@ export class ZUGFeRDV1Decoder extends CIIBaseDecoder {
|
|
|
126
152
|
reverseCharge: reverseCharge,
|
|
127
153
|
currency: currencyCode as finance.TCurrency,
|
|
128
154
|
notes: notes,
|
|
129
|
-
|
|
155
|
+
// the day of supply as the document states it (BT-72), never the issue date in its place
|
|
156
|
+
...(deliveryDate === undefined ? {} : { deliveryDate }),
|
|
130
157
|
objectActions: []
|
|
131
158
|
};
|
|
132
159
|
|
|
@@ -3,6 +3,8 @@ import type { TAccountingDoc, TCreditNote, TInvoiceDocument } from '../../../int
|
|
|
3
3
|
import { UBLDocumentType } from '../ubl.types.js';
|
|
4
4
|
import { DOMParser, XMLSerializer } from '../../../plugins.js';
|
|
5
5
|
import { getDocumentTypeCode } from '../../utils/document.typecode.js';
|
|
6
|
+
import { getPrecedingInvoiceReferences } from '../../utils/preceding.invoice.js';
|
|
7
|
+
import { getWritableDate } from '../../utils/date.value.js';
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* UBL Encoder implementation
|
|
@@ -24,13 +26,15 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
24
26
|
|
|
25
27
|
// Add credit note specific data
|
|
26
28
|
this.addCreditNoteSpecificData(doc, creditNote);
|
|
29
|
+
|
|
30
|
+
this.orderRootElements(doc);
|
|
27
31
|
|
|
28
32
|
// Serialize to string
|
|
29
33
|
return new XMLSerializer().serializeToString(doc);
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
/**
|
|
33
|
-
* Encodes an invoice document (invoice, debit note or self-billed invoice) into UBL XML
|
|
37
|
+
* Encodes an invoice document (invoice, corrected invoice, debit note or self-billed invoice) into UBL XML
|
|
34
38
|
* @param invoice Invoice document to encode
|
|
35
39
|
* @returns UBL XML string
|
|
36
40
|
*/
|
|
@@ -44,6 +48,8 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
44
48
|
|
|
45
49
|
// Add invoice specific data
|
|
46
50
|
this.addInvoiceSpecificData(doc, invoice);
|
|
51
|
+
|
|
52
|
+
this.orderRootElements(doc);
|
|
47
53
|
|
|
48
54
|
// Serialize to string
|
|
49
55
|
return new XMLSerializer().serializeToString(doc);
|
|
@@ -71,16 +77,15 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
71
77
|
this.appendElement(doc, root, 'cbc:ID', invoice.id);
|
|
72
78
|
|
|
73
79
|
// Issue Date
|
|
74
|
-
this.appendElement(doc, root, 'cbc:IssueDate', this.formatDate(invoice.date));
|
|
80
|
+
this.appendElement(doc, root, 'cbc:IssueDate', this.formatDate(invoice.date, 'BT-2 invoice issue date'));
|
|
75
81
|
|
|
76
82
|
// Due Date - the CreditNote schema has no DueDate element; its payment due date lives in cac:PaymentMeans
|
|
77
83
|
if (documentType === UBLDocumentType.INVOICE) {
|
|
78
|
-
const
|
|
79
|
-
const dueDate = new Date(issueTimestamp);
|
|
84
|
+
const dueDate = getWritableDate(invoice.date, 'BT-2 invoice issue date', 'ubl');
|
|
80
85
|
dueDate.setDate(dueDate.getDate() + (invoice.dueInDays || 30));
|
|
81
|
-
this.appendElement(doc, root, 'cbc:DueDate', this.formatDate(dueDate.getTime()));
|
|
86
|
+
this.appendElement(doc, root, 'cbc:DueDate', this.formatDate(dueDate.getTime(), 'BT-9 payment due date'));
|
|
82
87
|
}
|
|
83
|
-
// Document Type Code (380 invoice, 381 credit note, 383 debit note, 389 self-billed invoice; the element differs per schema)
|
|
88
|
+
// Document Type Code (380 invoice, 381 credit note, 383 debit note, 384 corrected invoice, 389 self-billed invoice; the element differs per schema)
|
|
84
89
|
this.appendElement(
|
|
85
90
|
doc,
|
|
86
91
|
root,
|
|
@@ -104,12 +109,21 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
104
109
|
|
|
105
110
|
// Document Currency Code
|
|
106
111
|
this.appendElement(doc, root, 'cbc:DocumentCurrencyCode', invoice.currency);
|
|
112
|
+
|
|
113
|
+
// Invoicing period (BG-14), in schema order before the document references
|
|
114
|
+
this.addInvoicePeriod(doc, root, invoice);
|
|
115
|
+
|
|
116
|
+
// Preceding invoice references (BG-3)
|
|
117
|
+
this.addBillingReferences(doc, root, invoice);
|
|
107
118
|
|
|
108
119
|
// Add accounting supplier party (seller)
|
|
109
120
|
this.addParty(doc, root, 'cac:AccountingSupplierParty', invoice.from);
|
|
110
121
|
|
|
111
122
|
// Add accounting customer party (buyer)
|
|
112
123
|
this.addParty(doc, root, 'cac:AccountingCustomerParty', invoice.to);
|
|
124
|
+
|
|
125
|
+
// Actual delivery date (BT-72), in schema order after the parties
|
|
126
|
+
this.addDelivery(doc, root, invoice);
|
|
113
127
|
|
|
114
128
|
// Add payment terms
|
|
115
129
|
this.addPaymentTerms(doc, root, invoice);
|
|
@@ -127,6 +141,57 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
127
141
|
this.preserveMetadata(doc, root, invoice);
|
|
128
142
|
}
|
|
129
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Writes the invoicing period (BG-14: BT-73 and BT-74) the document states.
|
|
146
|
+
* @param doc XML document
|
|
147
|
+
* @param root Document root
|
|
148
|
+
* @param invoice Accounting document
|
|
149
|
+
*/
|
|
150
|
+
private addInvoicePeriod(doc: Document, root: Element, invoice: TAccountingDoc): void {
|
|
151
|
+
if (!invoice.periodOfPerformance) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const invoicePeriod = doc.createElement('cac:InvoicePeriod');
|
|
155
|
+
this.appendElement(doc, invoicePeriod, 'cbc:StartDate', this.formatDate(invoice.periodOfPerformance.from, 'BT-73 invoicing period start date'));
|
|
156
|
+
this.appendElement(doc, invoicePeriod, 'cbc:EndDate', this.formatDate(invoice.periodOfPerformance.to, 'BT-74 invoicing period end date'));
|
|
157
|
+
root.appendChild(invoicePeriod);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Writes one preceding invoice reference (BG-3) per invoice the document
|
|
162
|
+
* states it corrects: the number (BT-25) and, when stated, the issue date (BT-26).
|
|
163
|
+
* @param doc XML document
|
|
164
|
+
* @param root Document root
|
|
165
|
+
* @param invoice Accounting document
|
|
166
|
+
*/
|
|
167
|
+
private addBillingReferences(doc: Document, root: Element, invoice: TAccountingDoc): void {
|
|
168
|
+
for (const reference of getPrecedingInvoiceReferences(invoice)) {
|
|
169
|
+
const billingReference = doc.createElement('cac:BillingReference');
|
|
170
|
+
const invoiceDocumentReference = doc.createElement('cac:InvoiceDocumentReference');
|
|
171
|
+
this.appendElement(doc, invoiceDocumentReference, 'cbc:ID', reference.documentId);
|
|
172
|
+
if (reference.issueDate !== undefined) {
|
|
173
|
+
this.appendElement(doc, invoiceDocumentReference, 'cbc:IssueDate', this.formatDate(reference.issueDate, 'BT-26 preceding invoice issue date'));
|
|
174
|
+
}
|
|
175
|
+
billingReference.appendChild(invoiceDocumentReference);
|
|
176
|
+
root.appendChild(billingReference);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Writes the actual delivery date (BT-72) the document states.
|
|
182
|
+
* @param doc XML document
|
|
183
|
+
* @param root Document root
|
|
184
|
+
* @param invoice Accounting document
|
|
185
|
+
*/
|
|
186
|
+
private addDelivery(doc: Document, root: Element, invoice: TAccountingDoc): void {
|
|
187
|
+
if (invoice.deliveryDate === undefined) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const delivery = doc.createElement('cac:Delivery');
|
|
191
|
+
this.appendElement(doc, delivery, 'cbc:ActualDeliveryDate', this.formatDate(invoice.deliveryDate, 'BT-72 actual delivery date'));
|
|
192
|
+
root.appendChild(delivery);
|
|
193
|
+
}
|
|
194
|
+
|
|
130
195
|
/**
|
|
131
196
|
* Adds credit note specific data to the document
|
|
132
197
|
* @param doc XML document
|
|
@@ -280,11 +345,10 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
280
345
|
// Payment means code - default to credit transfer
|
|
281
346
|
this.appendElement(doc, paymentMeansNode, 'cbc:PaymentMeansCode', '30');
|
|
282
347
|
|
|
283
|
-
// Payment due date
|
|
284
|
-
const
|
|
285
|
-
const dueDate = new Date(issueTimestamp);
|
|
348
|
+
// Payment due date, counted from the issue date the document states
|
|
349
|
+
const dueDate = getWritableDate(invoice.date, 'BT-2 invoice issue date', 'ubl');
|
|
286
350
|
dueDate.setDate(dueDate.getDate() + (invoice.dueInDays || 30));
|
|
287
|
-
this.appendElement(doc, paymentMeansNode, 'cbc:PaymentDueDate', this.formatDate(dueDate.getTime()));
|
|
351
|
+
this.appendElement(doc, paymentMeansNode, 'cbc:PaymentDueDate', this.formatDate(dueDate.getTime(), 'BT-9 payment due date'));
|
|
288
352
|
|
|
289
353
|
// Add payment channel code if available
|
|
290
354
|
if (paymentOptions.description) {
|
|
@@ -89,7 +89,7 @@ export abstract class UBLBaseDecoder extends BaseDecoder {
|
|
|
89
89
|
protected abstract decodeCreditNote(): Promise<TCreditNote>;
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
|
-
* Decodes a UBL invoice document: an invoice, a debit note or a self-billed invoice
|
|
92
|
+
* Decodes a UBL invoice document: an invoice, a corrected invoice, a debit note or a self-billed invoice
|
|
93
93
|
* @param accountingDocType The type the document type code stands for
|
|
94
94
|
* @returns Promise resolving to the invoice document
|
|
95
95
|
*/
|
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
import { BaseEncoder } from '../base/base.encoder.js';
|
|
2
2
|
import type { TAccountingDoc, TCreditNote, TInvoiceDocument } from '../../interfaces/common.js';
|
|
3
3
|
import { UBLDocumentType, UBL_NAMESPACES } from './ubl.types.js';
|
|
4
|
+
import { getWritableDate } from '../utils/date.value.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The children of the document root in the order the UBL 2.1 schema requires
|
|
8
|
+
* them (UBL-Invoice-2.1.xsd and UBL-CreditNote-2.1.xsd, local names).
|
|
9
|
+
*/
|
|
10
|
+
const UBL_ROOT_SEQUENCES: Record<UBLDocumentType, readonly string[]> = {
|
|
11
|
+
[UBLDocumentType.INVOICE]: [
|
|
12
|
+
'UBLExtensions', 'UBLVersionID', 'CustomizationID', 'ProfileID', 'ProfileExecutionID', 'ID',
|
|
13
|
+
'CopyIndicator', 'UUID', 'IssueDate', 'IssueTime', 'DueDate', 'InvoiceTypeCode', 'Note',
|
|
14
|
+
'TaxPointDate', 'DocumentCurrencyCode', 'TaxCurrencyCode', 'PricingCurrencyCode',
|
|
15
|
+
'PaymentCurrencyCode', 'PaymentAlternativeCurrencyCode', 'AccountingCostCode', 'AccountingCost',
|
|
16
|
+
'LineCountNumeric', 'BuyerReference', 'InvoicePeriod', 'OrderReference', 'BillingReference',
|
|
17
|
+
'DespatchDocumentReference', 'ReceiptDocumentReference', 'StatementDocumentReference',
|
|
18
|
+
'OriginatorDocumentReference', 'ContractDocumentReference', 'AdditionalDocumentReference',
|
|
19
|
+
'ProjectReference', 'Signature', 'AccountingSupplierParty', 'AccountingCustomerParty',
|
|
20
|
+
'PayeeParty', 'BuyerCustomerParty', 'SellerSupplierParty', 'TaxRepresentativeParty', 'Delivery',
|
|
21
|
+
'DeliveryTerms', 'PaymentMeans', 'PaymentTerms', 'PrepaidPayment', 'AllowanceCharge',
|
|
22
|
+
'TaxExchangeRate', 'PricingExchangeRate', 'PaymentExchangeRate', 'PaymentAlternativeExchangeRate',
|
|
23
|
+
'TaxTotal', 'WithholdingTaxTotal', 'LegalMonetaryTotal', 'InvoiceLine',
|
|
24
|
+
],
|
|
25
|
+
[UBLDocumentType.CREDIT_NOTE]: [
|
|
26
|
+
'UBLExtensions', 'UBLVersionID', 'CustomizationID', 'ProfileID', 'ProfileExecutionID', 'ID',
|
|
27
|
+
'CopyIndicator', 'UUID', 'IssueDate', 'IssueTime', 'TaxPointDate', 'CreditNoteTypeCode', 'Note',
|
|
28
|
+
'DocumentCurrencyCode', 'TaxCurrencyCode', 'PricingCurrencyCode', 'PaymentCurrencyCode',
|
|
29
|
+
'PaymentAlternativeCurrencyCode', 'AccountingCostCode', 'AccountingCost', 'LineCountNumeric',
|
|
30
|
+
'BuyerReference', 'InvoicePeriod', 'DiscrepancyResponse', 'OrderReference', 'BillingReference',
|
|
31
|
+
'DespatchDocumentReference', 'ReceiptDocumentReference', 'ContractDocumentReference',
|
|
32
|
+
'AdditionalDocumentReference', 'StatementDocumentReference', 'OriginatorDocumentReference',
|
|
33
|
+
'Signature', 'AccountingSupplierParty', 'AccountingCustomerParty', 'PayeeParty',
|
|
34
|
+
'BuyerCustomerParty', 'SellerSupplierParty', 'TaxRepresentativeParty', 'Delivery',
|
|
35
|
+
'DeliveryTerms', 'PaymentMeans', 'PaymentTerms', 'TaxExchangeRate', 'PricingExchangeRate',
|
|
36
|
+
'PaymentExchangeRate', 'PaymentAlternativeExchangeRate', 'AllowanceCharge', 'TaxTotal',
|
|
37
|
+
'LegalMonetaryTotal', 'CreditNoteLine',
|
|
38
|
+
],
|
|
39
|
+
};
|
|
4
40
|
|
|
5
41
|
/**
|
|
6
42
|
* Base encoder for UBL-based invoice formats
|
|
@@ -27,7 +63,7 @@ export abstract class UBLBaseEncoder extends BaseEncoder {
|
|
|
27
63
|
protected abstract encodeCreditNote(creditNote: TCreditNote): Promise<string>;
|
|
28
64
|
|
|
29
65
|
/**
|
|
30
|
-
* Encodes an invoice document (invoice, debit note or self-billed invoice) into UBL XML
|
|
66
|
+
* Encodes an invoice document (invoice, corrected invoice, debit note or self-billed invoice) into UBL XML
|
|
31
67
|
* @param invoice Invoice document to encode
|
|
32
68
|
* @returns UBL XML string
|
|
33
69
|
*/
|
|
@@ -47,20 +83,48 @@ export abstract class UBLBaseEncoder extends BaseEncoder {
|
|
|
47
83
|
}
|
|
48
84
|
|
|
49
85
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
86
|
+
* Puts the children of the document root into the order the schema requires.
|
|
87
|
+
* Elements are added in several passes (the generic encoder, a customization,
|
|
88
|
+
* preserved metadata), each inserting where it can; this final pass makes the
|
|
89
|
+
* result follow the schema sequence. The sort is stable, so repeated elements
|
|
90
|
+
* (notes, references, lines) keep their order, and an element the sequence
|
|
91
|
+
* does not name stays behind the element it followed.
|
|
92
|
+
* @param doc XML document
|
|
53
93
|
*/
|
|
54
|
-
protected
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
94
|
+
protected orderRootElements(doc: Document): void {
|
|
95
|
+
const root = doc.documentElement;
|
|
96
|
+
const sequence = UBL_ROOT_SEQUENCES[root.localName as UBLDocumentType];
|
|
97
|
+
if (!sequence) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const children: Element[] = [];
|
|
101
|
+
for (let node = root.firstChild; node; node = node.nextSibling) {
|
|
102
|
+
if (node.nodeType === 1) {
|
|
103
|
+
children.push(node as Element);
|
|
104
|
+
}
|
|
58
105
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
106
|
+
let lastRank = -1;
|
|
107
|
+
const ranked = children.map((element, position) => {
|
|
108
|
+
// an element made with createElement('cac:X') carries its prefix in localName as well
|
|
109
|
+
const rank = sequence.indexOf(element.nodeName.slice(element.nodeName.indexOf(':') + 1));
|
|
110
|
+
lastRank = rank === -1 ? lastRank : rank;
|
|
111
|
+
return { element, rank: lastRank, position };
|
|
112
|
+
});
|
|
113
|
+
ranked.sort((a, b) => a.rank - b.rank || a.position - b.position);
|
|
114
|
+
for (const { element } of ranked) {
|
|
115
|
+
root.appendChild(element);
|
|
63
116
|
}
|
|
64
|
-
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Formats a date as an ISO string (YYYY-MM-DD, UTC). A value that is no
|
|
121
|
+
* valid timestamp is refused with an `EInvoiceFormatError` naming the field;
|
|
122
|
+
* no date is put in its place.
|
|
123
|
+
* @param timestamp Timestamp to format
|
|
124
|
+
* @param field The business term the date is written as
|
|
125
|
+
* @returns Formatted date string
|
|
126
|
+
*/
|
|
127
|
+
protected formatDate(timestamp: number, field: string): string {
|
|
128
|
+
return getWritableDate(timestamp, field, 'ubl').toISOString().split('T')[0];
|
|
65
129
|
}
|
|
66
130
|
}
|
|
@@ -8,6 +8,10 @@ import type {
|
|
|
8
8
|
import { business, finance } from '../../../plugins.js';
|
|
9
9
|
import { UBLDocumentType } from '../ubl.types.js';
|
|
10
10
|
import { EN16931Validator } from '../../validation/en16931.validator.js';
|
|
11
|
+
import {
|
|
12
|
+
getPrecedingInvoiceFields,
|
|
13
|
+
type TPrecedingInvoiceReference,
|
|
14
|
+
} from '../../utils/preceding.invoice.js';
|
|
11
15
|
|
|
12
16
|
/**
|
|
13
17
|
* Decoder for XRechnung (UBL) format
|
|
@@ -23,7 +27,7 @@ export class XRechnungDecoder extends UBLBaseDecoder {
|
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
/**
|
|
26
|
-
* Decodes a UBL invoice document: an invoice, a debit note or a self-billed invoice
|
|
30
|
+
* Decodes a UBL invoice document: an invoice, a corrected invoice, a debit note or a self-billed invoice
|
|
27
31
|
* @param accountingDocType The type the document type code stands for
|
|
28
32
|
* @returns Promise resolving to the invoice document
|
|
29
33
|
*/
|
|
@@ -168,10 +172,11 @@ export class XRechnungDecoder extends UBLBaseDecoder {
|
|
|
168
172
|
const paymentTermsNote = this.getText('//cac:PaymentTerms/cbc:Note', this.doc);
|
|
169
173
|
const discountPercent = this.getText('//cac:PaymentTerms/cbc:SettlementDiscountPercent', this.doc);
|
|
170
174
|
|
|
171
|
-
// Extract period
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
const
|
|
175
|
+
// Extract the document level period (BG-14) and delivery date (BT-72); a line has
|
|
176
|
+
// an invoice period (BG-26) and a delivery of its own, which are not the document's
|
|
177
|
+
const periodStart = this.getText('/*/cac:InvoicePeriod/cbc:StartDate', this.doc);
|
|
178
|
+
const periodEnd = this.getText('/*/cac:InvoicePeriod/cbc:EndDate', this.doc);
|
|
179
|
+
const deliveryDate = this.getText('/*/cac:Delivery/cbc:ActualDeliveryDate', this.doc);
|
|
175
180
|
|
|
176
181
|
// Extract notes (excluding PaymentTerms notes)
|
|
177
182
|
const allNotes: string[] = [];
|
|
@@ -255,7 +260,18 @@ export class XRechnungDecoder extends UBLBaseDecoder {
|
|
|
255
260
|
reverseCharge: false,
|
|
256
261
|
currency: currencyCode as finance.TCurrency,
|
|
257
262
|
notes: notes,
|
|
258
|
-
objectActions: []
|
|
263
|
+
objectActions: [],
|
|
264
|
+
// the day and the period of supply as the document states them (BT-72, BG-14)
|
|
265
|
+
...(deliveryDate ? { deliveryDate: this.parseRequiredUblDate(deliveryDate) } : {}),
|
|
266
|
+
...(periodStart && periodEnd
|
|
267
|
+
? {
|
|
268
|
+
periodOfPerformance: {
|
|
269
|
+
from: this.parseRequiredUblDate(periodStart),
|
|
270
|
+
to: this.parseRequiredUblDate(periodEnd),
|
|
271
|
+
},
|
|
272
|
+
}
|
|
273
|
+
: {}),
|
|
274
|
+
...getPrecedingInvoiceFields(accountingDocType, this.extractPrecedingInvoiceReferences()),
|
|
259
275
|
};
|
|
260
276
|
|
|
261
277
|
if (hasBusinessReferences || hasPaymentInformation || hasDateInformation) {
|
|
@@ -281,6 +297,29 @@ export class XRechnungDecoder extends UBLBaseDecoder {
|
|
|
281
297
|
}
|
|
282
298
|
}
|
|
283
299
|
|
|
300
|
+
/**
|
|
301
|
+
* Reads the preceding invoice references (BG-3): the number (BT-25) and,
|
|
302
|
+
* when stated, the issue date (BT-26) of each.
|
|
303
|
+
*/
|
|
304
|
+
private extractPrecedingInvoiceReferences(): TPrecedingInvoiceReference[] {
|
|
305
|
+
const referenceNodes = this.select(
|
|
306
|
+
'./cac:BillingReference/cac:InvoiceDocumentReference',
|
|
307
|
+
this.doc.documentElement,
|
|
308
|
+
);
|
|
309
|
+
const references: TPrecedingInvoiceReference[] = [];
|
|
310
|
+
for (const referenceNode of Array.isArray(referenceNodes) ? referenceNodes : []) {
|
|
311
|
+
const documentId = this.getText('./cbc:ID', referenceNode).trim();
|
|
312
|
+
if (!documentId) {
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
const issueDate = this.getText('./cbc:IssueDate', referenceNode).trim();
|
|
316
|
+
references.push(
|
|
317
|
+
issueDate ? { documentId, issueDate: this.parseRequiredUblDate(issueDate) } : { documentId },
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
return references;
|
|
321
|
+
}
|
|
322
|
+
|
|
284
323
|
/**
|
|
285
324
|
* Extracts party information from XML
|
|
286
325
|
* @param partyPath XPath to the party element
|
|
@@ -25,7 +25,7 @@ export class XRechnungEncoder extends UBLEncoder {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* Encodes an invoice document (invoice, debit note or self-billed invoice) into XRechnung XML
|
|
28
|
+
* Encodes an invoice document (invoice, corrected invoice, debit note or self-billed invoice) into XRechnung XML
|
|
29
29
|
* @param invoice Invoice document to encode
|
|
30
30
|
* @returns XRechnung XML string
|
|
31
31
|
*/
|
|
@@ -114,6 +114,9 @@ export class XRechnungEncoder extends UBLEncoder {
|
|
|
114
114
|
|
|
115
115
|
// Enhance line items with metadata
|
|
116
116
|
this.enhanceLineItems(doc, invoice);
|
|
117
|
+
|
|
118
|
+
// the customizations above insert where they can; the schema order is restored last
|
|
119
|
+
this.orderRootElements(doc);
|
|
117
120
|
}
|
|
118
121
|
|
|
119
122
|
/**
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { EInvoiceFormatError } from '../../errors.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The date an encoder is about to write, as a `Date`. A value that is no
|
|
5
|
+
* finite timestamp, or lies outside the range of a JavaScript date, is refused
|
|
6
|
+
* with an `EInvoiceFormatError` naming the field: the encoders never write a
|
|
7
|
+
* date the document does not state, such as today in place of a missing one.
|
|
8
|
+
* @param timestamp The timestamp (milliseconds since the epoch) to write
|
|
9
|
+
* @param field The business term it is written as, for example `BT-2 invoice issue date`
|
|
10
|
+
* @param targetFormat The format being written
|
|
11
|
+
*/
|
|
12
|
+
export const getWritableDate = (timestamp: unknown, field: string, targetFormat: string): Date => {
|
|
13
|
+
const date = typeof timestamp === 'number' && Number.isFinite(timestamp) ? new Date(timestamp) : undefined;
|
|
14
|
+
if (!date || Number.isNaN(date.getTime())) {
|
|
15
|
+
throw new EInvoiceFormatError(`${field} is no valid date: ${String(timestamp)}`, {
|
|
16
|
+
targetFormat,
|
|
17
|
+
unsupportedFeatures: [field],
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return date;
|
|
21
|
+
};
|
|
@@ -7,6 +7,7 @@ import { EInvoiceFormatError } from '../../errors.js';
|
|
|
7
7
|
*/
|
|
8
8
|
const DOCUMENT_TYPE_CODES: Record<TAccountingDocType, string> = {
|
|
9
9
|
'invoice': '380',
|
|
10
|
+
'corrected-invoice': '384',
|
|
10
11
|
'creditnote': '381',
|
|
11
12
|
'debitnote': '383',
|
|
12
13
|
'self-billed-invoice': '389',
|
|
@@ -42,9 +43,9 @@ export const getDocumentTypeCode = (accountingDocType: TAccountingDocType): stri
|
|
|
42
43
|
};
|
|
43
44
|
|
|
44
45
|
/**
|
|
45
|
-
* The accounting document type of a decoded type code. 383 and 389 have
|
|
46
|
-
* type of their own; the remaining credit note related codes are credit
|
|
47
|
-
* notes; everything else is an invoice, which covers 380
|
|
46
|
+
* The accounting document type of a decoded type code. 383, 384 and 389 have
|
|
47
|
+
* a type of their own; the remaining credit note related codes are credit
|
|
48
|
+
* notes; everything else is an invoice, which covers 380 and the other
|
|
48
49
|
* invoice related codes (326, 386, 875, ...) the envelope has no name for.
|
|
49
50
|
* A missing or unlisted code stays an invoice as well: the document is an
|
|
50
51
|
* invoice document, and reporting the code is the validators' job (BR-04,
|
|
@@ -55,6 +56,9 @@ export const getAccountingDocType = (typeCode: string): TAccountingDocType => {
|
|
|
55
56
|
if (code === DOCUMENT_TYPE_CODES['debitnote']) {
|
|
56
57
|
return 'debitnote';
|
|
57
58
|
}
|
|
59
|
+
if (code === DOCUMENT_TYPE_CODES['corrected-invoice']) {
|
|
60
|
+
return 'corrected-invoice';
|
|
61
|
+
}
|
|
58
62
|
if (code === DOCUMENT_TYPE_CODES['self-billed-invoice']) {
|
|
59
63
|
return 'self-billed-invoice';
|
|
60
64
|
}
|