@fin.cx/einvoice 8.2.3 → 8.2.4
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/formats/cii/cii.encoder.d.ts +10 -0
- package/dist_ts/formats/cii/cii.encoder.js +39 -2
- package/dist_ts/formats/cii/cii.types.d.ts +11 -0
- package/dist_ts/formats/cii/cii.types.js +41 -1
- package/dist_ts/formats/cii/facturx/facturx.encoder.js +9 -3
- package/dist_ts/formats/cii/zugferd/zugferd.encoder.js +37 -30
- package/dist_ts/formats/ubl/generic/ubl.encoder.js +5 -3
- package/dist_ts/formats/utils/document.totals.js +5 -2
- package/dist_ts/formats/utils/number.text.d.ts +9 -0
- package/dist_ts/formats/utils/number.text.js +26 -0
- package/package.json +2 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/formats/cii/cii.encoder.ts +39 -1
- package/ts/formats/cii/cii.types.ts +41 -0
- package/ts/formats/cii/facturx/facturx.encoder.ts +10 -2
- package/ts/formats/cii/zugferd/zugferd.encoder.ts +42 -33
- package/ts/formats/ubl/generic/ubl.encoder.ts +4 -2
- package/ts/formats/utils/document.totals.ts +7 -1
- package/ts/formats/utils/number.text.ts +25 -0
|
@@ -7,6 +7,7 @@ import { getDocumentTypeCode } from '../../utils/document.typecode.js';
|
|
|
7
7
|
import { getWritableDate, getWritableDueDate } from '../../utils/date.value.js';
|
|
8
8
|
import { getPaymentTermsNote } from '../../utils/payment.terms.js';
|
|
9
9
|
import { computeDocumentTotals } from '../../utils/document.totals.js';
|
|
10
|
+
import { toPlainDecimalString } from '../../utils/number.text.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Encoder for ZUGFeRD invoice format
|
|
@@ -34,6 +35,9 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
34
35
|
this.addCommonInvoiceData(xmlDoc, creditNote);
|
|
35
36
|
|
|
36
37
|
// Serialize to string
|
|
38
|
+
// the schema order of every element, whatever order the passes above added them in
|
|
39
|
+
this.orderCiiElements(xmlDoc);
|
|
40
|
+
|
|
37
41
|
return new XMLSerializer().serializeToString(xmlDoc);
|
|
38
42
|
}
|
|
39
43
|
|
|
@@ -53,6 +57,9 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
53
57
|
this.addCommonInvoiceData(xmlDoc, invoice);
|
|
54
58
|
|
|
55
59
|
// Serialize to string
|
|
60
|
+
// the schema order of every element, whatever order the passes above added them in
|
|
61
|
+
this.orderCiiElements(xmlDoc);
|
|
62
|
+
|
|
56
63
|
return new XMLSerializer().serializeToString(xmlDoc);
|
|
57
64
|
}
|
|
58
65
|
|
|
@@ -219,14 +226,15 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
219
226
|
const sellerElement = doc.createElement('ram:SellerTradeParty');
|
|
220
227
|
this.addPartyInfo(doc, sellerElement, invoice.from);
|
|
221
228
|
|
|
222
|
-
//
|
|
229
|
+
// Seller electronic address (BT-34): ram:URIUniversalCommunication/ram:URIID of the party; a
|
|
230
|
+
// trade contact (ram:DefinedTradeContact) has no URIID
|
|
223
231
|
if (invoice.electronicAddress && invoice.from.type === 'company') {
|
|
224
|
-
const
|
|
232
|
+
const communicationElement = doc.createElement('ram:URIUniversalCommunication');
|
|
225
233
|
const uriElement = doc.createElement('ram:URIID');
|
|
226
234
|
uriElement.setAttribute('schemeID', invoice.electronicAddress.scheme);
|
|
227
235
|
uriElement.textContent = invoice.electronicAddress.value;
|
|
228
|
-
|
|
229
|
-
sellerElement.appendChild(
|
|
236
|
+
communicationElement.appendChild(uriElement);
|
|
237
|
+
sellerElement.appendChild(communicationElement);
|
|
230
238
|
}
|
|
231
239
|
|
|
232
240
|
agreementElement.appendChild(sellerElement);
|
|
@@ -334,33 +342,6 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
334
342
|
deliveryDateElement.appendChild(occurrenceDateElement);
|
|
335
343
|
deliveryElement.appendChild(deliveryDateElement);
|
|
336
344
|
}
|
|
337
|
-
|
|
338
|
-
// Add period of performance if available
|
|
339
|
-
if (invoice.periodOfPerformance) {
|
|
340
|
-
const periodElement = doc.createElement('ram:BillingSpecifiedPeriod');
|
|
341
|
-
|
|
342
|
-
// Start date
|
|
343
|
-
if (invoice.periodOfPerformance.from !== undefined) {
|
|
344
|
-
const startDateElement = doc.createElement('ram:StartDateTime');
|
|
345
|
-
const startDateStringElement = doc.createElement('udt:DateTimeString');
|
|
346
|
-
startDateStringElement.setAttribute('format', '102'); // YYYYMMDD format
|
|
347
|
-
startDateStringElement.textContent = this.formatDateYYYYMMDD(invoice.periodOfPerformance.from, 'BT-73 invoicing period start date');
|
|
348
|
-
startDateElement.appendChild(startDateStringElement);
|
|
349
|
-
periodElement.appendChild(startDateElement);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// End date
|
|
353
|
-
if (invoice.periodOfPerformance.to !== undefined) {
|
|
354
|
-
const endDateElement = doc.createElement('ram:EndDateTime');
|
|
355
|
-
const endDateStringElement = doc.createElement('udt:DateTimeString');
|
|
356
|
-
endDateStringElement.setAttribute('format', '102'); // YYYYMMDD format
|
|
357
|
-
endDateStringElement.textContent = this.formatDateYYYYMMDD(invoice.periodOfPerformance.to, 'BT-74 invoicing period end date');
|
|
358
|
-
endDateElement.appendChild(endDateStringElement);
|
|
359
|
-
periodElement.appendChild(endDateElement);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
deliveryElement.appendChild(periodElement);
|
|
363
|
-
}
|
|
364
345
|
}
|
|
365
346
|
|
|
366
347
|
/**
|
|
@@ -448,6 +429,33 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
448
429
|
// Add tax details
|
|
449
430
|
this.addTaxDetails(doc, settlementElement, invoice);
|
|
450
431
|
|
|
432
|
+
// Invoicing period (BG-14): in the header settlement, where the CII schema puts it
|
|
433
|
+
if (invoice.periodOfPerformance) {
|
|
434
|
+
const periodElement = doc.createElement('ram:BillingSpecifiedPeriod');
|
|
435
|
+
|
|
436
|
+
// Start date
|
|
437
|
+
if (invoice.periodOfPerformance.from !== undefined) {
|
|
438
|
+
const startDateElement = doc.createElement('ram:StartDateTime');
|
|
439
|
+
const startDateStringElement = doc.createElement('udt:DateTimeString');
|
|
440
|
+
startDateStringElement.setAttribute('format', '102'); // YYYYMMDD format
|
|
441
|
+
startDateStringElement.textContent = this.formatDateYYYYMMDD(invoice.periodOfPerformance.from, 'BT-73 invoicing period start date');
|
|
442
|
+
startDateElement.appendChild(startDateStringElement);
|
|
443
|
+
periodElement.appendChild(startDateElement);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// End date
|
|
447
|
+
if (invoice.periodOfPerformance.to !== undefined) {
|
|
448
|
+
const endDateElement = doc.createElement('ram:EndDateTime');
|
|
449
|
+
const endDateStringElement = doc.createElement('udt:DateTimeString');
|
|
450
|
+
endDateStringElement.setAttribute('format', '102'); // YYYYMMDD format
|
|
451
|
+
endDateStringElement.textContent = this.formatDateYYYYMMDD(invoice.periodOfPerformance.to, 'BT-74 invoicing period end date');
|
|
452
|
+
endDateElement.appendChild(endDateStringElement);
|
|
453
|
+
periodElement.appendChild(endDateElement);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
settlementElement.appendChild(periodElement);
|
|
457
|
+
}
|
|
458
|
+
|
|
451
459
|
// Add totals
|
|
452
460
|
this.addMonetarySummation(doc, settlementElement, invoice);
|
|
453
461
|
}
|
|
@@ -581,7 +589,8 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
581
589
|
const agreementElement = doc.createElement('ram:SpecifiedLineTradeAgreement');
|
|
582
590
|
const priceElement = doc.createElement('ram:NetPriceProductTradePrice');
|
|
583
591
|
const chargeAmountElement = doc.createElement('ram:ChargeAmount');
|
|
584
|
-
|
|
592
|
+
// Item net price (BT-146) with the precision it has; EN 16931 does not limit its decimals
|
|
593
|
+
chargeAmountElement.textContent = toPlainDecimalString(item.unitNetPrice);
|
|
585
594
|
priceElement.appendChild(chargeAmountElement);
|
|
586
595
|
agreementElement.appendChild(priceElement);
|
|
587
596
|
lineItemElement.appendChild(agreementElement);
|
|
@@ -589,7 +598,7 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
|
|
|
589
598
|
// Add delivery information (quantity)
|
|
590
599
|
const deliveryElement = doc.createElement('ram:SpecifiedLineTradeDelivery');
|
|
591
600
|
const quantityElement = doc.createElement('ram:BilledQuantity');
|
|
592
|
-
quantityElement.textContent = item.unitQuantity
|
|
601
|
+
quantityElement.textContent = toPlainDecimalString(item.unitQuantity); // BT-129
|
|
593
602
|
// a line without a unit is written without one, so that BR-23 reports it
|
|
594
603
|
if (item.unitType) {
|
|
595
604
|
quantityElement.setAttribute('unitCode', item.unitType);
|
|
@@ -7,6 +7,7 @@ import { getPrecedingInvoiceReferences } from '../../utils/preceding.invoice.js'
|
|
|
7
7
|
import { getWritableDueDate } from '../../utils/date.value.js';
|
|
8
8
|
import { getPaymentTermsNote } from '../../utils/payment.terms.js';
|
|
9
9
|
import { computeDocumentTotals } from '../../utils/document.totals.js';
|
|
10
|
+
import { toPlainDecimalString } from '../../utils/number.text.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* UBL Encoder implementation
|
|
@@ -495,7 +496,7 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
495
496
|
if (item.unitType) {
|
|
496
497
|
quantityElement.setAttribute('unitCode', item.unitType);
|
|
497
498
|
}
|
|
498
|
-
quantityElement.textContent = item.unitQuantity
|
|
499
|
+
quantityElement.textContent = toPlainDecimalString(item.unitQuantity); // BT-129
|
|
499
500
|
invoiceLineNode.appendChild(quantityElement);
|
|
500
501
|
|
|
501
502
|
// Invoice line net amount (BT-131): quantity × net price, rounded
|
|
@@ -542,7 +543,8 @@ export class UBLEncoder extends UBLBaseEncoder {
|
|
|
542
543
|
// Price amount
|
|
543
544
|
const priceAmountElement = doc.createElement('cbc:PriceAmount');
|
|
544
545
|
priceAmountElement.setAttribute('currencyID', invoice.currency);
|
|
545
|
-
|
|
546
|
+
// Item net price (BT-146) with the precision it has; EN 16931 does not limit its decimals
|
|
547
|
+
priceAmountElement.textContent = toPlainDecimalString(item.unitNetPrice);
|
|
546
548
|
priceNode.appendChild(priceAmountElement);
|
|
547
549
|
}
|
|
548
550
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { TAccountingDoc } from '../../interfaces/common.js';
|
|
2
2
|
import { Decimal } from './decimal.js';
|
|
3
3
|
import { DecimalCurrencyCalculator } from './currency.calculator.decimal.js';
|
|
4
|
+
import { toPlainDecimalString } from './number.text.js';
|
|
4
5
|
import { EInvoiceFormatError } from '../../errors.js';
|
|
5
6
|
import type { ValidationResult } from '../validation/validation.types.js';
|
|
6
7
|
import { getVatCategory, getVatExemption, type IVatExemption, type TVatCategoryCode } from './vat.category.js';
|
|
@@ -130,7 +131,12 @@ export const computeDocumentTotals = (
|
|
|
130
131
|
);
|
|
131
132
|
}
|
|
132
133
|
for (const item of accountingDoc.items ?? []) {
|
|
133
|
-
|
|
134
|
+
// quantity × net price from the numerals the encoders write, so the line net amount is the
|
|
135
|
+
// product of the written values, at their full precision
|
|
136
|
+
const lineNet = calculator.calculateLineNet(
|
|
137
|
+
toPlainDecimalString(item.unitQuantity),
|
|
138
|
+
toPlainDecimalString(item.unitNetPrice),
|
|
139
|
+
);
|
|
134
140
|
lineNetAmounts.push(lineNet);
|
|
135
141
|
lineVatCategories.push(category);
|
|
136
142
|
// grouped by VAT category and rate: the same rate under two categories is two groups
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A finite number as a plain decimal numeral (`xsd:decimal`), the shortest one
|
|
3
|
+
* that reads back as the same number, without an exponent: `0.335`, `1234.5`,
|
|
4
|
+
* `0.0000001` (not `1e-7`). Quantities (BT-129) and net prices (BT-146) are
|
|
5
|
+
* written this way, with the precision they have; EN 16931 limits neither to
|
|
6
|
+
* two decimals (BR-DEC-* cover amounts only).
|
|
7
|
+
* @param value A finite number
|
|
8
|
+
*/
|
|
9
|
+
export const toPlainDecimalString = (value: number): string => {
|
|
10
|
+
const text = String(value);
|
|
11
|
+
const match = /^(-?)(\d+)(?:\.(\d+))?e([+-]\d+)$/i.exec(text);
|
|
12
|
+
if (!match) {
|
|
13
|
+
return text;
|
|
14
|
+
}
|
|
15
|
+
const [, sign, integerDigits, fractionDigits = '', exponentText] = match;
|
|
16
|
+
const digits = integerDigits + fractionDigits;
|
|
17
|
+
const point = integerDigits.length + Number(exponentText);
|
|
18
|
+
if (point <= 0) {
|
|
19
|
+
return `${sign}0.${'0'.repeat(-point)}${digits}`;
|
|
20
|
+
}
|
|
21
|
+
if (point >= digits.length) {
|
|
22
|
+
return `${sign}${digits}${'0'.repeat(point - digits.length)}`;
|
|
23
|
+
}
|
|
24
|
+
return `${sign}${digits.slice(0, point)}.${digits.slice(point)}`;
|
|
25
|
+
};
|