@stackforge-eu/factur-x 1.4.3 → 1.4.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/CHANGELOG.md +18 -0
- package/dist/index.cjs +37 -21
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +37 -21
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.4.4] — 2026-11-09
|
|
11
|
+
|
|
12
|
+
- **Fixed header reference element order.** `buildXml` emitted
|
|
13
|
+
`input.references` in input order, so an `order` reference listed before a
|
|
14
|
+
`seller-order` (or `contract`) reference produced XML that failed XSD
|
|
15
|
+
validation. References are now emitted in the CII sequence (SellerOrder →
|
|
16
|
+
BuyerOrder → Contract → SpecifiedProcuringProject); input order is only
|
|
17
|
+
kept among references of the same type. Reported by @SCPAU100 (#23).
|
|
18
|
+
- **Fixed `project` references failing XSD validation.** `SpecifiedProcuringProject`
|
|
19
|
+
requires a `ram:Name` child that was never emitted. A new optional `name`
|
|
20
|
+
field on `DocumentReferenceInput` fills it, defaulting to `"Project"`.
|
|
21
|
+
- **`validateInput` rejects duplicate single-occurrence references.** The
|
|
22
|
+
schema allows `order`, `contract`, `seller-order`, `project` and `despatch`
|
|
23
|
+
references at most once each, and `delivery.despatchAdviceReference` and a
|
|
24
|
+
`despatch` reference target the same element. Both cases used to surface
|
|
25
|
+
only as an XSD error after building.
|
|
26
|
+
|
|
10
27
|
## [1.4.3] — 2026-09-28
|
|
11
28
|
|
|
12
29
|
- **Fixed half-cent rounding.** `Math.round(n * 100) / 100` turns
|
|
@@ -274,6 +291,7 @@ This element is not expected`. Reordered the three blocks in
|
|
|
274
291
|
- Deno compatibility test suite
|
|
275
292
|
|
|
276
293
|
[Unreleased]: https://github.com/StackForge-EU/factur-x/commits/main
|
|
294
|
+
[1.4.4]: https://github.com/StackForge-EU/factur-x/compare/v1.4.3...v1.4.4
|
|
277
295
|
[1.4.3]: https://github.com/StackForge-EU/factur-x/compare/v1.4.2...v1.4.3
|
|
278
296
|
[1.4.2]: https://github.com/StackForge-EU/factur-x/compare/v1.4.1...v1.4.2
|
|
279
297
|
[1.4.1]: https://github.com/StackForge-EU/factur-x/compare/v1.4.0...v1.4.1
|
package/dist/index.cjs
CHANGED
|
@@ -570,27 +570,21 @@ function buildXml(input, profile, flavor) {
|
|
|
570
570
|
buildParty(input.sellerTaxRepresentative, profile)
|
|
571
571
|
);
|
|
572
572
|
if (input.references && atLeast(profile, "BASIC_WL" /* BASIC_WL */)) {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
case "project":
|
|
589
|
-
if (atLeast(profile, "EN16931" /* EN16931 */))
|
|
590
|
-
agree += tag("ram:SpecifiedProcuringProject", tag("ram:ID", escapeXml(ref.id)));
|
|
591
|
-
break;
|
|
592
|
-
}
|
|
593
|
-
}
|
|
573
|
+
const refsOfType = (type) => input.references.filter((ref) => (ref.type ?? "order") === type);
|
|
574
|
+
const refDoc = (ref) => tag("ram:IssuerAssignedID", escapeXml(ref.id)) + (ref.issueDate ? dateEl("ram:FormattedIssueDateTime", ref.issueDate) : "");
|
|
575
|
+
if (atLeast(profile, "EN16931" /* EN16931 */))
|
|
576
|
+
for (const ref of refsOfType("seller-order"))
|
|
577
|
+
agree += tag("ram:SellerOrderReferencedDocument", refDoc(ref));
|
|
578
|
+
for (const ref of refsOfType("order"))
|
|
579
|
+
agree += tag("ram:BuyerOrderReferencedDocument", refDoc(ref));
|
|
580
|
+
for (const ref of refsOfType("contract"))
|
|
581
|
+
agree += tag("ram:ContractReferencedDocument", refDoc(ref));
|
|
582
|
+
if (atLeast(profile, "EN16931" /* EN16931 */))
|
|
583
|
+
for (const ref of refsOfType("project"))
|
|
584
|
+
agree += tag(
|
|
585
|
+
"ram:SpecifiedProcuringProject",
|
|
586
|
+
tag("ram:ID", escapeXml(ref.id)) + tag("ram:Name", escapeXml(ref.name ?? "Project"))
|
|
587
|
+
);
|
|
594
588
|
}
|
|
595
589
|
tx += tag("ram:ApplicableHeaderTradeAgreement", agree);
|
|
596
590
|
let del = "";
|
|
@@ -1036,6 +1030,28 @@ function validateInput(input, profile, flavor) {
|
|
|
1036
1030
|
}
|
|
1037
1031
|
}
|
|
1038
1032
|
}
|
|
1033
|
+
if (req("BASIC_WL" /* BASIC_WL */) && input.references) {
|
|
1034
|
+
const singleOccurrence = ["order", "contract", "seller-order", "project", "despatch"];
|
|
1035
|
+
for (const type of singleOccurrence) {
|
|
1036
|
+
const count = input.references.filter((ref) => (ref.type ?? "order") === type).length;
|
|
1037
|
+
if (count > 1) {
|
|
1038
|
+
addError(
|
|
1039
|
+
errors,
|
|
1040
|
+
"references",
|
|
1041
|
+
`Only one "${type}" reference is allowed, got ${count}.`,
|
|
1042
|
+
"BASIC_WL" /* BASIC_WL */
|
|
1043
|
+
);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
if (input.delivery?.despatchAdviceReference && input.references.some((ref) => ref.type === "despatch")) {
|
|
1047
|
+
addError(
|
|
1048
|
+
errors,
|
|
1049
|
+
"references",
|
|
1050
|
+
'Use either delivery.despatchAdviceReference or a "despatch" reference, not both.',
|
|
1051
|
+
"BASIC_WL" /* BASIC_WL */
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1039
1055
|
if (flavor === "xrechnung" /* XRECHNUNG */) {
|
|
1040
1056
|
if (!input.document?.buyerReference) {
|
|
1041
1057
|
addError(
|
package/dist/index.d.cts
CHANGED
|
@@ -947,6 +947,12 @@ interface DocumentReferenceInput {
|
|
|
947
947
|
type?: "order" | "contract" | "despatch" | "preceding" | "seller-order" | "project";
|
|
948
948
|
/** Issue date of the referenced document */
|
|
949
949
|
issueDate?: string;
|
|
950
|
+
/**
|
|
951
|
+
* Project name, only used for `"project"` references. The CII schema
|
|
952
|
+
* requires a `ram:Name` next to the project ID (BT-11); falls back to
|
|
953
|
+
* `"Project"` when omitted.
|
|
954
|
+
*/
|
|
955
|
+
name?: string;
|
|
950
956
|
}
|
|
951
957
|
/**
|
|
952
958
|
* Billing/invoicing period.
|
package/dist/index.d.ts
CHANGED
|
@@ -947,6 +947,12 @@ interface DocumentReferenceInput {
|
|
|
947
947
|
type?: "order" | "contract" | "despatch" | "preceding" | "seller-order" | "project";
|
|
948
948
|
/** Issue date of the referenced document */
|
|
949
949
|
issueDate?: string;
|
|
950
|
+
/**
|
|
951
|
+
* Project name, only used for `"project"` references. The CII schema
|
|
952
|
+
* requires a `ram:Name` next to the project ID (BT-11); falls back to
|
|
953
|
+
* `"Project"` when omitted.
|
|
954
|
+
*/
|
|
955
|
+
name?: string;
|
|
950
956
|
}
|
|
951
957
|
/**
|
|
952
958
|
* Billing/invoicing period.
|
package/dist/index.js
CHANGED
|
@@ -540,27 +540,21 @@ function buildXml(input, profile, flavor) {
|
|
|
540
540
|
buildParty(input.sellerTaxRepresentative, profile)
|
|
541
541
|
);
|
|
542
542
|
if (input.references && atLeast(profile, "BASIC_WL" /* BASIC_WL */)) {
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
case "project":
|
|
559
|
-
if (atLeast(profile, "EN16931" /* EN16931 */))
|
|
560
|
-
agree += tag("ram:SpecifiedProcuringProject", tag("ram:ID", escapeXml(ref.id)));
|
|
561
|
-
break;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
543
|
+
const refsOfType = (type) => input.references.filter((ref) => (ref.type ?? "order") === type);
|
|
544
|
+
const refDoc = (ref) => tag("ram:IssuerAssignedID", escapeXml(ref.id)) + (ref.issueDate ? dateEl("ram:FormattedIssueDateTime", ref.issueDate) : "");
|
|
545
|
+
if (atLeast(profile, "EN16931" /* EN16931 */))
|
|
546
|
+
for (const ref of refsOfType("seller-order"))
|
|
547
|
+
agree += tag("ram:SellerOrderReferencedDocument", refDoc(ref));
|
|
548
|
+
for (const ref of refsOfType("order"))
|
|
549
|
+
agree += tag("ram:BuyerOrderReferencedDocument", refDoc(ref));
|
|
550
|
+
for (const ref of refsOfType("contract"))
|
|
551
|
+
agree += tag("ram:ContractReferencedDocument", refDoc(ref));
|
|
552
|
+
if (atLeast(profile, "EN16931" /* EN16931 */))
|
|
553
|
+
for (const ref of refsOfType("project"))
|
|
554
|
+
agree += tag(
|
|
555
|
+
"ram:SpecifiedProcuringProject",
|
|
556
|
+
tag("ram:ID", escapeXml(ref.id)) + tag("ram:Name", escapeXml(ref.name ?? "Project"))
|
|
557
|
+
);
|
|
564
558
|
}
|
|
565
559
|
tx += tag("ram:ApplicableHeaderTradeAgreement", agree);
|
|
566
560
|
let del = "";
|
|
@@ -1006,6 +1000,28 @@ function validateInput(input, profile, flavor) {
|
|
|
1006
1000
|
}
|
|
1007
1001
|
}
|
|
1008
1002
|
}
|
|
1003
|
+
if (req("BASIC_WL" /* BASIC_WL */) && input.references) {
|
|
1004
|
+
const singleOccurrence = ["order", "contract", "seller-order", "project", "despatch"];
|
|
1005
|
+
for (const type of singleOccurrence) {
|
|
1006
|
+
const count = input.references.filter((ref) => (ref.type ?? "order") === type).length;
|
|
1007
|
+
if (count > 1) {
|
|
1008
|
+
addError(
|
|
1009
|
+
errors,
|
|
1010
|
+
"references",
|
|
1011
|
+
`Only one "${type}" reference is allowed, got ${count}.`,
|
|
1012
|
+
"BASIC_WL" /* BASIC_WL */
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
if (input.delivery?.despatchAdviceReference && input.references.some((ref) => ref.type === "despatch")) {
|
|
1017
|
+
addError(
|
|
1018
|
+
errors,
|
|
1019
|
+
"references",
|
|
1020
|
+
'Use either delivery.despatchAdviceReference or a "despatch" reference, not both.',
|
|
1021
|
+
"BASIC_WL" /* BASIC_WL */
|
|
1022
|
+
);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1009
1025
|
if (flavor === "xrechnung" /* XRECHNUNG */) {
|
|
1010
1026
|
if (!input.document?.buyerReference) {
|
|
1011
1027
|
addError(
|