@stackforge-eu/factur-x 1.4.0 → 1.4.2

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/CHANGELOG.md CHANGED
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.4.2] — 2026-09-25
11
+
12
+ - **Fixed the embedded XML MIME type back to `text/xml`.** 1.2.1 switched the
13
+ `factur-x.xml` filespec `/Subtype` to `application/xml` (#7), but the
14
+ Factur-X specification requires `text/xml`, and the official FNFE-MPE
15
+ validator rejects anything else. PDFs generated with 1.2.1 through 1.4.1
16
+ should be regenerated. A test now pins the value (#19).
17
+
18
+ ## [1.4.1] — 2026-09-14
19
+
20
+ - **Added the rounding amount (BT-114).** A new optional `roundingAmount`
21
+ field on `InvoiceTotalsInput` emits `ram:RoundingAmount` between the tax
22
+ total and the grand total, so an invoice can state the amount that takes
23
+ the grand total (BT-112) to the payable figure (BT-115). Emitted for
24
+ EN 16931 and EXTENDED only, as the lower-profile XSDs do not define the
25
+ element. `validateInput` now also checks BR-CO-16
26
+ (`duePayableAmount = grandTotal - prepaidAmount + roundingAmount`), which
27
+ the schematron already enforced. Omitting the field keeps the output
28
+ byte-for-byte identical (#17).
29
+
10
30
  ## [1.4.0] — 2026-09-03
11
31
 
12
32
  - **Added the item price discount (BT-147).** A new optional `priceDiscount`
@@ -245,6 +265,8 @@ This element is not expected`. Reordered the three blocks in
245
265
  - Deno compatibility test suite
246
266
 
247
267
  [Unreleased]: https://github.com/StackForge-EU/factur-x/commits/main
268
+ [1.4.2]: https://github.com/StackForge-EU/factur-x/compare/v1.4.1...v1.4.2
269
+ [1.4.1]: https://github.com/StackForge-EU/factur-x/compare/v1.4.0...v1.4.1
248
270
  [1.4.0]: https://github.com/StackForge-EU/factur-x/compare/v1.3.1...v1.4.0
249
271
  [1.3.1]: https://github.com/StackForge-EU/factur-x/compare/v1.3.0...v1.3.1
250
272
  [1.3.0]: https://github.com/StackForge-EU/factur-x/compare/v1.2.1...v1.3.0
package/dist/index.cjs CHANGED
@@ -496,6 +496,8 @@ function buildMonetarySummation(t, profile) {
496
496
  x += tag("ram:TaxTotalAmount", fmtAmt(t.taxTotal), {
497
497
  currencyID: t.currency
498
498
  });
499
+ if (atLeast(profile, "EN16931" /* EN16931 */) && t.roundingAmount !== void 0)
500
+ x += tag("ram:RoundingAmount", fmtAmt(t.roundingAmount));
499
501
  x += tag("ram:GrandTotalAmount", fmtAmt(t.grandTotal));
500
502
  if (atLeast(profile, "BASIC_WL" /* BASIC_WL */) && t.prepaidAmount !== void 0)
501
503
  x += tag("ram:TotalPrepaidAmount", fmtAmt(t.prepaidAmount));
@@ -936,6 +938,22 @@ function validateInput(input, profile, flavor) {
936
938
  }
937
939
  }
938
940
  }
941
+ if (req("BASIC_WL" /* BASIC_WL */) && input.totals) {
942
+ const t = input.totals;
943
+ if (t.grandTotal != null && t.duePayableAmount != null) {
944
+ const prepaid = t.prepaidAmount ?? 0;
945
+ const rounding = t.roundingAmount ?? 0;
946
+ const expected = round2(t.grandTotal - prepaid + rounding);
947
+ if (Math.abs(round2(t.duePayableAmount) - expected) > 0.01) {
948
+ addError(
949
+ errors,
950
+ "totals.duePayableAmount",
951
+ `duePayableAmount ${t.duePayableAmount} does not equal grandTotal - prepaidAmount + roundingAmount (${expected}) (BR-CO-16).`,
952
+ "BASIC_WL" /* BASIC_WL */
953
+ );
954
+ }
955
+ }
956
+ }
939
957
  if (req("BASIC_WL" /* BASIC_WL */) && input.totals) {
940
958
  const t = input.totals;
941
959
  if (t.lineTotal != null && t.taxBasisTotal != null) {
@@ -1424,7 +1442,8 @@ async function embedFacturX(options) {
1424
1442
  const xmlBytes = new TextEncoder().encode(xml);
1425
1443
  const afRel = resolveAfRelationship(profile, options.afRelationship);
1426
1444
  await pdfDoc.attach(xmlBytes, flavorConfig.attachmentFilename, {
1427
- mimeType: "application/xml",
1445
+ // Factur-X mandates text/xml on the filespec; the FNFE validator rejects application/xml.
1446
+ mimeType: "text/xml",
1428
1447
  afRelationship: afRel,
1429
1448
  description: "Factur-X Invoice"
1430
1449
  });
package/dist/index.d.cts CHANGED
@@ -859,6 +859,14 @@ interface InvoiceTotalsInput {
859
859
  * @see EN 16931 BT-113
860
860
  */
861
861
  prepaidAmount?: number;
862
+ /**
863
+ * Rounding amount — the amount added to the grand total (BT-112) to round
864
+ * the amount due (BT-115) to a payable figure, e.g. `0.02` to take
865
+ * `29.95` to `29.97`. Emitted as `ram:RoundingAmount` for EN 16931 and
866
+ * EXTENDED only; the BASIC / BASIC WL XSDs do not define the element.
867
+ * @see EN 16931 BT-114, BR-CO-16
868
+ */
869
+ roundingAmount?: number;
862
870
  /**
863
871
  * Due payable amount.
864
872
  * @see EN 16931 BT-115
package/dist/index.d.ts CHANGED
@@ -859,6 +859,14 @@ interface InvoiceTotalsInput {
859
859
  * @see EN 16931 BT-113
860
860
  */
861
861
  prepaidAmount?: number;
862
+ /**
863
+ * Rounding amount — the amount added to the grand total (BT-112) to round
864
+ * the amount due (BT-115) to a payable figure, e.g. `0.02` to take
865
+ * `29.95` to `29.97`. Emitted as `ram:RoundingAmount` for EN 16931 and
866
+ * EXTENDED only; the BASIC / BASIC WL XSDs do not define the element.
867
+ * @see EN 16931 BT-114, BR-CO-16
868
+ */
869
+ roundingAmount?: number;
862
870
  /**
863
871
  * Due payable amount.
864
872
  * @see EN 16931 BT-115
package/dist/index.js CHANGED
@@ -466,6 +466,8 @@ function buildMonetarySummation(t, profile) {
466
466
  x += tag("ram:TaxTotalAmount", fmtAmt(t.taxTotal), {
467
467
  currencyID: t.currency
468
468
  });
469
+ if (atLeast(profile, "EN16931" /* EN16931 */) && t.roundingAmount !== void 0)
470
+ x += tag("ram:RoundingAmount", fmtAmt(t.roundingAmount));
469
471
  x += tag("ram:GrandTotalAmount", fmtAmt(t.grandTotal));
470
472
  if (atLeast(profile, "BASIC_WL" /* BASIC_WL */) && t.prepaidAmount !== void 0)
471
473
  x += tag("ram:TotalPrepaidAmount", fmtAmt(t.prepaidAmount));
@@ -906,6 +908,22 @@ function validateInput(input, profile, flavor) {
906
908
  }
907
909
  }
908
910
  }
911
+ if (req("BASIC_WL" /* BASIC_WL */) && input.totals) {
912
+ const t = input.totals;
913
+ if (t.grandTotal != null && t.duePayableAmount != null) {
914
+ const prepaid = t.prepaidAmount ?? 0;
915
+ const rounding = t.roundingAmount ?? 0;
916
+ const expected = round2(t.grandTotal - prepaid + rounding);
917
+ if (Math.abs(round2(t.duePayableAmount) - expected) > 0.01) {
918
+ addError(
919
+ errors,
920
+ "totals.duePayableAmount",
921
+ `duePayableAmount ${t.duePayableAmount} does not equal grandTotal - prepaidAmount + roundingAmount (${expected}) (BR-CO-16).`,
922
+ "BASIC_WL" /* BASIC_WL */
923
+ );
924
+ }
925
+ }
926
+ }
909
927
  if (req("BASIC_WL" /* BASIC_WL */) && input.totals) {
910
928
  const t = input.totals;
911
929
  if (t.lineTotal != null && t.taxBasisTotal != null) {
@@ -1394,7 +1412,8 @@ async function embedFacturX(options) {
1394
1412
  const xmlBytes = new TextEncoder().encode(xml);
1395
1413
  const afRel = resolveAfRelationship(profile, options.afRelationship);
1396
1414
  await pdfDoc.attach(xmlBytes, flavorConfig.attachmentFilename, {
1397
- mimeType: "application/xml",
1415
+ // Factur-X mandates text/xml on the filespec; the FNFE validator rejects application/xml.
1416
+ mimeType: "text/xml",
1398
1417
  afRelationship: afRel,
1399
1418
  description: "Factur-X Invoice"
1400
1419
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackforge-eu/factur-x",
3
- "version": "1.4.0",
3
+ "version": "1.4.2",
4
4
  "type": "module",
5
5
  "description": "Embed Factur-X, ZUGFeRD, and XRechnung structured invoice XML into PDF/A-3 documents",
6
6
  "keywords": [