@fin.cx/einvoice 8.0.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 +48 -3
- package/dist_ts/formats/base/base.encoder.d.ts +1 -1
- package/dist_ts/formats/base/base.validator.d.ts +5 -0
- package/dist_ts/formats/base/base.validator.js +28 -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/cii.validator.d.ts +11 -0
- package/dist_ts/formats/cii/cii.validator.js +35 -1
- package/dist_ts/formats/cii/facturx/facturx.decoder.d.ts +1 -1
- package/dist_ts/formats/cii/facturx/facturx.decoder.js +11 -5
- package/dist_ts/formats/cii/facturx/facturx.encoder.d.ts +2 -2
- package/dist_ts/formats/cii/facturx/facturx.encoder.js +17 -11
- package/dist_ts/formats/cii/facturx/facturx.validator.js +8 -4
- package/dist_ts/formats/cii/zugferd/zugferd.decoder.d.ts +1 -1
- package/dist_ts/formats/cii/zugferd/zugferd.decoder.js +11 -5
- package/dist_ts/formats/cii/zugferd/zugferd.encoder.d.ts +1 -1
- package/dist_ts/formats/cii/zugferd/zugferd.encoder.js +20 -14
- package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.d.ts +7 -1
- package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.js +29 -5
- package/dist_ts/formats/cii/zugferd/zugferd.validator.js +5 -4
- package/dist_ts/formats/converters/xml-to-einvoice.converter.d.ts +5 -0
- package/dist_ts/formats/converters/xml-to-einvoice.converter.js +11 -2
- package/dist_ts/formats/semantic/semantic.adapter.js +2 -2
- package/dist_ts/formats/ubl/en16931.ubl.validator.js +5 -1
- package/dist_ts/formats/ubl/generic/ubl.encoder.d.ts +23 -1
- package/dist_ts/formats/ubl/generic/ubl.encoder.js +71 -12
- 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.validator.d.ts +11 -0
- package/dist_ts/formats/ubl/ubl.validator.js +35 -1
- package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.d.ts +6 -1
- package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.js +39 -8
- 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/formats/utils/unit.codes.d.ts +40 -0
- package/dist_ts/formats/utils/unit.codes.js +193 -0
- package/dist_ts/formats/validation/codelist.validator.d.ts +1 -1
- package/dist_ts/formats/validation/codelist.validator.js +7 -9
- package/dist_ts/formats/validation/en16931.business-rules.validator.js +13 -13
- package/dist_ts/formats/validation/validation.types.d.ts +0 -4
- package/dist_ts/formats/validation/validation.types.js +1 -28
- package/dist_ts/index.d.ts +5 -1
- package/dist_ts/index.js +6 -1
- package/dist_ts/interfaces/common.d.ts +6 -2
- package/dist_ts_install/download-schematron.js +3 -5
- package/package.json +8 -8
- package/readme.md +103 -10
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/einvoice.ts +51 -2
- package/ts/formats/base/base.encoder.ts +1 -1
- package/ts/formats/base/base.validator.ts +28 -0
- 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/cii.validator.ts +47 -0
- package/ts/formats/cii/facturx/facturx.decoder.ts +11 -4
- package/ts/formats/cii/facturx/facturx.encoder.ts +17 -10
- package/ts/formats/cii/facturx/facturx.validator.ts +9 -3
- package/ts/formats/cii/zugferd/zugferd.decoder.ts +11 -4
- package/ts/formats/cii/zugferd/zugferd.encoder.ts +20 -13
- package/ts/formats/cii/zugferd/zugferd.v1.decoder.ts +32 -4
- package/ts/formats/cii/zugferd/zugferd.validator.ts +4 -3
- package/ts/formats/converters/xml-to-einvoice.converter.ts +11 -1
- package/ts/formats/semantic/semantic.adapter.ts +1 -1
- package/ts/formats/ubl/en16931.ubl.validator.ts +6 -0
- package/ts/formats/ubl/generic/ubl.encoder.ts +78 -11
- package/ts/formats/ubl/ubl.decoder.ts +1 -1
- package/ts/formats/ubl/ubl.encoder.ts +77 -13
- package/ts/formats/ubl/ubl.validator.ts +47 -0
- package/ts/formats/ubl/xrechnung/xrechnung.decoder.ts +47 -7
- 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/formats/utils/unit.codes.ts +215 -0
- package/ts/formats/validation/codelist.validator.ts +8 -19
- package/ts/formats/validation/en16931.business-rules.validator.ts +13 -13
- package/ts/formats/validation/validation.types.ts +0 -28
- package/ts/index.ts +12 -0
- package/ts/interfaces/common.ts +6 -1
package/ts/einvoice.ts
CHANGED
|
@@ -32,6 +32,7 @@ import { ValidatorFactory } from './formats/factories/validator.factory.js';
|
|
|
32
32
|
// Import format detector
|
|
33
33
|
import { FormatDetector } from './formats/utils/format.detector.js';
|
|
34
34
|
import { isAccountingDocType } from './formats/utils/document.typecode.js';
|
|
35
|
+
import { meetsUnitCodeRule } from './formats/utils/unit.codes.js';
|
|
35
36
|
|
|
36
37
|
import {
|
|
37
38
|
FACTURX_ATTACHMENT_DESCRIPTION,
|
|
@@ -55,7 +56,8 @@ import type { IEInvoiceMetadata } from './interfaces/en16931-metadata.js';
|
|
|
55
56
|
* Main class for working with electronic invoices.
|
|
56
57
|
* Supports various invoice formats including Factur-X, ZUGFeRD, UBL, and XRechnung
|
|
57
58
|
* Implements the tsclass accounting document envelope: one class holds an invoice,
|
|
58
|
-
* a credit note, a debit note or a self-billed invoice, told
|
|
59
|
+
* a corrected invoice, a credit note, a debit note or a self-billed invoice, told
|
|
60
|
+
* apart by `accountingDocType`
|
|
59
61
|
*/
|
|
60
62
|
export class EInvoice implements TAccountingDocEnvelope<TAccountingDocType, {}> {
|
|
61
63
|
private static sharedPdfEmbedder?: import('./formats/pdf/pdf.embedder.js').PDFEmbedder;
|
|
@@ -151,6 +153,20 @@ export class EInvoice implements TAccountingDocEnvelope<TAccountingDocType, {}>
|
|
|
151
153
|
documentId: string;
|
|
152
154
|
issueDate?: number;
|
|
153
155
|
}>;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* On a corrected invoice (type code 384): the invoice it corrects, written
|
|
159
|
+
* as the first preceding invoice reference (BG-3). A received corrected
|
|
160
|
+
* invoice may lack it.
|
|
161
|
+
*/
|
|
162
|
+
public correctedInvoice?: finance.TCorrectedInvoiceReference;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* On a corrected invoice: the details it supplies or corrects, as the
|
|
166
|
+
* issuer states them. EN 16931 has no element for them, so they are not
|
|
167
|
+
* written to the XML; an issuer states them as a note in `notes`.
|
|
168
|
+
*/
|
|
169
|
+
public corrections?: finance.TCorrectedInvoice['corrections'];
|
|
154
170
|
public printResult?: {
|
|
155
171
|
pdfBufferString: string;
|
|
156
172
|
totalNet: number;
|
|
@@ -446,6 +462,8 @@ export class EInvoice implements TAccountingDocEnvelope<TAccountingDocType, {}>
|
|
|
446
462
|
electronicAddress: this.electronicAddress,
|
|
447
463
|
paymentOptions: this.paymentOptions,
|
|
448
464
|
relatedDocuments: this.relatedDocuments,
|
|
465
|
+
correctedInvoice: this.correctedInvoice,
|
|
466
|
+
corrections: this.corrections,
|
|
449
467
|
printResult: this.printResult
|
|
450
468
|
};
|
|
451
469
|
|
|
@@ -517,6 +535,14 @@ export class EInvoice implements TAccountingDocEnvelope<TAccountingDocType, {}>
|
|
|
517
535
|
result = this.validateDecodedInvoice(level);
|
|
518
536
|
}
|
|
519
537
|
|
|
538
|
+
// At the business level the document check covers the unit rules (BR-23, BR-CL-23) on every
|
|
539
|
+
// line. The feature flag validators below check the lines again on the decoded model, which
|
|
540
|
+
// cannot tell an absent unit from an empty unitCode="" attribute, so there they would report
|
|
541
|
+
// a second, possibly different rule for the same line. Below the business level the flags
|
|
542
|
+
// are the only unit checks and report them.
|
|
543
|
+
const isDocumentUnitRule = (ruleId: string): boolean =>
|
|
544
|
+
level === ValidationLevel.BUSINESS && (ruleId === 'BR-23' || ruleId === 'BR-CL-23');
|
|
545
|
+
|
|
520
546
|
// Enhanced validation with feature flags
|
|
521
547
|
if (options?.featureFlags?.includes('EN16931_BUSINESS_RULES')) {
|
|
522
548
|
const businessRulesValidator = new EN16931BusinessRulesValidator();
|
|
@@ -526,6 +552,7 @@ export class EInvoice implements TAccountingDocEnvelope<TAccountingDocType, {}>
|
|
|
526
552
|
result.errors = result.errors.concat(
|
|
527
553
|
businessResults
|
|
528
554
|
.filter(r => r.severity === 'error')
|
|
555
|
+
.filter(r => !isDocumentUnitRule(r.ruleId))
|
|
529
556
|
.map(r => ({ code: r.ruleId, message: r.message, field: r.field }))
|
|
530
557
|
);
|
|
531
558
|
|
|
@@ -548,6 +575,7 @@ export class EInvoice implements TAccountingDocEnvelope<TAccountingDocType, {}>
|
|
|
548
575
|
result.errors = result.errors.concat(
|
|
549
576
|
codeListResults
|
|
550
577
|
.filter(r => r.severity === 'error')
|
|
578
|
+
.filter(r => !isDocumentUnitRule(r.ruleId))
|
|
551
579
|
.map(r => ({ code: r.ruleId, message: r.message, field: r.field }))
|
|
552
580
|
);
|
|
553
581
|
}
|
|
@@ -576,6 +604,27 @@ export class EInvoice implements TAccountingDocEnvelope<TAccountingDocType, {}>
|
|
|
576
604
|
this.createValidationError(message)
|
|
577
605
|
);
|
|
578
606
|
|
|
607
|
+
if (level === ValidationLevel.BUSINESS) {
|
|
608
|
+
// BR-23 and BR-CL-23: every line has a unit code, and it is a code of UN/ECE Recommendation 20
|
|
609
|
+
// with the Recommendation 21 extension, as the XML validators check it on an exported document
|
|
610
|
+
(invoice.items ?? []).forEach((item, index) => {
|
|
611
|
+
if (!item.unitType) {
|
|
612
|
+
// BR-23: the line has no unit code at all
|
|
613
|
+
errors.push({
|
|
614
|
+
code: 'BR-23',
|
|
615
|
+
message: 'An Invoice line (BG-25) shall have an Invoiced quantity unit of measure code (BT-130)',
|
|
616
|
+
location: `items[${index}].unitType`
|
|
617
|
+
});
|
|
618
|
+
} else if (!meetsUnitCodeRule(item.unitType)) {
|
|
619
|
+
errors.push({
|
|
620
|
+
code: 'BR-CL-23',
|
|
621
|
+
message: `Unit code MUST be coded according to the UN/ECE Recommendation 20 with Rec 21 extension; found "${item.unitType}"`,
|
|
622
|
+
location: `items[${index}].unitType`
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
|
|
579
628
|
return {
|
|
580
629
|
valid: errors.length === 0,
|
|
581
630
|
errors,
|
|
@@ -793,7 +842,7 @@ export class EInvoice implements TAccountingDocEnvelope<TAccountingDocType, {}>
|
|
|
793
842
|
position: data.position || this.items.length + 1,
|
|
794
843
|
name: data.name || '',
|
|
795
844
|
articleNumber: data.articleNumber,
|
|
796
|
-
unitType: data.unitType || '
|
|
845
|
+
unitType: data.unitType || 'C62',
|
|
797
846
|
unitQuantity: data.unitQuantity || 1,
|
|
798
847
|
unitNetPrice: data.unitNetPrice || 0,
|
|
799
848
|
vatPercentage: data.vatPercentage || 0
|
|
@@ -6,7 +6,7 @@ import type { TAccountingDoc } from '../../interfaces/common.js';
|
|
|
6
6
|
*/
|
|
7
7
|
export abstract class BaseEncoder {
|
|
8
8
|
/**
|
|
9
|
-
* Encodes an accounting document (invoice, credit note, debit note or self-billed invoice) into XML
|
|
9
|
+
* Encodes an accounting document (invoice, corrected invoice, credit note, debit note or self-billed invoice) into XML
|
|
10
10
|
* @param invoice Accounting document to encode
|
|
11
11
|
* @returns XML string
|
|
12
12
|
*/
|
|
@@ -61,4 +61,32 @@ export abstract class BaseValidator {
|
|
|
61
61
|
location
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The path of an element in the document, with the position of each step among its siblings
|
|
67
|
+
* of the same name, for example `/Invoice/cac:InvoiceLine[2]/cbc:InvoicedQuantity`.
|
|
68
|
+
*/
|
|
69
|
+
protected locationOf(element: Element): string {
|
|
70
|
+
const steps: string[] = [];
|
|
71
|
+
let current: Element | null = element;
|
|
72
|
+
while (current) {
|
|
73
|
+
const name = current.nodeName;
|
|
74
|
+
let position = 1;
|
|
75
|
+
let count = 0;
|
|
76
|
+
const parent: Node | null = current.parentNode;
|
|
77
|
+
if (parent) {
|
|
78
|
+
for (let child = parent.firstChild; child; child = child.nextSibling) {
|
|
79
|
+
if (child.nodeType === 1 && child.nodeName === name) {
|
|
80
|
+
count++;
|
|
81
|
+
if (child === current) {
|
|
82
|
+
position = count;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
steps.unshift(count > 1 ? `${name}[${position}]` : name);
|
|
88
|
+
current = parent && parent.nodeType === 1 ? parent as Element : null;
|
|
89
|
+
}
|
|
90
|
+
return `/${steps.join('/')}`;
|
|
91
|
+
}
|
|
64
92
|
}
|
|
@@ -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,46 @@ 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 actual delivery date (BT-72) of the header delivery; undefined
|
|
132
|
+
* when the document does not state one.
|
|
133
|
+
*/
|
|
134
|
+
protected extractDeliveryDate(): number | undefined {
|
|
135
|
+
const deliveryDatePath =
|
|
136
|
+
'/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeDelivery/ram:ActualDeliverySupplyChainEvent/ram:OccurrenceDateTime/udt:DateTimeString';
|
|
137
|
+
const deliveryDate = this.getText(deliveryDatePath).trim();
|
|
138
|
+
if (!deliveryDate) {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
return this.parseRequiredCIIDate(deliveryDate, this.getText(`${deliveryDatePath}/@format`).trim());
|
|
142
|
+
}
|
|
143
|
+
|
|
102
144
|
/**
|
|
103
145
|
* Gets a text value from an XPath expression
|
|
104
146
|
* @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
|
|
@@ -3,6 +3,7 @@ import { ValidationLevel } from '../../interfaces/common.js';
|
|
|
3
3
|
import type { ValidationResult } from '../../interfaces/common.js';
|
|
4
4
|
import { CII_NAMESPACES, CIIProfile } from './cii.types.js';
|
|
5
5
|
import { DOMParser, xpath } from '../../plugins.js';
|
|
6
|
+
import { meetsUnitCodeRule } from '../utils/unit.codes.js';
|
|
6
7
|
|
|
7
8
|
const ciiValidatorParser = new DOMParser();
|
|
8
9
|
const ciiValidatorNamespaces = {
|
|
@@ -113,6 +114,52 @@ export abstract class CIIBaseValidator extends BaseValidator {
|
|
|
113
114
|
*/
|
|
114
115
|
protected abstract validateStructure(): boolean;
|
|
115
116
|
|
|
117
|
+
/**
|
|
118
|
+
* BR-23: every invoice line has a unit code on its billed quantity.
|
|
119
|
+
* @returns True if every line has one
|
|
120
|
+
*/
|
|
121
|
+
protected validateLineUnitCodePresence(): boolean {
|
|
122
|
+
// BR-23: every invoice line (BG-25) has an invoiced quantity unit of measure code (BT-130)
|
|
123
|
+
const lines = this.select('//ram:IncludedSupplyChainTradeLineItem', this.doc) as Element[];
|
|
124
|
+
let valid = true;
|
|
125
|
+
for (const line of lines) {
|
|
126
|
+
if (!this.exists('./ram:SpecifiedLineTradeDelivery/ram:BilledQuantity/@unitCode', line)) {
|
|
127
|
+
this.addError(
|
|
128
|
+
'BR-23',
|
|
129
|
+
'An Invoice line (BG-25) shall have an Invoiced quantity unit of measure code (BT-130)',
|
|
130
|
+
this.locationOf(line)
|
|
131
|
+
);
|
|
132
|
+
valid = false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return valid;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* BR-CL-23: every unit code of a billed or basis quantity (BT-130, BT-150) is a code of
|
|
140
|
+
* UN/ECE Recommendation 20 with the Recommendation 21 extension.
|
|
141
|
+
* @returns True if every unit code passed
|
|
142
|
+
*/
|
|
143
|
+
protected validateUnitCodes(): boolean {
|
|
144
|
+
const quantities = this.select(
|
|
145
|
+
'//ram:BilledQuantity[@unitCode] | //ram:BasisQuantity[@unitCode]',
|
|
146
|
+
this.doc
|
|
147
|
+
) as Element[];
|
|
148
|
+
let valid = true;
|
|
149
|
+
for (const quantity of quantities) {
|
|
150
|
+
const unitCode = quantity.getAttribute('unitCode') ?? '';
|
|
151
|
+
if (!meetsUnitCodeRule(unitCode)) {
|
|
152
|
+
this.addError(
|
|
153
|
+
'BR-CL-23',
|
|
154
|
+
`Unit code MUST be coded according to the UN/ECE Recommendation 20 with Rec 21 extension; found "${unitCode}"`,
|
|
155
|
+
`${this.locationOf(quantity)}/@unitCode`
|
|
156
|
+
);
|
|
157
|
+
valid = false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return valid;
|
|
161
|
+
}
|
|
162
|
+
|
|
116
163
|
/**
|
|
117
164
|
* Detects the CII profile from the XML
|
|
118
165
|
*/
|
|
@@ -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
|
*/
|
|
@@ -92,6 +93,9 @@ export class FacturXDecoder extends CIIBaseDecoder {
|
|
|
92
93
|
notes = allNotes.slice(1); // Remove subject from notes
|
|
93
94
|
}
|
|
94
95
|
|
|
96
|
+
// Extract the actual delivery date, if stated
|
|
97
|
+
const deliveryDate = this.extractDeliveryDate();
|
|
98
|
+
|
|
95
99
|
// Check for reverse charge
|
|
96
100
|
const reverseCharge = this.exists('//ram:SpecifiedTradeAllowanceCharge/ram:ReasonCode[text()="62"]');
|
|
97
101
|
|
|
@@ -119,8 +123,10 @@ export class FacturXDecoder extends CIIBaseDecoder {
|
|
|
119
123
|
reverseCharge: reverseCharge,
|
|
120
124
|
currency: currencyCode as finance.TCurrency,
|
|
121
125
|
notes: notes,
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
// the day of supply as the document states it (BT-72)
|
|
127
|
+
...(deliveryDate === undefined ? {} : { deliveryDate }),
|
|
128
|
+
objectActions: [],
|
|
129
|
+
...getPrecedingInvoiceFields(accountingDocType, this.extractPrecedingInvoiceReferences()),
|
|
124
130
|
};
|
|
125
131
|
|
|
126
132
|
// Validate mandatory EN16931 fields unless validation is skipped
|
|
@@ -191,7 +197,8 @@ export class FacturXDecoder extends CIIBaseDecoder {
|
|
|
191
197
|
const name = this.getText('ram:SpecifiedTradeProduct/ram:Name', itemNode);
|
|
192
198
|
const articleNumber = this.getText('ram:SpecifiedTradeProduct/ram:SellerAssignedID', itemNode);
|
|
193
199
|
const unitQuantity = this.getNumber('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity', itemNode);
|
|
194
|
-
|
|
200
|
+
// an absent unit code (BT-130) stays absent, so that BR-23 reports it
|
|
201
|
+
const unitType = this.getText('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity/@unitCode', itemNode);
|
|
195
202
|
const unitNetPrice = this.getNumber('ram:SpecifiedLineTradeAgreement/ram:NetPriceProductTradePrice/ram:ChargeAmount', itemNode);
|
|
196
203
|
const vatPercentage = this.getNumber('ram:SpecifiedLineTradeSettlement/ram:ApplicableTradeTax/ram:RateApplicablePercent', itemNode);
|
|
197
204
|
|
|
@@ -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 } 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);
|
|
@@ -324,7 +328,7 @@ export class FacturXEncoder extends CIIBaseEncoder {
|
|
|
324
328
|
const dueDate = new Date(invoice.date);
|
|
325
329
|
dueDate.setDate(dueDate.getDate() + invoice.dueInDays);
|
|
326
330
|
|
|
327
|
-
dateStringElement.textContent = this.formatDateYYYYMMDD(dueDate.getTime());
|
|
331
|
+
dateStringElement.textContent = this.formatDateYYYYMMDD(dueDate.getTime(), 'BT-9 payment due date');
|
|
328
332
|
dueDateElement.appendChild(dateStringElement);
|
|
329
333
|
paymentTermsElement.appendChild(dueDateElement);
|
|
330
334
|
|
|
@@ -431,7 +435,10 @@ export class FacturXEncoder extends CIIBaseEncoder {
|
|
|
431
435
|
const deliveryElement = doc.createElement('ram:SpecifiedLineTradeDelivery');
|
|
432
436
|
const quantityElement = doc.createElement('ram:BilledQuantity');
|
|
433
437
|
quantityElement.textContent = item.unitQuantity.toString();
|
|
434
|
-
|
|
438
|
+
// a line without a unit is written without one, so that BR-23 reports it
|
|
439
|
+
if (item.unitType) {
|
|
440
|
+
quantityElement.setAttribute('unitCode', item.unitType);
|
|
441
|
+
}
|
|
435
442
|
deliveryElement.appendChild(quantityElement);
|
|
436
443
|
lineItemElement.appendChild(deliveryElement);
|
|
437
444
|
|
|
@@ -484,8 +491,8 @@ export class FacturXEncoder extends CIIBaseEncoder {
|
|
|
484
491
|
* @param timestamp Timestamp to format
|
|
485
492
|
* @returns Formatted date string
|
|
486
493
|
*/
|
|
487
|
-
private formatDateYYYYMMDD(timestamp: number): string {
|
|
488
|
-
const date =
|
|
494
|
+
private formatDateYYYYMMDD(timestamp: number, field: string): string {
|
|
495
|
+
const date = getWritableDate(timestamp, field, 'cii');
|
|
489
496
|
const year = date.getFullYear();
|
|
490
497
|
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
491
498
|
const day = date.getDate().toString().padStart(2, '0');
|
|
@@ -24,14 +24,14 @@ export class FacturXValidator extends CIIBaseValidator {
|
|
|
24
24
|
];
|
|
25
25
|
|
|
26
26
|
for (const section of sections) {
|
|
27
|
-
if (!this.exists(section)) {
|
|
27
|
+
if (!this.exists(`/rsm:CrossIndustryInvoice/${section}`)) {
|
|
28
28
|
this.addError('FX-STRUCT-1', `Required section ${section} is missing`, '/rsm:CrossIndustryInvoice');
|
|
29
29
|
valid = false;
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// Check for SupplyChainTradeTransaction sections
|
|
34
|
-
if (this.exists('rsm:SupplyChainTradeTransaction')) {
|
|
34
|
+
if (this.exists('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction')) {
|
|
35
35
|
const tradeSubsections = [
|
|
36
36
|
'ram:ApplicableHeaderTradeAgreement',
|
|
37
37
|
'ram:ApplicableHeaderTradeDelivery',
|
|
@@ -39,7 +39,7 @@ export class FacturXValidator extends CIIBaseValidator {
|
|
|
39
39
|
];
|
|
40
40
|
|
|
41
41
|
for (const subsection of tradeSubsections) {
|
|
42
|
-
if (!this.exists(
|
|
42
|
+
if (!this.exists(`/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/${subsection}`)) {
|
|
43
43
|
this.addError('FX-STRUCT-2', `Required subsection ${subsection} is missing`,
|
|
44
44
|
'/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction');
|
|
45
45
|
valid = false;
|
|
@@ -70,6 +70,12 @@ export class FacturXValidator extends CIIBaseValidator {
|
|
|
70
70
|
// and/or the Seller tax representative VAT identifier (BT-63).
|
|
71
71
|
valid = this.validateSellerVatIdentifier() && valid;
|
|
72
72
|
|
|
73
|
+
// BR-23: every line has a unit code
|
|
74
|
+
valid = this.validateLineUnitCodePresence() && valid;
|
|
75
|
+
|
|
76
|
+
// BR-CL-23: unit codes of UN/ECE Recommendation 20 with the Recommendation 21 extension
|
|
77
|
+
valid = this.validateUnitCodes() && valid;
|
|
78
|
+
|
|
73
79
|
return valid;
|
|
74
80
|
}
|
|
75
81
|
|
|
@@ -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
|
|
@@ -195,7 +201,8 @@ export class ZUGFeRDDecoder extends CIIBaseDecoder {
|
|
|
195
201
|
const name = this.getText('ram:SpecifiedTradeProduct/ram:Name', itemNode);
|
|
196
202
|
const articleNumber = this.getText('ram:SpecifiedTradeProduct/ram:SellerAssignedID', itemNode);
|
|
197
203
|
const unitQuantity = this.getNumber('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity', itemNode);
|
|
198
|
-
|
|
204
|
+
// an absent unit code (BT-130) stays absent, so that BR-23 reports it
|
|
205
|
+
const unitType = this.getText('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity/@unitCode', itemNode);
|
|
199
206
|
const unitNetPrice = this.getNumber('ram:SpecifiedLineTradeAgreement/ram:NetPriceProductTradePrice/ram:ChargeAmount', itemNode);
|
|
200
207
|
const vatPercentage = this.getNumber('ram:SpecifiedLineTradeSettlement/ram:ApplicableTradeTax/ram:RateApplicablePercent', itemNode);
|
|
201
208
|
|