@getpeppr/cli 0.8.2 → 0.8.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 +24 -0
- package/README.md +6 -3
- package/dist/index.js +1037 -723
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -130,7 +130,7 @@ Validating: ${pc.bold(filename)}
|
|
|
130
130
|
return lines.join("\n");
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
//
|
|
133
|
+
// ../../../getpeppr/packages/sdk/dist/core/canonical-schemes.js
|
|
134
134
|
var ALIAS_TO_EAS = /* @__PURE__ */ new Map([
|
|
135
135
|
["AD:VAT", "9922"],
|
|
136
136
|
["AE:TIN", "0235"],
|
|
@@ -356,7 +356,7 @@ function countryForScheme(scheme) {
|
|
|
356
356
|
var CANONICAL_SCHEME_COUNT = ALIAS_TO_EAS.size;
|
|
357
357
|
var SCHEME_COUNTRY_COUNT = EAS_TO_COUNTRY.size;
|
|
358
358
|
|
|
359
|
-
//
|
|
359
|
+
// ../../../getpeppr/packages/sdk/dist/core/peppol-id.js
|
|
360
360
|
function isWellFormedPeppolId(peppolId) {
|
|
361
361
|
if (!peppolId.includes(":"))
|
|
362
362
|
return false;
|
|
@@ -382,7 +382,7 @@ function parsePeppolId(peppolId) {
|
|
|
382
382
|
};
|
|
383
383
|
}
|
|
384
384
|
|
|
385
|
-
//
|
|
385
|
+
// ../../../getpeppr/packages/sdk/dist/core/iso6523-icd-codes.js
|
|
386
386
|
var ICD_CODES = /* @__PURE__ */ new Set([
|
|
387
387
|
"0002",
|
|
388
388
|
"0003",
|
|
@@ -642,7 +642,7 @@ function canCarryPartyIdentification(scheme, context) {
|
|
|
642
642
|
return scheme === "SEPA" && context !== "AccountingCustomerParty";
|
|
643
643
|
}
|
|
644
644
|
|
|
645
|
-
//
|
|
645
|
+
// ../../../getpeppr/packages/sdk/dist/core/ubl-builder.js
|
|
646
646
|
var UBL_NS = "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2";
|
|
647
647
|
var CAC_NS = "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2";
|
|
648
648
|
var CBC_NS = "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2";
|
|
@@ -651,6 +651,24 @@ var PEPPOL_CUSTOMIZATION_ID = "urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.
|
|
|
651
651
|
var PEPPOL_PROFILE_ID = "urn:fdc:peppol.eu:2017:poacc:billing:01:1.0";
|
|
652
652
|
var DEFAULT_UNIT = "EA";
|
|
653
653
|
var DEFAULT_PAYMENT_MEANS = 30;
|
|
654
|
+
var EXEMPTION_REASON_CATEGORIES = /* @__PURE__ */ new Set(["E", "AE", "K", "G", "O"]);
|
|
655
|
+
var EXEMPTION_REASON_RULES = {
|
|
656
|
+
E: "BR-E-10",
|
|
657
|
+
AE: "BR-AE-10",
|
|
658
|
+
K: "BR-IC-10",
|
|
659
|
+
G: "BR-G-10",
|
|
660
|
+
O: "BR-O-10"
|
|
661
|
+
};
|
|
662
|
+
var UblBuilderInputError = class extends Error {
|
|
663
|
+
field;
|
|
664
|
+
ruleId;
|
|
665
|
+
constructor(message, field, ruleId) {
|
|
666
|
+
super(message);
|
|
667
|
+
this.field = field;
|
|
668
|
+
this.ruleId = ruleId;
|
|
669
|
+
this.name = "UblBuilderInputError";
|
|
670
|
+
}
|
|
671
|
+
};
|
|
654
672
|
var UNIT_CODE_MAP = {
|
|
655
673
|
each: "EA",
|
|
656
674
|
piece: "EA",
|
|
@@ -693,9 +711,51 @@ function formatDate(dateStr) {
|
|
|
693
711
|
function formatAmount(amount) {
|
|
694
712
|
return amount.toFixed(2);
|
|
695
713
|
}
|
|
714
|
+
function formatVatRate(vatRate, field) {
|
|
715
|
+
if (typeof vatRate !== "number" || !Number.isFinite(vatRate)) {
|
|
716
|
+
throw new UblBuilderInputError("vatRate must be a finite number.", field);
|
|
717
|
+
}
|
|
718
|
+
return String(vatRate);
|
|
719
|
+
}
|
|
720
|
+
function formatBaseQuantity(baseQuantity, field) {
|
|
721
|
+
if (typeof baseQuantity !== "number" || !Number.isFinite(baseQuantity) || baseQuantity <= 0) {
|
|
722
|
+
throw new UblBuilderInputError("baseQuantity must be a finite number greater than zero.", field, "PEPPOL-EN16931-R121");
|
|
723
|
+
}
|
|
724
|
+
const numeric = String(baseQuantity);
|
|
725
|
+
const exponentMarker = numeric.search(/[eE]/);
|
|
726
|
+
if (exponentMarker === -1)
|
|
727
|
+
return numeric;
|
|
728
|
+
const coefficient = numeric.slice(0, exponentMarker);
|
|
729
|
+
const exponent = Number(numeric.slice(exponentMarker + 1));
|
|
730
|
+
const decimalPoint = coefficient.indexOf(".");
|
|
731
|
+
const digits = coefficient.replace(".", "");
|
|
732
|
+
const integerDigits = decimalPoint === -1 ? coefficient.length : decimalPoint;
|
|
733
|
+
const outputPoint = integerDigits + exponent;
|
|
734
|
+
if (outputPoint <= 0) {
|
|
735
|
+
return `0.${"0".repeat(-outputPoint)}${digits}`;
|
|
736
|
+
}
|
|
737
|
+
if (outputPoint >= digits.length) {
|
|
738
|
+
return `${digits}${"0".repeat(outputPoint - digits.length)}`;
|
|
739
|
+
}
|
|
740
|
+
return `${digits.slice(0, outputPoint)}.${digits.slice(outputPoint)}`;
|
|
741
|
+
}
|
|
696
742
|
function round2(n) {
|
|
697
743
|
return Math.round(n * 100) / 100;
|
|
698
744
|
}
|
|
745
|
+
function normalizedTaxExemptReason(vatCategory, reason) {
|
|
746
|
+
if (!EXEMPTION_REASON_CATEGORIES.has(vatCategory) || typeof reason !== "string") {
|
|
747
|
+
return void 0;
|
|
748
|
+
}
|
|
749
|
+
const normalized = reason.trim();
|
|
750
|
+
for (const character of normalized) {
|
|
751
|
+
const codePoint = character.codePointAt(0);
|
|
752
|
+
const allowed = codePoint === 9 || codePoint === 10 || codePoint === 13 || codePoint >= 32 && codePoint <= 55295 || codePoint >= 57344 && codePoint <= 65533 || codePoint >= 65536 && codePoint <= 1114111;
|
|
753
|
+
if (!allowed) {
|
|
754
|
+
throw new UblBuilderInputError("taxExemptReason contains an invalid XML character.", "taxExemptReason");
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return normalized || void 0;
|
|
758
|
+
}
|
|
699
759
|
function buildPartyXml(party, role) {
|
|
700
760
|
const { scheme: endpointScheme, id: endpointId } = parsePeppolId(party.peppolId);
|
|
701
761
|
return `
|
|
@@ -876,8 +936,8 @@ function buildDocumentAllowanceChargeXml(item, isCharge, currency) {
|
|
|
876
936
|
<cbc:AllowanceChargeReason>${escapeXml(item.reason)}</cbc:AllowanceChargeReason>
|
|
877
937
|
<cbc:Amount currencyID="${escapeXml(currency)}">${formatAmount(item.amount)}</cbc:Amount>
|
|
878
938
|
<cac:TaxCategory>
|
|
879
|
-
<cbc:ID>${vatCategory}</cbc:ID>
|
|
880
|
-
|
|
939
|
+
<cbc:ID>${escapeXml(vatCategory)}</cbc:ID>
|
|
940
|
+
${vatCategory === "O" ? "" : `<cbc:Percent>${formatVatRate(item.vatRate, "vatRate")}</cbc:Percent>`}
|
|
881
941
|
<cac:TaxScheme>
|
|
882
942
|
<cbc:ID>VAT</cbc:ID>
|
|
883
943
|
</cac:TaxScheme>
|
|
@@ -917,8 +977,8 @@ function buildDocumentLineXml(line, index, currency, lineTag, qtyTag) {
|
|
|
917
977
|
<cbc:ID>${escapeXml(line.itemId)}</cbc:ID>
|
|
918
978
|
</cac:SellersItemIdentification>` : ""}
|
|
919
979
|
<cac:ClassifiedTaxCategory>
|
|
920
|
-
<cbc:ID>${vatCategory}</cbc:ID>
|
|
921
|
-
|
|
980
|
+
<cbc:ID>${escapeXml(vatCategory)}</cbc:ID>
|
|
981
|
+
${vatCategory === "O" ? "" : `<cbc:Percent>${formatVatRate(line.vatRate, "vatRate")}</cbc:Percent>`}
|
|
922
982
|
<cac:TaxScheme>
|
|
923
983
|
<cbc:ID>VAT</cbc:ID>
|
|
924
984
|
</cac:TaxScheme>
|
|
@@ -936,46 +996,67 @@ function buildDocumentLineXml(line, index, currency, lineTag, qtyTag) {
|
|
|
936
996
|
</cac:Item>
|
|
937
997
|
<cac:Price>
|
|
938
998
|
<cbc:PriceAmount currencyID="${escapeXml(currency)}">${formatAmount(line.unitPrice)}</cbc:PriceAmount>
|
|
939
|
-
${line.baseQuantity
|
|
999
|
+
${line.baseQuantity !== void 0 ? `<cbc:BaseQuantity unitCode="${escapeXml(resolveUnitCode(line.baseQuantityUnit ?? line.unit ?? DEFAULT_UNIT))}">${formatBaseQuantity(line.baseQuantity, `lines[${index}].baseQuantity`)}</cbc:BaseQuantity>` : ""}
|
|
940
1000
|
</cac:Price>
|
|
941
1001
|
</cac:${lineTag}>`;
|
|
942
1002
|
}
|
|
943
1003
|
function buildInvoiceLineXml(line, index, currency) {
|
|
944
1004
|
return buildDocumentLineXml(line, index, currency, "InvoiceLine", "InvoicedQuantity");
|
|
945
1005
|
}
|
|
946
|
-
function calculateTaxSubtotals(lines, allowances, charges) {
|
|
1006
|
+
function calculateTaxSubtotals(lines, allowances, charges, options = {}) {
|
|
947
1007
|
const groups = /* @__PURE__ */ new Map();
|
|
948
|
-
function addToGroup(vatCategory, vatRate, amount) {
|
|
949
|
-
|
|
1008
|
+
function addToGroup(vatCategory, vatRate, amount, taxExemptReason, field = "taxExemptReason") {
|
|
1009
|
+
if (typeof vatCategory !== "string") {
|
|
1010
|
+
throw new UblBuilderInputError("vatCategory must be a string.", `${field}.vatCategory`);
|
|
1011
|
+
}
|
|
1012
|
+
formatVatRate(vatRate, `${field}.vatRate`);
|
|
1013
|
+
if (options.forUbl && vatCategory === "O" && vatRate !== 0) {
|
|
1014
|
+
throw new UblBuilderInputError("Category O must use vatRate 0 in SDK input.", `${field}.vatRate`);
|
|
1015
|
+
}
|
|
1016
|
+
const effectiveVatRate = options.forUbl && vatCategory === "O" ? 0 : vatRate;
|
|
1017
|
+
const reason = options.forUbl ? normalizedTaxExemptReason(vatCategory, taxExemptReason) : void 0;
|
|
1018
|
+
const key = `${vatCategory}-${effectiveVatRate}`;
|
|
950
1019
|
const existing = groups.get(key);
|
|
951
1020
|
if (existing) {
|
|
1021
|
+
if (reason && existing.taxExemptReason && reason !== existing.taxExemptReason) {
|
|
1022
|
+
throw new UblBuilderInputError(`Conflicting taxExemptReason values for VAT group ${vatCategory}/${effectiveVatRate}.`, "taxExemptReason");
|
|
1023
|
+
}
|
|
1024
|
+
existing.taxExemptReason ??= reason;
|
|
952
1025
|
existing.taxableAmount = round2(existing.taxableAmount + amount);
|
|
953
1026
|
} else {
|
|
954
1027
|
groups.set(key, {
|
|
955
|
-
vatRate,
|
|
1028
|
+
vatRate: effectiveVatRate,
|
|
956
1029
|
vatCategory,
|
|
1030
|
+
taxExemptReason: reason,
|
|
957
1031
|
taxableAmount: amount,
|
|
958
1032
|
taxAmount: 0
|
|
959
1033
|
// computed once per group below (BR-CO-17)
|
|
960
1034
|
});
|
|
961
1035
|
}
|
|
962
1036
|
}
|
|
963
|
-
for (const line of lines) {
|
|
964
|
-
addToGroup(line.vatCategory ?? "S", line.vatRate, calculateLineExtensionAmount(line));
|
|
1037
|
+
for (const [index, line] of lines.entries()) {
|
|
1038
|
+
addToGroup(line.vatCategory ?? "S", line.vatRate, calculateLineExtensionAmount(line), line.taxExemptReason, `lines[${index}]`);
|
|
965
1039
|
}
|
|
966
|
-
for (const a of allowances ?? []) {
|
|
967
|
-
addToGroup(a.vatCategory ?? "S", a.vatRate, -a.amount);
|
|
1040
|
+
for (const [index, a] of (allowances ?? []).entries()) {
|
|
1041
|
+
addToGroup(a.vatCategory ?? "S", a.vatRate, -a.amount, a.taxExemptReason, `allowances[${index}]`);
|
|
968
1042
|
}
|
|
969
|
-
for (const c of charges ?? []) {
|
|
970
|
-
addToGroup(c.vatCategory ?? "S", c.vatRate, c.amount);
|
|
1043
|
+
for (const [index, c] of (charges ?? []).entries()) {
|
|
1044
|
+
addToGroup(c.vatCategory ?? "S", c.vatRate, c.amount, c.taxExemptReason, `charges[${index}]`);
|
|
1045
|
+
}
|
|
1046
|
+
if (options.forUbl) {
|
|
1047
|
+
for (const subtotal of groups.values()) {
|
|
1048
|
+
if (EXEMPTION_REASON_CATEGORIES.has(subtotal.vatCategory) && !subtotal.taxExemptReason) {
|
|
1049
|
+
throw new UblBuilderInputError(`VAT category ${subtotal.vatCategory} requires a non-empty taxExemptReason.`, "taxExemptReason", EXEMPTION_REASON_RULES[subtotal.vatCategory]);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
971
1052
|
}
|
|
972
1053
|
return Array.from(groups.values()).map((subtotal) => ({
|
|
973
1054
|
...subtotal,
|
|
974
1055
|
taxAmount: round2(subtotal.taxableAmount * (subtotal.vatRate / 100))
|
|
975
1056
|
}));
|
|
976
1057
|
}
|
|
977
|
-
function calculateDocumentTotals(lines, allowances, charges) {
|
|
978
|
-
const taxSubtotals = calculateTaxSubtotals(lines, allowances, charges);
|
|
1058
|
+
function calculateDocumentTotals(lines, allowances, charges, options = {}) {
|
|
1059
|
+
const taxSubtotals = calculateTaxSubtotals(lines, allowances, charges, options);
|
|
979
1060
|
const lineExtensionAmount = lines.reduce((sum, line) => sum + calculateLineExtensionAmount(line), 0);
|
|
980
1061
|
const allowanceTotalAmount = (allowances ?? []).reduce((sum, a) => sum + a.amount, 0);
|
|
981
1062
|
const chargeTotalAmount = (charges ?? []).reduce((sum, c) => sum + c.amount, 0);
|
|
@@ -999,8 +1080,9 @@ function buildTaxTotalXml(taxSubtotals, totalTax, currency) {
|
|
|
999
1080
|
<cbc:TaxableAmount currencyID="${escapeXml(currency)}">${formatAmount(st.taxableAmount)}</cbc:TaxableAmount>
|
|
1000
1081
|
<cbc:TaxAmount currencyID="${escapeXml(currency)}">${formatAmount(st.taxAmount)}</cbc:TaxAmount>
|
|
1001
1082
|
<cac:TaxCategory>
|
|
1002
|
-
<cbc:ID>${st.vatCategory}</cbc:ID>
|
|
1003
|
-
|
|
1083
|
+
<cbc:ID>${escapeXml(st.vatCategory)}</cbc:ID>
|
|
1084
|
+
${st.vatCategory === "O" ? "" : `<cbc:Percent>${formatVatRate(st.vatRate, "vatRate")}</cbc:Percent>`}
|
|
1085
|
+
${st.taxExemptReason ? `<cbc:TaxExemptionReason>${escapeXml(st.taxExemptReason)}</cbc:TaxExemptionReason>` : ""}
|
|
1004
1086
|
<cac:TaxScheme>
|
|
1005
1087
|
<cbc:ID>VAT</cbc:ID>
|
|
1006
1088
|
</cac:TaxScheme>
|
|
@@ -1069,7 +1151,7 @@ function buildInvoiceXml(input) {
|
|
|
1069
1151
|
const date = formatDate(input.date);
|
|
1070
1152
|
const dueDate = input.dueDate ? formatDate(input.dueDate) : void 0;
|
|
1071
1153
|
const hasTaxCurrency = input.taxCurrency && input.taxCurrency !== currency;
|
|
1072
|
-
const totals = calculateDocumentTotals(input.lines, input.allowances, input.charges);
|
|
1154
|
+
const totals = calculateDocumentTotals(input.lines, input.allowances, input.charges, { forUbl: true });
|
|
1073
1155
|
const linesXml = input.lines.map((line, i) => buildInvoiceLineXml(line, i, currency)).join("");
|
|
1074
1156
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
1075
1157
|
<Invoice xmlns="${UBL_NS}"
|
|
@@ -1116,7 +1198,7 @@ function buildCreditNoteXml(input) {
|
|
|
1116
1198
|
const date = formatDate(input.date);
|
|
1117
1199
|
const dueDate = input.dueDate ? formatDate(input.dueDate) : void 0;
|
|
1118
1200
|
const hasTaxCurrency = input.taxCurrency && input.taxCurrency !== currency;
|
|
1119
|
-
const totals = calculateDocumentTotals(input.lines, input.allowances, input.charges);
|
|
1201
|
+
const totals = calculateDocumentTotals(input.lines, input.allowances, input.charges, { forUbl: true });
|
|
1120
1202
|
const linesXml = input.lines.map((line, i) => buildCreditNoteLineXml(line, i, currency)).join("");
|
|
1121
1203
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
1122
1204
|
<CreditNote xmlns="${CREDIT_NOTE_NS}"
|
|
@@ -1160,7 +1242,7 @@ function buildCreditNoteXml(input) {
|
|
|
1160
1242
|
</CreditNote>`;
|
|
1161
1243
|
}
|
|
1162
1244
|
|
|
1163
|
-
//
|
|
1245
|
+
// ../../../getpeppr/packages/sdk/dist/core/checksums/luhn.js
|
|
1164
1246
|
function isValidLuhn(input) {
|
|
1165
1247
|
if (typeof input !== "string")
|
|
1166
1248
|
return false;
|
|
@@ -1201,7 +1283,7 @@ function isValidLuhnSiret(input) {
|
|
|
1201
1283
|
return false;
|
|
1202
1284
|
}
|
|
1203
1285
|
|
|
1204
|
-
//
|
|
1286
|
+
// ../../../getpeppr/packages/sdk/dist/core/country-rules.js
|
|
1205
1287
|
function warn(field, message, ruleId) {
|
|
1206
1288
|
return { field, message, ruleId };
|
|
1207
1289
|
}
|
|
@@ -1342,7 +1424,7 @@ function validateCountryRules(input) {
|
|
|
1342
1424
|
return { errors, warnings };
|
|
1343
1425
|
}
|
|
1344
1426
|
|
|
1345
|
-
//
|
|
1427
|
+
// ../../../getpeppr/packages/sdk/dist/core/code-lists.js
|
|
1346
1428
|
var CURRENCIES = /* @__PURE__ */ new Map([
|
|
1347
1429
|
["EUR", { code: "EUR", name: "Euro", minorUnits: 2 }],
|
|
1348
1430
|
["USD", { code: "USD", name: "US Dollar", minorUnits: 2 }],
|
|
@@ -1467,7 +1549,7 @@ function getAllUnits() {
|
|
|
1467
1549
|
return Array.from(UNIT_CODES.entries()).map(([code, name]) => ({ code, name })).sort((a, b) => a.code.localeCompare(b.code));
|
|
1468
1550
|
}
|
|
1469
1551
|
|
|
1470
|
-
//
|
|
1552
|
+
// ../../../getpeppr/packages/sdk/dist/core/validator.js
|
|
1471
1553
|
function error(field, message, ruleId, suggestion) {
|
|
1472
1554
|
return { field, message, ruleId, suggestion };
|
|
1473
1555
|
}
|
|
@@ -1545,6 +1627,11 @@ function validateLine(line, index, isCreditNote = false) {
|
|
|
1545
1627
|
} else if (line.unitPrice < 0) {
|
|
1546
1628
|
errors.push(error(`${path}.unitPrice`, `Unit price cannot be negative, got ${line.unitPrice}`, void 0, "For discounts, use a negative quantity or a separate discount line"));
|
|
1547
1629
|
}
|
|
1630
|
+
if (line.baseQuantity !== void 0) {
|
|
1631
|
+
if (typeof line.baseQuantity !== "number" || !Number.isFinite(line.baseQuantity) || line.baseQuantity <= 0) {
|
|
1632
|
+
errors.push(error(`${path}.baseQuantity`, "Base quantity must be a finite number greater than zero", "PEPPOL-EN16931-R121", "Use a positive number for the item price base quantity"));
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1548
1635
|
if (line.vatRate === void 0 || line.vatRate === null) {
|
|
1549
1636
|
errors.push(error(`${path}.vatRate`, "VAT rate is required", "BR-CO-17"));
|
|
1550
1637
|
} else if (line.vatRate < 0 || line.vatRate > 100) {
|
|
@@ -1607,11 +1694,32 @@ function validateInvoice(input) {
|
|
|
1607
1694
|
errors.push(error("payeeParty.peppolId", `Invalid Peppol ID format: "${ppId}"`, void 0, 'Must be "scheme:id" \u2014 e.g. "0208:0685660237". The scheme alone ("GB:VAT") is not an identifier.'));
|
|
1608
1695
|
}
|
|
1609
1696
|
}
|
|
1610
|
-
|
|
1697
|
+
const linesValue = input.lines;
|
|
1698
|
+
if (!Array.isArray(linesValue)) {
|
|
1699
|
+
errors.push(error("lines", "Line items must be an array", void 0));
|
|
1700
|
+
} else if (linesValue.length === 0) {
|
|
1611
1701
|
errors.push(error("lines", "At least one line item is required", "BR-16", "Add items to the lines array"));
|
|
1612
1702
|
} else {
|
|
1613
|
-
for (
|
|
1614
|
-
|
|
1703
|
+
for (const [i, line] of linesValue.entries()) {
|
|
1704
|
+
if (line === null || typeof line !== "object") {
|
|
1705
|
+
errors.push(error(`lines[${i}]`, `Line item ${i} must be an object`, void 0));
|
|
1706
|
+
continue;
|
|
1707
|
+
}
|
|
1708
|
+
errors.push(...validateLine(line, i, input.isCreditNote));
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
for (const field of ["allowances", "charges"]) {
|
|
1712
|
+
const value = input[field];
|
|
1713
|
+
if (value === void 0)
|
|
1714
|
+
continue;
|
|
1715
|
+
if (!Array.isArray(value)) {
|
|
1716
|
+
errors.push(error(field, `${field} must be an array`, void 0));
|
|
1717
|
+
continue;
|
|
1718
|
+
}
|
|
1719
|
+
for (const [index, item] of value.entries()) {
|
|
1720
|
+
if (item === null || typeof item !== "object") {
|
|
1721
|
+
errors.push(error(`${field}[${index}]`, `${field}[${index}] must be an object`, void 0));
|
|
1722
|
+
}
|
|
1615
1723
|
}
|
|
1616
1724
|
}
|
|
1617
1725
|
if (input.date) {
|
|
@@ -1673,174 +1781,699 @@ function validateInvoice(input) {
|
|
|
1673
1781
|
};
|
|
1674
1782
|
}
|
|
1675
1783
|
|
|
1676
|
-
//
|
|
1677
|
-
|
|
1678
|
-
{
|
|
1679
|
-
{ status: "rejected", family: "terminal-failure" },
|
|
1680
|
-
{ status: "paid", family: "terminal-success" },
|
|
1681
|
-
{ status: "partially_paid", family: "progress" },
|
|
1682
|
-
{ status: "accepted", family: "progress" },
|
|
1683
|
-
{ status: "conditionally_accepted", family: "progress" },
|
|
1684
|
-
{ status: "under_query", family: "progress" },
|
|
1685
|
-
{ status: "in_process", family: "progress" },
|
|
1686
|
-
{ status: "cleared", family: "progress" },
|
|
1687
|
-
{ status: "delivered", family: "progress" },
|
|
1688
|
-
{ status: "acknowledged", family: "progress" },
|
|
1689
|
-
// Terminal for developer wait semantics only — stays rank 40 (non-terminal)
|
|
1690
|
-
// in the projection guard (§3.12 two-level terminality).
|
|
1691
|
-
{ status: "no_action", family: "terminal-failure" },
|
|
1692
|
-
{ status: "submitted", family: "progress" },
|
|
1693
|
-
{ status: "unknown", family: "fallback" }
|
|
1694
|
-
];
|
|
1695
|
-
function statusFamily(status) {
|
|
1696
|
-
return STATUS_PRECEDENCE.find((e) => e.status === status)?.family ?? "fallback";
|
|
1697
|
-
}
|
|
1698
|
-
var TERMINAL_FAILURE_STATUSES = STATUS_PRECEDENCE.filter((e) => e.family === "terminal-failure").map((e) => e.status);
|
|
1699
|
-
|
|
1700
|
-
// ../sdk/dist/version.js
|
|
1701
|
-
var SDK_VERSION = "4.5.0";
|
|
1702
|
-
|
|
1703
|
-
// ../sdk/dist/core/client.js
|
|
1704
|
-
function findHeaderCaseInsensitive(headers, name) {
|
|
1705
|
-
if (!headers)
|
|
1706
|
-
return void 0;
|
|
1707
|
-
const target = name.toLowerCase();
|
|
1708
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
1709
|
-
if (key.toLowerCase() === target)
|
|
1710
|
-
return value;
|
|
1711
|
-
}
|
|
1712
|
-
return void 0;
|
|
1713
|
-
}
|
|
1714
|
-
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
1715
|
-
function sleep(ms) {
|
|
1716
|
-
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
1717
|
-
}
|
|
1718
|
-
function calculateRetryDelay(attempt, initialDelayMs, maxDelayMs, retryAfterMs) {
|
|
1719
|
-
if (retryAfterMs !== void 0)
|
|
1720
|
-
return Math.min(retryAfterMs, maxDelayMs);
|
|
1721
|
-
const exponentialDelay = initialDelayMs * Math.pow(2, attempt);
|
|
1722
|
-
const jitter = Math.random() * initialDelayMs;
|
|
1723
|
-
return Math.min(exponentialDelay + jitter, maxDelayMs);
|
|
1784
|
+
// ../../../getpeppr/packages/sdk/dist/core/schematron.js
|
|
1785
|
+
function violation(ruleId, severity, message, field) {
|
|
1786
|
+
return { ruleId, severity, message, field };
|
|
1724
1787
|
}
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
1730
|
-
return seconds * 1e3;
|
|
1731
|
-
}
|
|
1732
|
-
const dateMs = Date.parse(headerValue);
|
|
1733
|
-
if (!Number.isNaN(dateMs)) {
|
|
1734
|
-
const delayMs = dateMs - Date.now();
|
|
1735
|
-
return delayMs > 0 ? delayMs : 0;
|
|
1788
|
+
var _knownUnitCodes;
|
|
1789
|
+
function getKnownUnitCodes() {
|
|
1790
|
+
if (!_knownUnitCodes) {
|
|
1791
|
+
_knownUnitCodes = new Set(getAllUnits().map((u) => u.code));
|
|
1736
1792
|
}
|
|
1737
|
-
return
|
|
1738
|
-
}
|
|
1739
|
-
var CONTROL_CHARACTERS = /[\u0000-\u001F\u007F-\u009F]/g;
|
|
1740
|
-
function stripControls(value) {
|
|
1741
|
-
return value.replace(CONTROL_CHARACTERS, " ");
|
|
1742
|
-
}
|
|
1743
|
-
function readOwn(source, key) {
|
|
1744
|
-
return Object.hasOwn(source, key) ? source[key] : void 0;
|
|
1793
|
+
return _knownUnitCodes;
|
|
1745
1794
|
}
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
const
|
|
1751
|
-
|
|
1795
|
+
var VALID_VAT_CATEGORIES = /* @__PURE__ */ new Set(["S", "Z", "E", "AE", "K", "G", "O", "L", "M", "B"]);
|
|
1796
|
+
var SENDABLE_VAT_CATEGORIES = ["S", "Z", "E", "AE", "K", "G", "O"];
|
|
1797
|
+
var UNROUTABLE_VAT_CATEGORIES = /* @__PURE__ */ new Set(["L", "M", "B"]);
|
|
1798
|
+
function computeLineNet(line) {
|
|
1799
|
+
const baseQty = line.baseQuantity ?? 1;
|
|
1800
|
+
if (baseQty === 0)
|
|
1801
|
+
return NaN;
|
|
1802
|
+
const baseAmount = line.quantity * line.unitPrice / baseQty;
|
|
1803
|
+
const chargeTotal = (line.charges ?? []).reduce((sum, c) => sum + c.amount, 0);
|
|
1804
|
+
const allowanceTotal = (line.allowances ?? []).reduce((sum, a) => sum + a.amount, 0);
|
|
1805
|
+
return baseAmount + chargeTotal - allowanceTotal;
|
|
1752
1806
|
}
|
|
1753
|
-
|
|
1754
|
-
if (
|
|
1755
|
-
return
|
|
1756
|
-
let parsed;
|
|
1757
|
-
try {
|
|
1758
|
-
parsed = new URL(value);
|
|
1759
|
-
} catch {
|
|
1760
|
-
return null;
|
|
1807
|
+
var br02 = (input) => {
|
|
1808
|
+
if (!input.number?.trim()) {
|
|
1809
|
+
return [violation("BR-02", "error", "Invoice number is required.", "number")];
|
|
1761
1810
|
}
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
try {
|
|
1770
|
-
parsed = JSON.parse(rawBody);
|
|
1771
|
-
} catch {
|
|
1772
|
-
return verbatim;
|
|
1811
|
+
return [];
|
|
1812
|
+
};
|
|
1813
|
+
var br03 = (input) => {
|
|
1814
|
+
if (!input.date) {
|
|
1815
|
+
return [
|
|
1816
|
+
violation("BR-03", "warning", "Invoice issue date is not set. The SDK will default to today's date.", "date")
|
|
1817
|
+
];
|
|
1773
1818
|
}
|
|
1774
|
-
|
|
1775
|
-
|
|
1819
|
+
return [];
|
|
1820
|
+
};
|
|
1821
|
+
var br06 = (input) => {
|
|
1822
|
+
if (input.from && !input.from.vatNumber) {
|
|
1823
|
+
return [
|
|
1824
|
+
violation("BR-06", "warning", "Seller party has no VAT number. The gateway will use the account's VAT registration.", "from.vatNumber")
|
|
1825
|
+
];
|
|
1776
1826
|
}
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
}
|
|
1783
|
-
function isRetryableError(error2) {
|
|
1784
|
-
if (error2 instanceof PeppolApiError) {
|
|
1785
|
-
return RETRYABLE_STATUS_CODES.has(error2.statusCode);
|
|
1827
|
+
return [];
|
|
1828
|
+
};
|
|
1829
|
+
var br07 = (input) => {
|
|
1830
|
+
if (!input.to?.name?.trim()) {
|
|
1831
|
+
return [violation("BR-07", "error", "Buyer name is required.", "to.name")];
|
|
1786
1832
|
}
|
|
1787
|
-
|
|
1788
|
-
|
|
1833
|
+
return [];
|
|
1834
|
+
};
|
|
1835
|
+
var br08 = (input) => {
|
|
1836
|
+
if (!input.lines || input.lines.length === 0) {
|
|
1837
|
+
return [violation("BR-08", "error", "Invoice must have at least one line item.", "lines")];
|
|
1789
1838
|
}
|
|
1790
|
-
|
|
1791
|
-
|
|
1839
|
+
return [];
|
|
1840
|
+
};
|
|
1841
|
+
var br09 = (input) => {
|
|
1842
|
+
if (!input.dueDate && !input.paymentTerms) {
|
|
1843
|
+
return [
|
|
1844
|
+
violation("BR-09", "warning", "Neither due date nor payment terms specified. At least one is recommended.", "dueDate")
|
|
1845
|
+
];
|
|
1792
1846
|
}
|
|
1793
|
-
return
|
|
1794
|
-
}
|
|
1795
|
-
var
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
timeout;
|
|
1801
|
-
retryConfig;
|
|
1802
|
-
onRequest;
|
|
1803
|
-
onResponse;
|
|
1804
|
-
constructor(config) {
|
|
1805
|
-
this.apiKey = config.apiKey;
|
|
1806
|
-
this.timeout = config.timeout ?? 3e4;
|
|
1807
|
-
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
1808
|
-
this.retryConfig = {
|
|
1809
|
-
maxRetries: config.retry?.maxRetries ?? 3,
|
|
1810
|
-
initialDelayMs: config.retry?.initialDelayMs ?? 500,
|
|
1811
|
-
maxDelayMs: config.retry?.maxDelayMs ?? 3e4
|
|
1812
|
-
};
|
|
1813
|
-
this.onRequest = config.onRequest;
|
|
1814
|
-
this.onResponse = config.onResponse;
|
|
1847
|
+
return [];
|
|
1848
|
+
};
|
|
1849
|
+
var br10 = (input) => {
|
|
1850
|
+
if (!input.buyerReference && !input.orderReference) {
|
|
1851
|
+
return [
|
|
1852
|
+
violation("BR-10", "warning", "Neither buyerReference nor orderReference specified. Peppol BIS 3.0 requires at least one.", "buyerReference")
|
|
1853
|
+
];
|
|
1815
1854
|
}
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
const retryAfterMs = err instanceof PeppolApiError ? err.retryAfterMs : void 0;
|
|
1830
|
-
await sleep(calculateRetryDelay(attempt, initialDelayMs, maxDelayMs, retryAfterMs));
|
|
1831
|
-
continue;
|
|
1832
|
-
}
|
|
1833
|
-
throw err;
|
|
1834
|
-
}
|
|
1855
|
+
return [];
|
|
1856
|
+
};
|
|
1857
|
+
var brCo10 = (input) => {
|
|
1858
|
+
const violations = [];
|
|
1859
|
+
if (!input.lines)
|
|
1860
|
+
return violations;
|
|
1861
|
+
for (let i = 0; i < input.lines.length; i++) {
|
|
1862
|
+
const line = input.lines[i];
|
|
1863
|
+
const net = computeLineNet(line);
|
|
1864
|
+
if (!Number.isFinite(net)) {
|
|
1865
|
+
const baseQty = line.baseQuantity ?? 1;
|
|
1866
|
+
const detail = baseQty === 0 ? "baseQuantity is 0, causing division by zero." : "Computed line amount is not a finite number.";
|
|
1867
|
+
violations.push(violation("BR-CO-10", "error", `Line ${i}: invalid net amount. ${detail}`, `lines[${i}]`));
|
|
1835
1868
|
}
|
|
1836
|
-
throw lastError;
|
|
1837
1869
|
}
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1870
|
+
return violations;
|
|
1871
|
+
};
|
|
1872
|
+
var brCo13 = (input) => {
|
|
1873
|
+
if (!input.lines || input.lines.length === 0)
|
|
1874
|
+
return [];
|
|
1875
|
+
let totalVat = 0;
|
|
1876
|
+
for (let i = 0; i < input.lines.length; i++) {
|
|
1877
|
+
const line = input.lines[i];
|
|
1878
|
+
const net = computeLineNet(line);
|
|
1879
|
+
if (!Number.isFinite(net))
|
|
1880
|
+
continue;
|
|
1881
|
+
totalVat += net * (line.vatRate / 100);
|
|
1882
|
+
}
|
|
1883
|
+
for (const allowance of input.allowances ?? []) {
|
|
1884
|
+
totalVat -= allowance.amount * (allowance.vatRate / 100);
|
|
1885
|
+
}
|
|
1886
|
+
for (const charge of input.charges ?? []) {
|
|
1887
|
+
totalVat += charge.amount * (charge.vatRate / 100);
|
|
1888
|
+
}
|
|
1889
|
+
if (!Number.isFinite(totalVat)) {
|
|
1890
|
+
return [
|
|
1891
|
+
violation("BR-CO-13", "error", "Computed total VAT amount is not a finite number. Check line amounts and VAT rates.")
|
|
1892
|
+
];
|
|
1893
|
+
}
|
|
1894
|
+
if (totalVat < -0.01) {
|
|
1895
|
+
return [
|
|
1896
|
+
violation("BR-CO-13", "warning", `Computed total VAT is negative (${totalVat.toFixed(2)}). This is unusual for an invoice.`)
|
|
1897
|
+
];
|
|
1898
|
+
}
|
|
1899
|
+
return [];
|
|
1900
|
+
};
|
|
1901
|
+
var brCo15 = (input) => {
|
|
1902
|
+
if (!input.lines || input.lines.length === 0)
|
|
1903
|
+
return [];
|
|
1904
|
+
let lineTotal = 0;
|
|
1905
|
+
let vatTotal = 0;
|
|
1906
|
+
for (const line of input.lines) {
|
|
1907
|
+
const net = computeLineNet(line);
|
|
1908
|
+
if (!Number.isFinite(net))
|
|
1909
|
+
continue;
|
|
1910
|
+
lineTotal += net;
|
|
1911
|
+
vatTotal += net * (line.vatRate / 100);
|
|
1912
|
+
}
|
|
1913
|
+
for (const allowance of input.allowances ?? []) {
|
|
1914
|
+
lineTotal -= allowance.amount;
|
|
1915
|
+
vatTotal -= allowance.amount * (allowance.vatRate / 100);
|
|
1916
|
+
}
|
|
1917
|
+
for (const charge of input.charges ?? []) {
|
|
1918
|
+
lineTotal += charge.amount;
|
|
1919
|
+
vatTotal += charge.amount * (charge.vatRate / 100);
|
|
1920
|
+
}
|
|
1921
|
+
const taxInclusive = lineTotal + vatTotal;
|
|
1922
|
+
if (!Number.isFinite(taxInclusive)) {
|
|
1923
|
+
return [
|
|
1924
|
+
violation("BR-CO-15", "error", "Computed tax-inclusive amount is not a finite number.")
|
|
1925
|
+
];
|
|
1926
|
+
}
|
|
1927
|
+
if (taxInclusive < -0.01) {
|
|
1928
|
+
return [
|
|
1929
|
+
violation("BR-CO-15", "warning", `Computed tax-inclusive amount is negative (${taxInclusive.toFixed(2)}). Consider using a credit note instead.`)
|
|
1930
|
+
];
|
|
1931
|
+
}
|
|
1932
|
+
return [];
|
|
1933
|
+
};
|
|
1934
|
+
var brCo16 = (input) => {
|
|
1935
|
+
if (!input.lines || input.lines.length === 0)
|
|
1936
|
+
return [];
|
|
1937
|
+
let lineTotal = 0;
|
|
1938
|
+
let vatTotal = 0;
|
|
1939
|
+
for (const line of input.lines) {
|
|
1940
|
+
const net = computeLineNet(line);
|
|
1941
|
+
if (!Number.isFinite(net))
|
|
1942
|
+
continue;
|
|
1943
|
+
lineTotal += net;
|
|
1944
|
+
vatTotal += net * (line.vatRate / 100);
|
|
1945
|
+
}
|
|
1946
|
+
for (const allowance of input.allowances ?? []) {
|
|
1947
|
+
lineTotal -= allowance.amount;
|
|
1948
|
+
vatTotal -= allowance.amount * (allowance.vatRate / 100);
|
|
1949
|
+
}
|
|
1950
|
+
for (const charge of input.charges ?? []) {
|
|
1951
|
+
lineTotal += charge.amount;
|
|
1952
|
+
vatTotal += charge.amount * (charge.vatRate / 100);
|
|
1953
|
+
}
|
|
1954
|
+
const taxInclusive = lineTotal + vatTotal;
|
|
1955
|
+
const prepaid = input.prepaidAmount ?? 0;
|
|
1956
|
+
const rounding = input.roundingAmount ?? 0;
|
|
1957
|
+
const payable = taxInclusive - prepaid + rounding;
|
|
1958
|
+
if (!Number.isFinite(payable)) {
|
|
1959
|
+
return [
|
|
1960
|
+
violation("BR-CO-16", "error", "Computed payable amount is not a finite number.")
|
|
1961
|
+
];
|
|
1962
|
+
}
|
|
1963
|
+
if (payable < -0.01) {
|
|
1964
|
+
return [
|
|
1965
|
+
violation("BR-CO-16", "warning", `Computed payable amount is negative (${payable.toFixed(2)}). Prepaid amount (${prepaid}) exceeds the invoice total.`)
|
|
1966
|
+
];
|
|
1967
|
+
}
|
|
1968
|
+
return [];
|
|
1969
|
+
};
|
|
1970
|
+
var brS05 = (input) => {
|
|
1971
|
+
const violations = [];
|
|
1972
|
+
if (!input.lines)
|
|
1973
|
+
return violations;
|
|
1974
|
+
for (let i = 0; i < input.lines.length; i++) {
|
|
1975
|
+
const line = input.lines[i];
|
|
1976
|
+
const category = line.vatCategory ?? "S";
|
|
1977
|
+
if (category === "S" && (line.vatRate === void 0 || line.vatRate <= 0)) {
|
|
1978
|
+
violations.push(violation("BR-S-05", "error", `Line ${i}: standard rate (S) requires vatRate > 0, got ${line.vatRate ?? "undefined"}.`, `lines[${i}].vatRate`));
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
return violations;
|
|
1982
|
+
};
|
|
1983
|
+
var brZ05 = (input) => {
|
|
1984
|
+
const violations = [];
|
|
1985
|
+
if (!input.lines)
|
|
1986
|
+
return violations;
|
|
1987
|
+
for (let i = 0; i < input.lines.length; i++) {
|
|
1988
|
+
const line = input.lines[i];
|
|
1989
|
+
if (line.vatCategory === "Z" && line.vatRate !== 0) {
|
|
1990
|
+
violations.push(violation("BR-Z-05", "error", `Line ${i}: zero-rated (Z) requires vatRate = 0, got ${line.vatRate}.`, `lines[${i}].vatRate`));
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
return violations;
|
|
1994
|
+
};
|
|
1995
|
+
var brE05 = (input) => {
|
|
1996
|
+
const violations = [];
|
|
1997
|
+
if (!input.lines)
|
|
1998
|
+
return violations;
|
|
1999
|
+
for (let i = 0; i < input.lines.length; i++) {
|
|
2000
|
+
const line = input.lines[i];
|
|
2001
|
+
if (line.vatCategory === "E" && line.vatRate !== 0) {
|
|
2002
|
+
violations.push(violation("BR-E-05", "error", `Line ${i}: exempt (E) requires vatRate = 0, got ${line.vatRate}.`, `lines[${i}].vatRate`));
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
return violations;
|
|
2006
|
+
};
|
|
2007
|
+
var brAe05 = (input) => {
|
|
2008
|
+
const violations = [];
|
|
2009
|
+
if (!input.lines)
|
|
2010
|
+
return violations;
|
|
2011
|
+
for (let i = 0; i < input.lines.length; i++) {
|
|
2012
|
+
const line = input.lines[i];
|
|
2013
|
+
if (line.vatCategory === "AE" && line.vatRate !== 0) {
|
|
2014
|
+
violations.push(violation("BR-AE-05", "error", `Line ${i}: reverse charge (AE) requires vatRate = 0, got ${line.vatRate}.`, `lines[${i}].vatRate`));
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
return violations;
|
|
2018
|
+
};
|
|
2019
|
+
var brG05 = (input) => {
|
|
2020
|
+
const violations = [];
|
|
2021
|
+
for (let i = 0; i < (input.lines ?? []).length; i++) {
|
|
2022
|
+
const line = input.lines[i];
|
|
2023
|
+
if (line.vatCategory === "G" && line.vatRate !== 0) {
|
|
2024
|
+
violations.push(violation("BR-G-05", "error", `Line ${i}: export outside the EU (G) requires vatRate = 0, got ${line.vatRate}.`, `lines[${i}].vatRate`));
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
return violations;
|
|
2028
|
+
};
|
|
2029
|
+
var brIc05 = (input) => {
|
|
2030
|
+
const violations = [];
|
|
2031
|
+
for (let i = 0; i < (input.lines ?? []).length; i++) {
|
|
2032
|
+
const line = input.lines[i];
|
|
2033
|
+
if (line.vatCategory === "K" && line.vatRate !== 0) {
|
|
2034
|
+
violations.push(violation("BR-IC-05", "error", `Line ${i}: intra-community supply (K) requires vatRate = 0, got ${line.vatRate}.`, `lines[${i}].vatRate`));
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
return violations;
|
|
2038
|
+
};
|
|
2039
|
+
var documentAdjustmentVatRates = (input) => {
|
|
2040
|
+
const violations = [];
|
|
2041
|
+
const configs = /* @__PURE__ */ new Map([
|
|
2042
|
+
["S", { allowanceRule: "BR-S-06", chargeRule: "BR-S-07", valid: (rate) => rate > 0, label: "standard rate (S)" }],
|
|
2043
|
+
["Z", { allowanceRule: "BR-Z-06", chargeRule: "BR-Z-07", valid: (rate) => rate === 0, label: "zero-rated (Z)" }],
|
|
2044
|
+
["E", { allowanceRule: "BR-E-06", chargeRule: "BR-E-07", valid: (rate) => rate === 0, label: "exempt (E)" }],
|
|
2045
|
+
["AE", { allowanceRule: "BR-AE-06", chargeRule: "BR-AE-07", valid: (rate) => rate === 0, label: "reverse charge (AE)" }],
|
|
2046
|
+
["G", { allowanceRule: "BR-G-06", chargeRule: "BR-G-07", valid: (rate) => rate === 0, label: "export outside the EU (G)" }],
|
|
2047
|
+
["K", { allowanceRule: "BR-IC-06", chargeRule: "BR-IC-07", valid: (rate) => rate === 0, label: "intra-community supply (K)" }]
|
|
2048
|
+
]);
|
|
2049
|
+
const check = (items, kind) => {
|
|
2050
|
+
(items ?? []).forEach((item, index) => {
|
|
2051
|
+
const category = item.vatCategory ?? "S";
|
|
2052
|
+
const config = configs.get(category);
|
|
2053
|
+
if (!config || config.valid(item.vatRate))
|
|
2054
|
+
return;
|
|
2055
|
+
const ruleId = kind === "allowance" ? config.allowanceRule : config.chargeRule;
|
|
2056
|
+
violations.push(violation(ruleId, "error", `Document ${kind} ${index}: ${config.label} has an invalid vatRate (${item.vatRate}).`, `${kind === "allowance" ? "allowances" : "charges"}[${index}].vatRate`));
|
|
2057
|
+
});
|
|
2058
|
+
};
|
|
2059
|
+
check(input.allowances, "allowance");
|
|
2060
|
+
check(input.charges, "charge");
|
|
2061
|
+
return violations;
|
|
2062
|
+
};
|
|
2063
|
+
function exemptionReasonRule(vatCategory, ruleId, label) {
|
|
2064
|
+
return (input) => {
|
|
2065
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2066
|
+
const add = (category, rate, reason, field) => {
|
|
2067
|
+
if (category !== vatCategory)
|
|
2068
|
+
return;
|
|
2069
|
+
const effectiveRate = category === "O" ? 0 : rate;
|
|
2070
|
+
const key = `${category}-${effectiveRate}`;
|
|
2071
|
+
const hasReason = typeof reason === "string" && reason.trim().length > 0;
|
|
2072
|
+
const existing = groups.get(key);
|
|
2073
|
+
if (existing) {
|
|
2074
|
+
existing.hasReason ||= hasReason;
|
|
2075
|
+
} else {
|
|
2076
|
+
groups.set(key, { hasReason, field });
|
|
2077
|
+
}
|
|
2078
|
+
};
|
|
2079
|
+
(input.lines ?? []).forEach((line, index) => {
|
|
2080
|
+
add(line.vatCategory ?? "S", line.vatRate, line.taxExemptReason, `lines[${index}].taxExemptReason`);
|
|
2081
|
+
});
|
|
2082
|
+
(input.allowances ?? []).forEach((item, index) => {
|
|
2083
|
+
add(item.vatCategory ?? "S", item.vatRate, item.taxExemptReason, `allowances[${index}].taxExemptReason`);
|
|
2084
|
+
});
|
|
2085
|
+
(input.charges ?? []).forEach((item, index) => {
|
|
2086
|
+
add(item.vatCategory ?? "S", item.vatRate, item.taxExemptReason, `charges[${index}].taxExemptReason`);
|
|
2087
|
+
});
|
|
2088
|
+
return [...groups.values()].filter((group) => !group.hasReason).map((group) => violation(ruleId, "error", `${label} requires a non-empty taxExemptReason in its VAT breakdown.`, group.field));
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
2091
|
+
var brE10 = exemptionReasonRule("E", "BR-E-10", "Exempt from VAT (E)");
|
|
2092
|
+
var brAe10 = exemptionReasonRule("AE", "BR-AE-10", "Reverse charge (AE)");
|
|
2093
|
+
var brG10 = exemptionReasonRule("G", "BR-G-10", "Export outside the EU (G)");
|
|
2094
|
+
var brO10 = exemptionReasonRule("O", "BR-O-10", "Not subject to VAT (O)");
|
|
2095
|
+
var brIc10 = exemptionReasonRule("K", "BR-IC-10", "Intra-community supply (K)");
|
|
2096
|
+
var builderVatCategoryValidity = (input) => {
|
|
2097
|
+
const violations = [];
|
|
2098
|
+
const check = (category, field) => {
|
|
2099
|
+
if (category !== void 0 && !VALID_VAT_CATEGORIES.has(category)) {
|
|
2100
|
+
violations.push(violation("BR-CL-17", "error", "VAT category is not an EN 16931 code.", field));
|
|
2101
|
+
}
|
|
2102
|
+
};
|
|
2103
|
+
(input.lines ?? []).forEach((item, index) => check(item.vatCategory, `lines[${index}].vatCategory`));
|
|
2104
|
+
(input.allowances ?? []).forEach((item, index) => check(item.vatCategory, `allowances[${index}].vatCategory`));
|
|
2105
|
+
(input.charges ?? []).forEach((item, index) => check(item.vatCategory, `charges[${index}].vatCategory`));
|
|
2106
|
+
return violations;
|
|
2107
|
+
};
|
|
2108
|
+
var UBL_BUILDER_VAT_RULES = [
|
|
2109
|
+
builderVatCategoryValidity,
|
|
2110
|
+
brS05,
|
|
2111
|
+
brZ05,
|
|
2112
|
+
brE05,
|
|
2113
|
+
brAe05,
|
|
2114
|
+
brG05,
|
|
2115
|
+
brIc05,
|
|
2116
|
+
documentAdjustmentVatRates,
|
|
2117
|
+
brE10,
|
|
2118
|
+
brAe10,
|
|
2119
|
+
brG10,
|
|
2120
|
+
brO10,
|
|
2121
|
+
brIc10
|
|
2122
|
+
];
|
|
2123
|
+
function validateUblBuilderVat(input) {
|
|
2124
|
+
const shapeViolations = [];
|
|
2125
|
+
const checkCollection = (value, field) => {
|
|
2126
|
+
if (field === "lines" || value !== void 0) {
|
|
2127
|
+
if (!Array.isArray(value)) {
|
|
2128
|
+
shapeViolations.push(violation("SDK-INPUT", "error", `${field} must be an array.`, field));
|
|
2129
|
+
return;
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
if (!Array.isArray(value))
|
|
2133
|
+
return;
|
|
2134
|
+
for (const [index, item] of value.entries()) {
|
|
2135
|
+
const itemField = `${field}[${index}]`;
|
|
2136
|
+
if (item === null || typeof item !== "object") {
|
|
2137
|
+
shapeViolations.push(violation("SDK-INPUT", "error", `${itemField} must be an object.`, itemField));
|
|
2138
|
+
continue;
|
|
2139
|
+
}
|
|
2140
|
+
const candidate = item;
|
|
2141
|
+
if (typeof candidate.vatRate !== "number" || !Number.isFinite(candidate.vatRate)) {
|
|
2142
|
+
shapeViolations.push(violation("SDK-INPUT", "error", `${itemField}.vatRate must be a finite number.`, `${itemField}.vatRate`));
|
|
2143
|
+
}
|
|
2144
|
+
if (candidate.vatCategory !== void 0 && typeof candidate.vatCategory !== "string") {
|
|
2145
|
+
shapeViolations.push(violation("SDK-INPUT", "error", `${itemField}.vatCategory must be a string.`, `${itemField}.vatCategory`));
|
|
2146
|
+
}
|
|
2147
|
+
if (candidate.taxExemptReason !== void 0 && typeof candidate.taxExemptReason !== "string") {
|
|
2148
|
+
shapeViolations.push(violation("SDK-INPUT", "error", `${itemField}.taxExemptReason must be a string.`, `${itemField}.taxExemptReason`));
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
};
|
|
2152
|
+
checkCollection(input.lines, "lines");
|
|
2153
|
+
checkCollection(input.allowances, "allowances");
|
|
2154
|
+
checkCollection(input.charges, "charges");
|
|
2155
|
+
if (shapeViolations.length > 0)
|
|
2156
|
+
return shapeViolations;
|
|
2157
|
+
const oRateViolations = [];
|
|
2158
|
+
const checkORates = (items, field) => {
|
|
2159
|
+
(items ?? []).forEach((item, index) => {
|
|
2160
|
+
if (item.vatCategory === "O" && item.vatRate !== 0) {
|
|
2161
|
+
oRateViolations.push(violation("SDK-INPUT", "error", `Category O must use vatRate 0 in SDK input.`, `${field}[${index}].vatRate`));
|
|
2162
|
+
}
|
|
2163
|
+
});
|
|
2164
|
+
};
|
|
2165
|
+
checkORates(input.lines, "lines");
|
|
2166
|
+
checkORates(input.allowances, "allowances");
|
|
2167
|
+
checkORates(input.charges, "charges");
|
|
2168
|
+
return [
|
|
2169
|
+
...oRateViolations,
|
|
2170
|
+
...UBL_BUILDER_VAT_RULES.flatMap((rule) => rule(input))
|
|
2171
|
+
];
|
|
2172
|
+
}
|
|
2173
|
+
var peppolR004 = (input) => {
|
|
2174
|
+
if (!input.to?.peppolId) {
|
|
2175
|
+
return [
|
|
2176
|
+
violation("PEPPOL-EN16931-R004", "error", "Buyer electronic address (peppolId) is required for Peppol delivery.", "to.peppolId")
|
|
2177
|
+
];
|
|
2178
|
+
}
|
|
2179
|
+
return [];
|
|
2180
|
+
};
|
|
2181
|
+
var vatCategoryCodes = (input) => {
|
|
2182
|
+
const violations = [];
|
|
2183
|
+
const sendable = SENDABLE_VAT_CATEGORIES.join(", ");
|
|
2184
|
+
const echo = (v) => v.length <= 16 ? v : `${v.slice(0, 16)}\u2026`;
|
|
2185
|
+
const check = (cat, field, label) => {
|
|
2186
|
+
if (cat === void 0 || cat === null)
|
|
2187
|
+
return;
|
|
2188
|
+
if (!VALID_VAT_CATEGORIES.has(cat)) {
|
|
2189
|
+
violations.push(violation("BR-CL-17", "error", `${label}: "${echo(cat)}" is not a VAT category code. Use one of: ${sendable}. Codes are case-sensitive \u2014 "AE" is reverse charge, "ae" is not a category.`, field));
|
|
2190
|
+
return;
|
|
2191
|
+
}
|
|
2192
|
+
if (UNROUTABLE_VAT_CATEGORIES.has(cat)) {
|
|
2193
|
+
violations.push(violation("unsupported_vat_category", "error", `${label}: VAT category "${echo(cat)}" is valid under EN 16931 but getpeppr cannot route it \u2014 our provider has no vocabulary for it. Sendable categories: ${sendable}.`, field));
|
|
2194
|
+
}
|
|
2195
|
+
};
|
|
2196
|
+
(input.lines ?? []).forEach((line, i) => check(line.vatCategory, `lines[${i}].vatCategory`, `Line ${i}`));
|
|
2197
|
+
(input.allowances ?? []).forEach((a, i) => check(a.vatCategory, `allowances[${i}].vatCategory`, `Allowance ${i}`));
|
|
2198
|
+
(input.charges ?? []).forEach((c, i) => check(c.vatCategory, `charges[${i}].vatCategory`, `Charge ${i}`));
|
|
2199
|
+
return violations;
|
|
2200
|
+
};
|
|
2201
|
+
var peppolR080 = (input) => {
|
|
2202
|
+
const violations = [];
|
|
2203
|
+
if (!input.lines)
|
|
2204
|
+
return violations;
|
|
2205
|
+
const knownCodes = getKnownUnitCodes();
|
|
2206
|
+
for (let i = 0; i < input.lines.length; i++) {
|
|
2207
|
+
const line = input.lines[i];
|
|
2208
|
+
if (line.unit) {
|
|
2209
|
+
const resolved = resolveUnit(line.unit);
|
|
2210
|
+
if (!knownCodes.has(resolved)) {
|
|
2211
|
+
violations.push(violation("PEPPOL-EN16931-R080", "warning", `Line ${i}: unit "${line.unit}" (resolved: "${resolved}") is not a known UN/ECE Rec20 code. Common codes: EA, HUR, DAY, KGM.`, `lines[${i}].unit`));
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
return violations;
|
|
2216
|
+
};
|
|
2217
|
+
var ALL_RULES = [
|
|
2218
|
+
// Required fields (BR)
|
|
2219
|
+
br02,
|
|
2220
|
+
br03,
|
|
2221
|
+
br06,
|
|
2222
|
+
br07,
|
|
2223
|
+
br08,
|
|
2224
|
+
br09,
|
|
2225
|
+
br10,
|
|
2226
|
+
// Calculations (BR-CO)
|
|
2227
|
+
brCo10,
|
|
2228
|
+
brCo13,
|
|
2229
|
+
brCo15,
|
|
2230
|
+
brCo16,
|
|
2231
|
+
// Tax categories (one family per category)
|
|
2232
|
+
brS05,
|
|
2233
|
+
brZ05,
|
|
2234
|
+
brE05,
|
|
2235
|
+
brAe05,
|
|
2236
|
+
brG05,
|
|
2237
|
+
brIc05,
|
|
2238
|
+
documentAdjustmentVatRates,
|
|
2239
|
+
brE10,
|
|
2240
|
+
brAe10,
|
|
2241
|
+
brG10,
|
|
2242
|
+
brO10,
|
|
2243
|
+
brIc10,
|
|
2244
|
+
// Peppol-specific
|
|
2245
|
+
peppolR004,
|
|
2246
|
+
vatCategoryCodes,
|
|
2247
|
+
peppolR080
|
|
2248
|
+
];
|
|
2249
|
+
function validateSchematron(input) {
|
|
2250
|
+
const errors = [];
|
|
2251
|
+
const warnings = [];
|
|
2252
|
+
for (const rule of ALL_RULES) {
|
|
2253
|
+
const violations = rule(input);
|
|
2254
|
+
for (const v of violations) {
|
|
2255
|
+
if (v.severity === "error") {
|
|
2256
|
+
errors.push(v);
|
|
2257
|
+
} else {
|
|
2258
|
+
warnings.push(v);
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
return {
|
|
2263
|
+
valid: errors.length === 0,
|
|
2264
|
+
coverage: { rulesChecked: SDK_SCHEMATRON_RULE_IDS.length, ofNetworkFatalRules: "partial" },
|
|
2265
|
+
errors,
|
|
2266
|
+
warnings
|
|
2267
|
+
};
|
|
2268
|
+
}
|
|
2269
|
+
var SDK_SCHEMATRON_RULE_IDS = [
|
|
2270
|
+
"BR-02",
|
|
2271
|
+
"BR-03",
|
|
2272
|
+
"BR-06",
|
|
2273
|
+
"BR-07",
|
|
2274
|
+
"BR-08",
|
|
2275
|
+
"BR-09",
|
|
2276
|
+
"BR-10",
|
|
2277
|
+
"BR-CL-17",
|
|
2278
|
+
"BR-CO-10",
|
|
2279
|
+
"BR-CO-13",
|
|
2280
|
+
"BR-CO-15",
|
|
2281
|
+
"BR-CO-16",
|
|
2282
|
+
"BR-S-05",
|
|
2283
|
+
"BR-Z-05",
|
|
2284
|
+
"BR-E-05",
|
|
2285
|
+
"BR-AE-05",
|
|
2286
|
+
"BR-G-05",
|
|
2287
|
+
"BR-IC-05",
|
|
2288
|
+
"BR-S-06",
|
|
2289
|
+
"BR-S-07",
|
|
2290
|
+
"BR-Z-06",
|
|
2291
|
+
"BR-Z-07",
|
|
2292
|
+
"BR-E-06",
|
|
2293
|
+
"BR-E-07",
|
|
2294
|
+
"BR-AE-06",
|
|
2295
|
+
"BR-AE-07",
|
|
2296
|
+
"BR-G-06",
|
|
2297
|
+
"BR-G-07",
|
|
2298
|
+
"BR-IC-06",
|
|
2299
|
+
"BR-IC-07",
|
|
2300
|
+
"BR-E-10",
|
|
2301
|
+
"BR-AE-10",
|
|
2302
|
+
"BR-G-10",
|
|
2303
|
+
"BR-O-10",
|
|
2304
|
+
"BR-IC-10",
|
|
2305
|
+
"PEPPOL-EN16931-R004",
|
|
2306
|
+
"PEPPOL-EN16931-R080"
|
|
2307
|
+
];
|
|
2308
|
+
|
|
2309
|
+
// ../../../getpeppr/packages/sdk/dist/core/status-precedence.js
|
|
2310
|
+
var STATUS_PRECEDENCE = [
|
|
2311
|
+
{ status: "failed", family: "terminal-failure" },
|
|
2312
|
+
{ status: "rejected", family: "terminal-failure" },
|
|
2313
|
+
{ status: "paid", family: "terminal-success" },
|
|
2314
|
+
{ status: "partially_paid", family: "progress" },
|
|
2315
|
+
{ status: "accepted", family: "progress" },
|
|
2316
|
+
{ status: "conditionally_accepted", family: "progress" },
|
|
2317
|
+
{ status: "under_query", family: "progress" },
|
|
2318
|
+
{ status: "in_process", family: "progress" },
|
|
2319
|
+
{ status: "cleared", family: "progress" },
|
|
2320
|
+
{ status: "delivered", family: "progress" },
|
|
2321
|
+
{ status: "acknowledged", family: "progress" },
|
|
2322
|
+
// Terminal for developer wait semantics only — stays rank 40 (non-terminal)
|
|
2323
|
+
// in the projection guard (§3.12 two-level terminality).
|
|
2324
|
+
{ status: "no_action", family: "terminal-failure" },
|
|
2325
|
+
{ status: "submitted", family: "progress" },
|
|
2326
|
+
{ status: "unknown", family: "fallback" }
|
|
2327
|
+
];
|
|
2328
|
+
function statusFamily(status) {
|
|
2329
|
+
return STATUS_PRECEDENCE.find((e) => e.status === status)?.family ?? "fallback";
|
|
2330
|
+
}
|
|
2331
|
+
var TERMINAL_FAILURE_STATUSES = STATUS_PRECEDENCE.filter((e) => e.family === "terminal-failure").map((e) => e.status);
|
|
2332
|
+
|
|
2333
|
+
// ../../../getpeppr/packages/sdk/dist/version.js
|
|
2334
|
+
var SDK_VERSION = "4.7.0";
|
|
2335
|
+
|
|
2336
|
+
// ../../../getpeppr/packages/sdk/dist/core/client.js
|
|
2337
|
+
function findHeaderCaseInsensitive(headers, name) {
|
|
2338
|
+
if (!headers)
|
|
2339
|
+
return void 0;
|
|
2340
|
+
const target = name.toLowerCase();
|
|
2341
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
2342
|
+
if (key.toLowerCase() === target)
|
|
2343
|
+
return value;
|
|
2344
|
+
}
|
|
2345
|
+
return void 0;
|
|
2346
|
+
}
|
|
2347
|
+
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
2348
|
+
function sleep(ms) {
|
|
2349
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
2350
|
+
}
|
|
2351
|
+
function calculateRetryDelay(attempt, initialDelayMs, maxDelayMs, retryAfterMs) {
|
|
2352
|
+
if (retryAfterMs !== void 0)
|
|
2353
|
+
return Math.min(retryAfterMs, maxDelayMs);
|
|
2354
|
+
const exponentialDelay = initialDelayMs * Math.pow(2, attempt);
|
|
2355
|
+
const jitter = Math.random() * initialDelayMs;
|
|
2356
|
+
return Math.min(exponentialDelay + jitter, maxDelayMs);
|
|
2357
|
+
}
|
|
2358
|
+
function parseRetryAfter(headerValue) {
|
|
2359
|
+
if (!headerValue)
|
|
2360
|
+
return void 0;
|
|
2361
|
+
const seconds = Number(headerValue);
|
|
2362
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
2363
|
+
return seconds * 1e3;
|
|
2364
|
+
}
|
|
2365
|
+
const dateMs = Date.parse(headerValue);
|
|
2366
|
+
if (!Number.isNaN(dateMs)) {
|
|
2367
|
+
const delayMs = dateMs - Date.now();
|
|
2368
|
+
return delayMs > 0 ? delayMs : 0;
|
|
2369
|
+
}
|
|
2370
|
+
return void 0;
|
|
2371
|
+
}
|
|
2372
|
+
var CONTROL_CHARACTERS = /[\u0000-\u001F\u007F-\u009F]/g;
|
|
2373
|
+
function stripControls(value) {
|
|
2374
|
+
return value.replace(CONTROL_CHARACTERS, " ");
|
|
2375
|
+
}
|
|
2376
|
+
function readOwn(source, key) {
|
|
2377
|
+
return Object.hasOwn(source, key) ? source[key] : void 0;
|
|
2378
|
+
}
|
|
2379
|
+
function readSentence(source, key) {
|
|
2380
|
+
const value = readOwn(source, key);
|
|
2381
|
+
if (typeof value !== "string")
|
|
2382
|
+
return null;
|
|
2383
|
+
const cleaned = stripControls(value).trim();
|
|
2384
|
+
return cleaned === "" ? null : cleaned;
|
|
2385
|
+
}
|
|
2386
|
+
function safeDocsUrl(value) {
|
|
2387
|
+
if (typeof value !== "string")
|
|
2388
|
+
return null;
|
|
2389
|
+
let parsed;
|
|
2390
|
+
try {
|
|
2391
|
+
parsed = new URL(value);
|
|
2392
|
+
} catch {
|
|
2393
|
+
return null;
|
|
2394
|
+
}
|
|
2395
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
|
|
2396
|
+
return null;
|
|
2397
|
+
return parsed.href;
|
|
2398
|
+
}
|
|
2399
|
+
function formatApiErrorMessage(status, rawBody) {
|
|
2400
|
+
const verbatim = `getpeppr API error (${status}): ${stripControls(rawBody)}`;
|
|
2401
|
+
let parsed;
|
|
2402
|
+
try {
|
|
2403
|
+
parsed = JSON.parse(rawBody);
|
|
2404
|
+
} catch {
|
|
2405
|
+
return verbatim;
|
|
2406
|
+
}
|
|
2407
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
2408
|
+
return verbatim;
|
|
2409
|
+
}
|
|
2410
|
+
const sentence = readSentence(parsed, "message") ?? readSentence(parsed, "error");
|
|
2411
|
+
if (sentence === null)
|
|
2412
|
+
return verbatim;
|
|
2413
|
+
const link = safeDocsUrl(readOwn(parsed, "docs"));
|
|
2414
|
+
return `getpeppr API error (${status}): ${sentence}${link ? ` See ${link}` : ""}`;
|
|
2415
|
+
}
|
|
2416
|
+
function isRetryableError(error2) {
|
|
2417
|
+
if (error2 instanceof PeppolApiError) {
|
|
2418
|
+
return RETRYABLE_STATUS_CODES.has(error2.statusCode);
|
|
2419
|
+
}
|
|
2420
|
+
if (error2 instanceof Error && error2.name === "AbortError") {
|
|
2421
|
+
return true;
|
|
2422
|
+
}
|
|
2423
|
+
if (error2 instanceof TypeError && /fetch failed|network|ECONNREFUSED|ECONNRESET|ENOTFOUND|ETIMEDOUT/i.test(error2.message)) {
|
|
2424
|
+
return true;
|
|
2425
|
+
}
|
|
2426
|
+
return false;
|
|
2427
|
+
}
|
|
2428
|
+
var DEFAULT_BASE_URL = "https://api.getpeppr.dev/v1";
|
|
2429
|
+
var GetpepprAdapter = class {
|
|
2430
|
+
name = "getpeppr";
|
|
2431
|
+
baseUrl;
|
|
2432
|
+
apiKey;
|
|
2433
|
+
timeout;
|
|
2434
|
+
retryConfig;
|
|
2435
|
+
onRequest;
|
|
2436
|
+
onResponse;
|
|
2437
|
+
constructor(config) {
|
|
2438
|
+
this.apiKey = config.apiKey;
|
|
2439
|
+
this.timeout = config.timeout ?? 3e4;
|
|
2440
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
2441
|
+
this.retryConfig = {
|
|
2442
|
+
maxRetries: config.retry?.maxRetries ?? 3,
|
|
2443
|
+
initialDelayMs: config.retry?.initialDelayMs ?? 500,
|
|
2444
|
+
maxDelayMs: config.retry?.maxDelayMs ?? 3e4
|
|
2445
|
+
};
|
|
2446
|
+
this.onRequest = config.onRequest;
|
|
2447
|
+
this.onResponse = config.onResponse;
|
|
2448
|
+
}
|
|
2449
|
+
async request(method, path, body, extraHeaders) {
|
|
2450
|
+
const { maxRetries, initialDelayMs, maxDelayMs } = this.retryConfig;
|
|
2451
|
+
let lastError;
|
|
2452
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
2453
|
+
try {
|
|
2454
|
+
return await this.doRequest(method, path, body, extraHeaders);
|
|
2455
|
+
} catch (err) {
|
|
2456
|
+
lastError = err;
|
|
2457
|
+
const is429 = err instanceof PeppolApiError && err.statusCode === 429;
|
|
2458
|
+
const isSafeMethod = /^(GET|DELETE|HEAD)$/i.test(method);
|
|
2459
|
+
const hasIdempotencyKey = !!findHeaderCaseInsensitive(extraHeaders, "Idempotency-Key");
|
|
2460
|
+
const canRetry = is429 || isSafeMethod || hasIdempotencyKey;
|
|
2461
|
+
if (attempt < maxRetries && canRetry && isRetryableError(err)) {
|
|
2462
|
+
const retryAfterMs = err instanceof PeppolApiError ? err.retryAfterMs : void 0;
|
|
2463
|
+
await sleep(calculateRetryDelay(attempt, initialDelayMs, maxDelayMs, retryAfterMs));
|
|
2464
|
+
continue;
|
|
2465
|
+
}
|
|
2466
|
+
throw err;
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
throw lastError;
|
|
2470
|
+
}
|
|
2471
|
+
async doRequest(method, path, body, extraHeaders) {
|
|
2472
|
+
const url = `${this.baseUrl}${path}`;
|
|
2473
|
+
const controller = new AbortController();
|
|
2474
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2475
|
+
const requestHeaders = {
|
|
2476
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
1844
2477
|
"Content-Type": "application/json",
|
|
1845
2478
|
Accept: "application/json",
|
|
1846
2479
|
"User-Agent": `getpeppr-sdk/${SDK_VERSION}`,
|
|
@@ -1931,9 +2564,8 @@ var GetpepprAdapter = class {
|
|
|
1931
2564
|
clearTimeout(timeoutId);
|
|
1932
2565
|
}
|
|
1933
2566
|
}
|
|
1934
|
-
//
|
|
1935
|
-
//
|
|
1936
|
-
// { invoice: {...}, send_after_import: true }, createInvoice omits the flag.
|
|
2567
|
+
// sendInvoice and createInvoice hit the same endpoint. The latter adds the
|
|
2568
|
+
// legacy `_draft` marker, which the current gateway rejects explicitly.
|
|
1937
2569
|
async sendInvoice(input, options) {
|
|
1938
2570
|
const headers = {};
|
|
1939
2571
|
if (options?.idempotencyKey) {
|
|
@@ -2835,8 +3467,9 @@ var Peppol = class {
|
|
|
2835
3467
|
this.legalEntities = new LegalEntityOperations(this.adapter);
|
|
2836
3468
|
}
|
|
2837
3469
|
/**
|
|
2838
|
-
* Validate
|
|
2839
|
-
* Useful for pre-flight checks in your UI
|
|
3470
|
+
* Validate the structured JSON send payload without sending it.
|
|
3471
|
+
* Useful for pre-flight checks in your UI; provider-side normalization still
|
|
3472
|
+
* applies on send. `toXml()` adds the stricter direct-UBL builder checks.
|
|
2840
3473
|
*/
|
|
2841
3474
|
validate(input) {
|
|
2842
3475
|
return validateInvoice(input);
|
|
@@ -2846,14 +3479,39 @@ var Peppol = class {
|
|
|
2846
3479
|
* Useful for debugging or manual submission.
|
|
2847
3480
|
*/
|
|
2848
3481
|
toXml(input) {
|
|
2849
|
-
const
|
|
3482
|
+
const baseValidation = validateInvoice(input);
|
|
3483
|
+
const vatViolations = baseValidation.valid ? validateUblBuilderVat(input) : [];
|
|
3484
|
+
const validation = {
|
|
3485
|
+
valid: baseValidation.valid && vatViolations.every((item) => item.severity !== "error"),
|
|
3486
|
+
errors: [
|
|
3487
|
+
...baseValidation.errors,
|
|
3488
|
+
...vatViolations.filter((item) => item.severity === "error").map(({ field, message, ruleId }) => ({
|
|
3489
|
+
field: field ?? "invoice",
|
|
3490
|
+
message,
|
|
3491
|
+
ruleId: ruleId === "SDK-INPUT" ? void 0 : ruleId
|
|
3492
|
+
}))
|
|
3493
|
+
],
|
|
3494
|
+
warnings: baseValidation.warnings
|
|
3495
|
+
};
|
|
2850
3496
|
if (!validation.valid) {
|
|
2851
3497
|
throw new PeppolValidationError(`Invoice validation failed: ${validation.errors.map((e) => e.message).join("; ")}`, validation);
|
|
2852
3498
|
}
|
|
2853
|
-
|
|
2854
|
-
|
|
3499
|
+
try {
|
|
3500
|
+
if (input.isCreditNote) {
|
|
3501
|
+
return buildCreditNoteXml(input);
|
|
3502
|
+
}
|
|
3503
|
+
return buildInvoiceXml(input);
|
|
3504
|
+
} catch (error2) {
|
|
3505
|
+
if (error2 instanceof UblBuilderInputError) {
|
|
3506
|
+
const builderValidation = {
|
|
3507
|
+
valid: false,
|
|
3508
|
+
errors: [{ field: error2.field, message: error2.message, ruleId: error2.ruleId }],
|
|
3509
|
+
warnings: validation.warnings
|
|
3510
|
+
};
|
|
3511
|
+
throw new PeppolValidationError(`Invoice validation failed: ${error2.message}`, builderValidation);
|
|
3512
|
+
}
|
|
3513
|
+
throw error2;
|
|
2855
3514
|
}
|
|
2856
|
-
return buildInvoiceXml(input);
|
|
2857
3515
|
}
|
|
2858
3516
|
};
|
|
2859
3517
|
async function* paginate(fetchPage, options) {
|
|
@@ -2871,22 +3529,30 @@ async function* paginate(fetchPage, options) {
|
|
|
2871
3529
|
offset += page.data.length;
|
|
2872
3530
|
}
|
|
2873
3531
|
}
|
|
3532
|
+
function toGatewayInvoiceInput(input) {
|
|
3533
|
+
const stripReason = (item) => {
|
|
3534
|
+
const { taxExemptReason: _builderOnly, ...gatewayItem } = item;
|
|
3535
|
+
return gatewayItem;
|
|
3536
|
+
};
|
|
3537
|
+
return {
|
|
3538
|
+
...input,
|
|
3539
|
+
lines: input.lines.map(stripReason),
|
|
3540
|
+
...input.allowances ? { allowances: input.allowances.map(stripReason) } : {},
|
|
3541
|
+
...input.charges ? { charges: input.charges.map(stripReason) } : {}
|
|
3542
|
+
};
|
|
3543
|
+
}
|
|
2874
3544
|
var InvoiceOperations = class {
|
|
2875
3545
|
adapter;
|
|
2876
3546
|
constructor(adapter) {
|
|
2877
3547
|
this.adapter = adapter;
|
|
2878
3548
|
}
|
|
2879
3549
|
/**
|
|
2880
|
-
*
|
|
2881
|
-
* Validates input client-side, then creates the invoice via the gateway.
|
|
2882
|
-
* Use `sendById()` to send the draft when ready.
|
|
3550
|
+
* Request draft creation from the gateway.
|
|
2883
3551
|
*
|
|
2884
|
-
* @
|
|
2885
|
-
*
|
|
2886
|
-
*
|
|
2887
|
-
*
|
|
2888
|
-
* await peppol.invoices.sendById(draft.id);
|
|
2889
|
-
* ```
|
|
3552
|
+
* @deprecated The current Storecove-backed gateway does not support drafts
|
|
3553
|
+
* and returns 422 `drafts_not_supported`. Submit the final document with
|
|
3554
|
+
* `invoices.send()` instead.
|
|
3555
|
+
* @throws {PeppolApiError} 422 with code `drafts_not_supported`
|
|
2890
3556
|
*/
|
|
2891
3557
|
async create(input, options) {
|
|
2892
3558
|
const validation = validateInvoice(input);
|
|
@@ -2894,21 +3560,18 @@ var InvoiceOperations = class {
|
|
|
2894
3560
|
throw new PeppolValidationError(`Invoice validation failed:
|
|
2895
3561
|
${validation.errors.map((e) => ` - ${e.field}: ${e.message}${e.suggestion ? ` (${e.suggestion})` : ""}`).join("\n")}`, validation);
|
|
2896
3562
|
}
|
|
2897
|
-
const result = await this.adapter.createInvoice(input, options);
|
|
3563
|
+
const result = await this.adapter.createInvoice(toGatewayInvoiceInput(input), options);
|
|
2898
3564
|
if (validation.warnings.length > 0) {
|
|
2899
3565
|
result.warnings = validation.warnings;
|
|
2900
3566
|
}
|
|
2901
3567
|
return result;
|
|
2902
3568
|
}
|
|
2903
3569
|
/**
|
|
2904
|
-
*
|
|
2905
|
-
* The invoice must have been previously created with `create()`.
|
|
3570
|
+
* Request sending of an existing draft invoice by ID.
|
|
2906
3571
|
*
|
|
2907
|
-
* @
|
|
2908
|
-
*
|
|
2909
|
-
*
|
|
2910
|
-
* ```
|
|
2911
|
-
* @throws {PeppolApiError} 501 if the gateway provider does not support draft sending
|
|
3572
|
+
* @deprecated The current Storecove-backed gateway has no draft lifecycle and
|
|
3573
|
+
* always returns 501. Submit the final document with `invoices.send()`.
|
|
3574
|
+
* @throws {PeppolApiError} 501 with the current gateway provider
|
|
2912
3575
|
*/
|
|
2913
3576
|
async sendById(id) {
|
|
2914
3577
|
return this.adapter.sendInvoiceById(id);
|
|
@@ -2934,7 +3597,7 @@ ${validation.errors.map((e) => ` - ${e.field}: ${e.message}${e.suggestion ? ` (
|
|
|
2934
3597
|
throw new PeppolValidationError(`Invoice validation failed:
|
|
2935
3598
|
${validation.errors.map((e) => ` - ${e.field}: ${e.message}${e.suggestion ? ` (${e.suggestion})` : ""}`).join("\n")}`, validation);
|
|
2936
3599
|
}
|
|
2937
|
-
const result = await this.adapter.sendInvoice(input, options);
|
|
3600
|
+
const result = await this.adapter.sendInvoice(toGatewayInvoiceInput(input), options);
|
|
2938
3601
|
if (validation.warnings.length > 0) {
|
|
2939
3602
|
result.warnings = validation.warnings;
|
|
2940
3603
|
}
|
|
@@ -3079,52 +3742,39 @@ ${validation.errors.map((e) => ` - ${e.field}: ${e.message}${e.suggestion ? ` (
|
|
|
3079
3742
|
return this.adapter.importInvoice(options);
|
|
3080
3743
|
}
|
|
3081
3744
|
/**
|
|
3082
|
-
*
|
|
3745
|
+
* Request acknowledgement of a received invoice.
|
|
3083
3746
|
*
|
|
3084
|
-
* @
|
|
3085
|
-
*
|
|
3086
|
-
*
|
|
3087
|
-
* console.log(result.status); // "accepted"
|
|
3088
|
-
* ```
|
|
3089
|
-
* @throws {PeppolApiError} 501 if the gateway provider does not support acknowledgement
|
|
3747
|
+
* @deprecated The current Storecove-backed gateway does not support
|
|
3748
|
+
* acknowledgement and always returns 501.
|
|
3749
|
+
* @throws {PeppolApiError} 501 with the current gateway provider
|
|
3090
3750
|
*/
|
|
3091
3751
|
async acknowledge(id) {
|
|
3092
3752
|
return this.adapter.acknowledgeInvoice(id);
|
|
3093
3753
|
}
|
|
3094
3754
|
/**
|
|
3095
|
-
*
|
|
3096
|
-
* Only include the fields you want to change — partial updates are supported.
|
|
3755
|
+
* Request an update to an existing invoice.
|
|
3097
3756
|
*
|
|
3098
|
-
* @
|
|
3099
|
-
*
|
|
3100
|
-
*
|
|
3101
|
-
* dueDate: "2026-04-01",
|
|
3102
|
-
* lines: [
|
|
3103
|
-
* { id: "line-1", quantity: 5 },
|
|
3104
|
-
* { id: "line-2", _destroy: true },
|
|
3105
|
-
* ],
|
|
3106
|
-
* });
|
|
3107
|
-
* ```
|
|
3108
|
-
* @throws {PeppolApiError} 501 if the gateway provider does not support invoice updates
|
|
3757
|
+
* @deprecated Storecove documents are immutable after submission. The
|
|
3758
|
+
* current gateway always returns 501; issue a credit note instead.
|
|
3759
|
+
* @throws {PeppolApiError} 501 with the current gateway provider
|
|
3109
3760
|
*/
|
|
3110
3761
|
async update(id, input) {
|
|
3111
3762
|
return this.adapter.updateInvoice(id, input);
|
|
3112
3763
|
}
|
|
3113
3764
|
/**
|
|
3114
|
-
*
|
|
3765
|
+
* Request deletion of an invoice.
|
|
3115
3766
|
*
|
|
3116
|
-
* @
|
|
3117
|
-
*
|
|
3118
|
-
*
|
|
3119
|
-
* ```
|
|
3120
|
-
* @throws {PeppolApiError} 501 if the gateway provider does not support invoice deletion
|
|
3767
|
+
* @deprecated The current Storecove-backed gateway does not support invoice
|
|
3768
|
+
* deletion and always returns 501.
|
|
3769
|
+
* @throws {PeppolApiError} 501 with the current gateway provider
|
|
3121
3770
|
*/
|
|
3122
3771
|
async delete(id) {
|
|
3123
3772
|
return this.adapter.deleteInvoice(id);
|
|
3124
3773
|
}
|
|
3125
3774
|
/**
|
|
3126
|
-
*
|
|
3127
|
-
*
|
|
3775
|
+
* Report a French CTC invoice as paid.
|
|
3776
|
+
* Other state transitions are retained for API compatibility but the current
|
|
3777
|
+
* Storecove-backed gateway returns 501 for them.
|
|
3128
3778
|
*
|
|
3129
3779
|
* `"paid"` on a French CTC invoice reports the payment collection
|
|
3130
3780
|
* (« signalement d'encaissement ») to the tax authority via the gateway —
|
|
@@ -3243,7 +3893,7 @@ var CreditNoteOperations = class {
|
|
|
3243
3893
|
throw new PeppolValidationError(`Credit note validation failed:
|
|
3244
3894
|
${validation.errors.map((e) => ` - ${e.field}: ${e.message}`).join("\n")}`, validation);
|
|
3245
3895
|
}
|
|
3246
|
-
return this.adapter.sendInvoice(invoiceInput);
|
|
3896
|
+
return this.adapter.sendInvoice(toGatewayInvoiceInput(invoiceInput));
|
|
3247
3897
|
}
|
|
3248
3898
|
};
|
|
3249
3899
|
var DirectoryOperations = class {
|
|
@@ -3559,523 +4209,179 @@ var LegalEntityOperations = class {
|
|
|
3559
4209
|
* an organisation admin starts the platform sandbox trial from the console
|
|
3560
4210
|
* overview and creates a sandbox master key at
|
|
3561
4211
|
* https://console.getpeppr.dev/api-keys; production platform access is set
|
|
3562
|
-
* up with our team (hello@getpeppr.dev).
|
|
3563
|
-
*
|
|
3564
|
-
* Transient failures are NOT auto-retried unless you pass `options.idempotencyKey`;
|
|
3565
|
-
* re-issuing mints a fresh token, so a retried call is safe.
|
|
3566
|
-
*
|
|
3567
|
-
* @example
|
|
3568
|
-
* ```ts
|
|
3569
|
-
* await peppol.legalEntities.requestAttestation(le.id, { contactEmail: "owner@acme.example" });
|
|
3570
|
-
* ```
|
|
3571
|
-
*/
|
|
3572
|
-
async requestAttestation(id, input, options) {
|
|
3573
|
-
return this.adapter.requestLegalEntityAttestation(id, input, options);
|
|
3574
|
-
}
|
|
3575
|
-
};
|
|
3576
|
-
var BankAccountOperations = class {
|
|
3577
|
-
adapter;
|
|
3578
|
-
constructor(adapter) {
|
|
3579
|
-
this.adapter = adapter;
|
|
3580
|
-
}
|
|
3581
|
-
/**
|
|
3582
|
-
* List bank accounts with optional pagination.
|
|
3583
|
-
*
|
|
3584
|
-
* @example
|
|
3585
|
-
* ```ts
|
|
3586
|
-
* const result = await peppol.bankAccounts.list({ limit: 10 });
|
|
3587
|
-
* console.log(result.data, result.meta);
|
|
3588
|
-
* ```
|
|
3589
|
-
*/
|
|
3590
|
-
async list(options) {
|
|
3591
|
-
return this.adapter.listBankAccounts(options);
|
|
3592
|
-
}
|
|
3593
|
-
/**
|
|
3594
|
-
* Get a single bank account by ID.
|
|
3595
|
-
*
|
|
3596
|
-
* @example
|
|
3597
|
-
* ```ts
|
|
3598
|
-
* const account = await peppol.bankAccounts.get("123");
|
|
3599
|
-
* console.log(account.name, account.iban);
|
|
3600
|
-
* ```
|
|
3601
|
-
*/
|
|
3602
|
-
async get(id) {
|
|
3603
|
-
return this.adapter.getBankAccount(id);
|
|
3604
|
-
}
|
|
3605
|
-
/**
|
|
3606
|
-
* Create a new bank account.
|
|
3607
|
-
*
|
|
3608
|
-
* @example
|
|
3609
|
-
* ```ts
|
|
3610
|
-
* const account = await peppol.bankAccounts.create({
|
|
3611
|
-
* name: "Main Account",
|
|
3612
|
-
* iban: "BE68539007547034",
|
|
3613
|
-
* bic: "BBRUBEBB",
|
|
3614
|
-
* country: "BE",
|
|
3615
|
-
* });
|
|
3616
|
-
* ```
|
|
3617
|
-
*/
|
|
3618
|
-
async create(input) {
|
|
3619
|
-
return this.adapter.createBankAccount(input);
|
|
3620
|
-
}
|
|
3621
|
-
/**
|
|
3622
|
-
* Update an existing bank account.
|
|
3623
|
-
*
|
|
3624
|
-
* @example
|
|
3625
|
-
* ```ts
|
|
3626
|
-
* const updated = await peppol.bankAccounts.update("123", { name: "Updated Name" });
|
|
3627
|
-
* ```
|
|
3628
|
-
*/
|
|
3629
|
-
async update(id, input) {
|
|
3630
|
-
return this.adapter.updateBankAccount(id, input);
|
|
3631
|
-
}
|
|
3632
|
-
/**
|
|
3633
|
-
* Delete a bank account.
|
|
3634
|
-
*
|
|
3635
|
-
* @example
|
|
3636
|
-
* ```ts
|
|
3637
|
-
* await peppol.bankAccounts.delete("123");
|
|
3638
|
-
* ```
|
|
3639
|
-
*/
|
|
3640
|
-
async delete(id) {
|
|
3641
|
-
return this.adapter.deleteBankAccount(id);
|
|
3642
|
-
}
|
|
3643
|
-
/**
|
|
3644
|
-
* Async iterator over all bank accounts, automatically handling pagination.
|
|
4212
|
+
* up with our team (hello@getpeppr.dev).
|
|
4213
|
+
*
|
|
4214
|
+
* Transient failures are NOT auto-retried unless you pass `options.idempotencyKey`;
|
|
4215
|
+
* re-issuing mints a fresh token, so a retried call is safe.
|
|
3645
4216
|
*
|
|
3646
4217
|
* @example
|
|
3647
4218
|
* ```ts
|
|
3648
|
-
*
|
|
3649
|
-
* console.log(account.name, account.iban);
|
|
3650
|
-
* }
|
|
4219
|
+
* await peppol.legalEntities.requestAttestation(le.id, { contactEmail: "owner@acme.example" });
|
|
3651
4220
|
* ```
|
|
3652
4221
|
*/
|
|
3653
|
-
|
|
3654
|
-
return
|
|
4222
|
+
async requestAttestation(id, input, options) {
|
|
4223
|
+
return this.adapter.requestLegalEntityAttestation(id, input, options);
|
|
3655
4224
|
}
|
|
3656
4225
|
};
|
|
3657
|
-
var
|
|
4226
|
+
var BankAccountOperations = class {
|
|
3658
4227
|
adapter;
|
|
3659
4228
|
constructor(adapter) {
|
|
3660
4229
|
this.adapter = adapter;
|
|
3661
4230
|
}
|
|
3662
4231
|
/**
|
|
3663
|
-
* List
|
|
3664
|
-
* Returns global transport types (not account-scoped).
|
|
3665
|
-
*
|
|
3666
|
-
* @example
|
|
3667
|
-
* ```ts
|
|
3668
|
-
* const types = await peppol.transports.listTypes();
|
|
3669
|
-
* console.log(types); // [{ code: "peppol", name: "Peppol BIS 3.0" }, ...]
|
|
3670
|
-
* ```
|
|
3671
|
-
*/
|
|
3672
|
-
async listTypes() {
|
|
3673
|
-
return this.adapter.listTransportTypes();
|
|
3674
|
-
}
|
|
3675
|
-
/**
|
|
3676
|
-
* List configured transports for this account.
|
|
4232
|
+
* List bank accounts with optional pagination.
|
|
3677
4233
|
*
|
|
3678
4234
|
* @example
|
|
3679
4235
|
* ```ts
|
|
3680
|
-
* const
|
|
3681
|
-
* console.log(
|
|
4236
|
+
* const result = await peppol.bankAccounts.list({ limit: 10 });
|
|
4237
|
+
* console.log(result.data, result.meta);
|
|
3682
4238
|
* ```
|
|
3683
4239
|
*/
|
|
3684
|
-
async list() {
|
|
3685
|
-
return this.adapter.
|
|
4240
|
+
async list(options) {
|
|
4241
|
+
return this.adapter.listBankAccounts(options);
|
|
3686
4242
|
}
|
|
3687
4243
|
/**
|
|
3688
|
-
* Get a single
|
|
4244
|
+
* Get a single bank account by ID.
|
|
3689
4245
|
*
|
|
3690
4246
|
* @example
|
|
3691
4247
|
* ```ts
|
|
3692
|
-
* const
|
|
4248
|
+
* const account = await peppol.bankAccounts.get("123");
|
|
4249
|
+
* console.log(account.name, account.iban);
|
|
3693
4250
|
* ```
|
|
3694
4251
|
*/
|
|
3695
|
-
async get(
|
|
3696
|
-
return this.adapter.
|
|
4252
|
+
async get(id) {
|
|
4253
|
+
return this.adapter.getBankAccount(id);
|
|
3697
4254
|
}
|
|
3698
4255
|
/**
|
|
3699
|
-
* Create a new
|
|
4256
|
+
* Create a new bank account.
|
|
3700
4257
|
*
|
|
3701
4258
|
* @example
|
|
3702
4259
|
* ```ts
|
|
3703
|
-
* const
|
|
3704
|
-
*
|
|
3705
|
-
*
|
|
4260
|
+
* const account = await peppol.bankAccounts.create({
|
|
4261
|
+
* name: "Main Account",
|
|
4262
|
+
* iban: "BE68539007547034",
|
|
4263
|
+
* bic: "BBRUBEBB",
|
|
4264
|
+
* country: "BE",
|
|
3706
4265
|
* });
|
|
3707
4266
|
* ```
|
|
3708
4267
|
*/
|
|
3709
4268
|
async create(input) {
|
|
3710
|
-
return this.adapter.
|
|
3711
|
-
}
|
|
3712
|
-
/**
|
|
3713
|
-
* Update an existing transport.
|
|
3714
|
-
*
|
|
3715
|
-
* @example
|
|
3716
|
-
* ```ts
|
|
3717
|
-
* const transport = await peppol.transports.update("peppol", { email: "new@acme.com" });
|
|
3718
|
-
* ```
|
|
3719
|
-
*/
|
|
3720
|
-
async update(code, input) {
|
|
3721
|
-
return this.adapter.updateTransport(code, input);
|
|
4269
|
+
return this.adapter.createBankAccount(input);
|
|
3722
4270
|
}
|
|
3723
4271
|
/**
|
|
3724
|
-
*
|
|
4272
|
+
* Update an existing bank account.
|
|
3725
4273
|
*
|
|
3726
4274
|
* @example
|
|
3727
4275
|
* ```ts
|
|
3728
|
-
* await peppol.
|
|
3729
|
-
* ```
|
|
3730
|
-
*/
|
|
3731
|
-
async
|
|
3732
|
-
return this.adapter.
|
|
3733
|
-
}
|
|
3734
|
-
};
|
|
3735
|
-
|
|
3736
|
-
// ../sdk/dist/core/schematron.js
|
|
3737
|
-
function violation(ruleId, severity, message, field) {
|
|
3738
|
-
return { ruleId, severity, message, field };
|
|
3739
|
-
}
|
|
3740
|
-
var _knownUnitCodes;
|
|
3741
|
-
function getKnownUnitCodes() {
|
|
3742
|
-
if (!_knownUnitCodes) {
|
|
3743
|
-
_knownUnitCodes = new Set(getAllUnits().map((u) => u.code));
|
|
3744
|
-
}
|
|
3745
|
-
return _knownUnitCodes;
|
|
3746
|
-
}
|
|
3747
|
-
var VALID_VAT_CATEGORIES = /* @__PURE__ */ new Set(["S", "Z", "E", "AE", "K", "G", "O", "L", "M", "B"]);
|
|
3748
|
-
var SENDABLE_VAT_CATEGORIES = ["S", "Z", "E", "AE", "K", "G", "O"];
|
|
3749
|
-
var UNROUTABLE_VAT_CATEGORIES = /* @__PURE__ */ new Set(["L", "M", "B"]);
|
|
3750
|
-
function computeLineNet(line) {
|
|
3751
|
-
const baseQty = line.baseQuantity ?? 1;
|
|
3752
|
-
if (baseQty === 0)
|
|
3753
|
-
return NaN;
|
|
3754
|
-
const baseAmount = line.quantity * line.unitPrice / baseQty;
|
|
3755
|
-
const chargeTotal = (line.charges ?? []).reduce((sum, c) => sum + c.amount, 0);
|
|
3756
|
-
const allowanceTotal = (line.allowances ?? []).reduce((sum, a) => sum + a.amount, 0);
|
|
3757
|
-
return baseAmount + chargeTotal - allowanceTotal;
|
|
3758
|
-
}
|
|
3759
|
-
var br02 = (input) => {
|
|
3760
|
-
if (!input.number?.trim()) {
|
|
3761
|
-
return [violation("BR-02", "error", "Invoice number is required.", "number")];
|
|
3762
|
-
}
|
|
3763
|
-
return [];
|
|
3764
|
-
};
|
|
3765
|
-
var br03 = (input) => {
|
|
3766
|
-
if (!input.date) {
|
|
3767
|
-
return [
|
|
3768
|
-
violation("BR-03", "warning", "Invoice issue date is not set. The SDK will default to today's date.", "date")
|
|
3769
|
-
];
|
|
3770
|
-
}
|
|
3771
|
-
return [];
|
|
3772
|
-
};
|
|
3773
|
-
var br06 = (input) => {
|
|
3774
|
-
if (input.from && !input.from.vatNumber) {
|
|
3775
|
-
return [
|
|
3776
|
-
violation("BR-06", "warning", "Seller party has no VAT number. The gateway will use the account's VAT registration.", "from.vatNumber")
|
|
3777
|
-
];
|
|
3778
|
-
}
|
|
3779
|
-
return [];
|
|
3780
|
-
};
|
|
3781
|
-
var br07 = (input) => {
|
|
3782
|
-
if (!input.to?.name?.trim()) {
|
|
3783
|
-
return [violation("BR-07", "error", "Buyer name is required.", "to.name")];
|
|
3784
|
-
}
|
|
3785
|
-
return [];
|
|
3786
|
-
};
|
|
3787
|
-
var br08 = (input) => {
|
|
3788
|
-
if (!input.lines || input.lines.length === 0) {
|
|
3789
|
-
return [violation("BR-08", "error", "Invoice must have at least one line item.", "lines")];
|
|
3790
|
-
}
|
|
3791
|
-
return [];
|
|
3792
|
-
};
|
|
3793
|
-
var br09 = (input) => {
|
|
3794
|
-
if (!input.dueDate && !input.paymentTerms) {
|
|
3795
|
-
return [
|
|
3796
|
-
violation("BR-09", "warning", "Neither due date nor payment terms specified. At least one is recommended.", "dueDate")
|
|
3797
|
-
];
|
|
3798
|
-
}
|
|
3799
|
-
return [];
|
|
3800
|
-
};
|
|
3801
|
-
var br10 = (input) => {
|
|
3802
|
-
if (!input.buyerReference && !input.orderReference) {
|
|
3803
|
-
return [
|
|
3804
|
-
violation("BR-10", "warning", "Neither buyerReference nor orderReference specified. Peppol BIS 3.0 requires at least one.", "buyerReference")
|
|
3805
|
-
];
|
|
3806
|
-
}
|
|
3807
|
-
return [];
|
|
3808
|
-
};
|
|
3809
|
-
var brCo10 = (input) => {
|
|
3810
|
-
const violations = [];
|
|
3811
|
-
if (!input.lines)
|
|
3812
|
-
return violations;
|
|
3813
|
-
for (let i = 0; i < input.lines.length; i++) {
|
|
3814
|
-
const line = input.lines[i];
|
|
3815
|
-
const net = computeLineNet(line);
|
|
3816
|
-
if (!Number.isFinite(net)) {
|
|
3817
|
-
const baseQty = line.baseQuantity ?? 1;
|
|
3818
|
-
const detail = baseQty === 0 ? "baseQuantity is 0, causing division by zero." : "Computed line amount is not a finite number.";
|
|
3819
|
-
violations.push(violation("BR-CO-10", "error", `Line ${i}: invalid net amount. ${detail}`, `lines[${i}]`));
|
|
3820
|
-
}
|
|
3821
|
-
}
|
|
3822
|
-
return violations;
|
|
3823
|
-
};
|
|
3824
|
-
var brCo13 = (input) => {
|
|
3825
|
-
if (!input.lines || input.lines.length === 0)
|
|
3826
|
-
return [];
|
|
3827
|
-
let totalVat = 0;
|
|
3828
|
-
for (let i = 0; i < input.lines.length; i++) {
|
|
3829
|
-
const line = input.lines[i];
|
|
3830
|
-
const net = computeLineNet(line);
|
|
3831
|
-
if (!Number.isFinite(net))
|
|
3832
|
-
continue;
|
|
3833
|
-
totalVat += net * (line.vatRate / 100);
|
|
3834
|
-
}
|
|
3835
|
-
for (const allowance of input.allowances ?? []) {
|
|
3836
|
-
totalVat -= allowance.amount * (allowance.vatRate / 100);
|
|
3837
|
-
}
|
|
3838
|
-
for (const charge of input.charges ?? []) {
|
|
3839
|
-
totalVat += charge.amount * (charge.vatRate / 100);
|
|
3840
|
-
}
|
|
3841
|
-
if (!Number.isFinite(totalVat)) {
|
|
3842
|
-
return [
|
|
3843
|
-
violation("BR-CO-13", "error", "Computed total VAT amount is not a finite number. Check line amounts and VAT rates.")
|
|
3844
|
-
];
|
|
3845
|
-
}
|
|
3846
|
-
if (totalVat < -0.01) {
|
|
3847
|
-
return [
|
|
3848
|
-
violation("BR-CO-13", "warning", `Computed total VAT is negative (${totalVat.toFixed(2)}). This is unusual for an invoice.`)
|
|
3849
|
-
];
|
|
3850
|
-
}
|
|
3851
|
-
return [];
|
|
3852
|
-
};
|
|
3853
|
-
var brCo15 = (input) => {
|
|
3854
|
-
if (!input.lines || input.lines.length === 0)
|
|
3855
|
-
return [];
|
|
3856
|
-
let lineTotal = 0;
|
|
3857
|
-
let vatTotal = 0;
|
|
3858
|
-
for (const line of input.lines) {
|
|
3859
|
-
const net = computeLineNet(line);
|
|
3860
|
-
if (!Number.isFinite(net))
|
|
3861
|
-
continue;
|
|
3862
|
-
lineTotal += net;
|
|
3863
|
-
vatTotal += net * (line.vatRate / 100);
|
|
3864
|
-
}
|
|
3865
|
-
for (const allowance of input.allowances ?? []) {
|
|
3866
|
-
lineTotal -= allowance.amount;
|
|
3867
|
-
vatTotal -= allowance.amount * (allowance.vatRate / 100);
|
|
3868
|
-
}
|
|
3869
|
-
for (const charge of input.charges ?? []) {
|
|
3870
|
-
lineTotal += charge.amount;
|
|
3871
|
-
vatTotal += charge.amount * (charge.vatRate / 100);
|
|
3872
|
-
}
|
|
3873
|
-
const taxInclusive = lineTotal + vatTotal;
|
|
3874
|
-
if (!Number.isFinite(taxInclusive)) {
|
|
3875
|
-
return [
|
|
3876
|
-
violation("BR-CO-15", "error", "Computed tax-inclusive amount is not a finite number.")
|
|
3877
|
-
];
|
|
3878
|
-
}
|
|
3879
|
-
if (taxInclusive < -0.01) {
|
|
3880
|
-
return [
|
|
3881
|
-
violation("BR-CO-15", "warning", `Computed tax-inclusive amount is negative (${taxInclusive.toFixed(2)}). Consider using a credit note instead.`)
|
|
3882
|
-
];
|
|
3883
|
-
}
|
|
3884
|
-
return [];
|
|
3885
|
-
};
|
|
3886
|
-
var brCo16 = (input) => {
|
|
3887
|
-
if (!input.lines || input.lines.length === 0)
|
|
3888
|
-
return [];
|
|
3889
|
-
let lineTotal = 0;
|
|
3890
|
-
let vatTotal = 0;
|
|
3891
|
-
for (const line of input.lines) {
|
|
3892
|
-
const net = computeLineNet(line);
|
|
3893
|
-
if (!Number.isFinite(net))
|
|
3894
|
-
continue;
|
|
3895
|
-
lineTotal += net;
|
|
3896
|
-
vatTotal += net * (line.vatRate / 100);
|
|
3897
|
-
}
|
|
3898
|
-
for (const allowance of input.allowances ?? []) {
|
|
3899
|
-
lineTotal -= allowance.amount;
|
|
3900
|
-
vatTotal -= allowance.amount * (allowance.vatRate / 100);
|
|
3901
|
-
}
|
|
3902
|
-
for (const charge of input.charges ?? []) {
|
|
3903
|
-
lineTotal += charge.amount;
|
|
3904
|
-
vatTotal += charge.amount * (charge.vatRate / 100);
|
|
3905
|
-
}
|
|
3906
|
-
const taxInclusive = lineTotal + vatTotal;
|
|
3907
|
-
const prepaid = input.prepaidAmount ?? 0;
|
|
3908
|
-
const rounding = input.roundingAmount ?? 0;
|
|
3909
|
-
const payable = taxInclusive - prepaid + rounding;
|
|
3910
|
-
if (!Number.isFinite(payable)) {
|
|
3911
|
-
return [
|
|
3912
|
-
violation("BR-CO-16", "error", "Computed payable amount is not a finite number.")
|
|
3913
|
-
];
|
|
4276
|
+
* const updated = await peppol.bankAccounts.update("123", { name: "Updated Name" });
|
|
4277
|
+
* ```
|
|
4278
|
+
*/
|
|
4279
|
+
async update(id, input) {
|
|
4280
|
+
return this.adapter.updateBankAccount(id, input);
|
|
3914
4281
|
}
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
4282
|
+
/**
|
|
4283
|
+
* Delete a bank account.
|
|
4284
|
+
*
|
|
4285
|
+
* @example
|
|
4286
|
+
* ```ts
|
|
4287
|
+
* await peppol.bankAccounts.delete("123");
|
|
4288
|
+
* ```
|
|
4289
|
+
*/
|
|
4290
|
+
async delete(id) {
|
|
4291
|
+
return this.adapter.deleteBankAccount(id);
|
|
3919
4292
|
}
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
}
|
|
4293
|
+
/**
|
|
4294
|
+
* Async iterator over all bank accounts, automatically handling pagination.
|
|
4295
|
+
*
|
|
4296
|
+
* @example
|
|
4297
|
+
* ```ts
|
|
4298
|
+
* for await (const account of peppol.bankAccounts.listAll()) {
|
|
4299
|
+
* console.log(account.name, account.iban);
|
|
4300
|
+
* }
|
|
4301
|
+
* ```
|
|
4302
|
+
*/
|
|
4303
|
+
listAll(options) {
|
|
4304
|
+
return paginate((offset, limit) => this.adapter.listBankAccounts({ ...options, offset, limit }), options);
|
|
3932
4305
|
}
|
|
3933
|
-
return violations;
|
|
3934
4306
|
};
|
|
3935
|
-
var
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
for (let i = 0; i < input.lines.length; i++) {
|
|
3940
|
-
const line = input.lines[i];
|
|
3941
|
-
if (line.vatCategory === "Z" && line.vatRate !== 0) {
|
|
3942
|
-
violations.push(violation("BR-Z-05", "error", `Line ${i}: zero-rated (Z) requires vatRate = 0, got ${line.vatRate}.`, `lines[${i}].vatRate`));
|
|
3943
|
-
}
|
|
4307
|
+
var TransportOperations = class {
|
|
4308
|
+
adapter;
|
|
4309
|
+
constructor(adapter) {
|
|
4310
|
+
this.adapter = adapter;
|
|
3944
4311
|
}
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
4312
|
+
/**
|
|
4313
|
+
* List all available transport types in the network.
|
|
4314
|
+
* Returns global transport types (not account-scoped).
|
|
4315
|
+
*
|
|
4316
|
+
* @example
|
|
4317
|
+
* ```ts
|
|
4318
|
+
* const types = await peppol.transports.listTypes();
|
|
4319
|
+
* console.log(types); // [{ code: "peppol", name: "Peppol BIS 3.0" }, ...]
|
|
4320
|
+
* ```
|
|
4321
|
+
*/
|
|
4322
|
+
async listTypes() {
|
|
4323
|
+
return this.adapter.listTransportTypes();
|
|
3956
4324
|
}
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
4325
|
+
/**
|
|
4326
|
+
* List configured transports for this account.
|
|
4327
|
+
*
|
|
4328
|
+
* @example
|
|
4329
|
+
* ```ts
|
|
4330
|
+
* const transports = await peppol.transports.list();
|
|
4331
|
+
* console.log(transports); // [{ id: "t-1", transportTypeCode: "peppol", name: "..." }, ...]
|
|
4332
|
+
* ```
|
|
4333
|
+
*/
|
|
4334
|
+
async list() {
|
|
4335
|
+
return this.adapter.listTransports();
|
|
3968
4336
|
}
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
4337
|
+
/**
|
|
4338
|
+
* Get a single transport by code.
|
|
4339
|
+
*
|
|
4340
|
+
* @example
|
|
4341
|
+
* ```ts
|
|
4342
|
+
* const transport = await peppol.transports.get("peppol");
|
|
4343
|
+
* ```
|
|
4344
|
+
*/
|
|
4345
|
+
async get(code) {
|
|
4346
|
+
return this.adapter.getTransport(code);
|
|
3976
4347
|
}
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
if (UNROUTABLE_VAT_CATEGORIES.has(cat)) {
|
|
3991
|
-
violations.push(violation("unsupported_vat_category", "error", `${label}: VAT category "${echo(cat)}" is valid under EN 16931 but getpeppr cannot route it \u2014 our provider has no vocabulary for it. Sendable categories: ${sendable}.`, field));
|
|
3992
|
-
}
|
|
3993
|
-
};
|
|
3994
|
-
(input.lines ?? []).forEach((line, i) => check(line.vatCategory, `lines[${i}].vatCategory`, `Line ${i}`));
|
|
3995
|
-
(input.allowances ?? []).forEach((a, i) => check(a.vatCategory, `allowances[${i}].vatCategory`, `Allowance ${i}`));
|
|
3996
|
-
(input.charges ?? []).forEach((c, i) => check(c.vatCategory, `charges[${i}].vatCategory`, `Charge ${i}`));
|
|
3997
|
-
return violations;
|
|
3998
|
-
};
|
|
3999
|
-
var peppolR080 = (input) => {
|
|
4000
|
-
const violations = [];
|
|
4001
|
-
if (!input.lines)
|
|
4002
|
-
return violations;
|
|
4003
|
-
const knownCodes = getKnownUnitCodes();
|
|
4004
|
-
for (let i = 0; i < input.lines.length; i++) {
|
|
4005
|
-
const line = input.lines[i];
|
|
4006
|
-
if (line.unit) {
|
|
4007
|
-
const resolved = resolveUnit(line.unit);
|
|
4008
|
-
if (!knownCodes.has(resolved)) {
|
|
4009
|
-
violations.push(violation("PEPPOL-EN16931-R080", "warning", `Line ${i}: unit "${line.unit}" (resolved: "${resolved}") is not a known UN/ECE Rec20 code. Common codes: EA, HUR, DAY, KGM.`, `lines[${i}].unit`));
|
|
4010
|
-
}
|
|
4011
|
-
}
|
|
4348
|
+
/**
|
|
4349
|
+
* Create a new transport.
|
|
4350
|
+
*
|
|
4351
|
+
* @example
|
|
4352
|
+
* ```ts
|
|
4353
|
+
* const transport = await peppol.transports.create({
|
|
4354
|
+
* transportTypeCode: "peppol",
|
|
4355
|
+
* email: "billing@acme.com",
|
|
4356
|
+
* });
|
|
4357
|
+
* ```
|
|
4358
|
+
*/
|
|
4359
|
+
async create(input) {
|
|
4360
|
+
return this.adapter.createTransport(input);
|
|
4012
4361
|
}
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
br10,
|
|
4024
|
-
// Calculations (BR-CO)
|
|
4025
|
-
brCo10,
|
|
4026
|
-
brCo13,
|
|
4027
|
-
brCo15,
|
|
4028
|
-
brCo16,
|
|
4029
|
-
// Tax categories (one family per category)
|
|
4030
|
-
brS05,
|
|
4031
|
-
brZ05,
|
|
4032
|
-
brE05,
|
|
4033
|
-
brAe05,
|
|
4034
|
-
// Peppol-specific
|
|
4035
|
-
peppolR004,
|
|
4036
|
-
vatCategoryCodes,
|
|
4037
|
-
peppolR080
|
|
4038
|
-
];
|
|
4039
|
-
function validateSchematron(input) {
|
|
4040
|
-
const errors = [];
|
|
4041
|
-
const warnings = [];
|
|
4042
|
-
for (const rule of ALL_RULES) {
|
|
4043
|
-
const violations = rule(input);
|
|
4044
|
-
for (const v of violations) {
|
|
4045
|
-
if (v.severity === "error") {
|
|
4046
|
-
errors.push(v);
|
|
4047
|
-
} else {
|
|
4048
|
-
warnings.push(v);
|
|
4049
|
-
}
|
|
4050
|
-
}
|
|
4362
|
+
/**
|
|
4363
|
+
* Update an existing transport.
|
|
4364
|
+
*
|
|
4365
|
+
* @example
|
|
4366
|
+
* ```ts
|
|
4367
|
+
* const transport = await peppol.transports.update("peppol", { email: "new@acme.com" });
|
|
4368
|
+
* ```
|
|
4369
|
+
*/
|
|
4370
|
+
async update(code, input) {
|
|
4371
|
+
return this.adapter.updateTransport(code, input);
|
|
4051
4372
|
}
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
"BR-08",
|
|
4065
|
-
"BR-09",
|
|
4066
|
-
"BR-10",
|
|
4067
|
-
"BR-CL-17",
|
|
4068
|
-
"BR-CO-10",
|
|
4069
|
-
"BR-CO-13",
|
|
4070
|
-
"BR-CO-15",
|
|
4071
|
-
"BR-CO-16",
|
|
4072
|
-
"BR-S-05",
|
|
4073
|
-
"BR-Z-05",
|
|
4074
|
-
"BR-E-05",
|
|
4075
|
-
"BR-AE-05",
|
|
4076
|
-
"PEPPOL-EN16931-R004",
|
|
4077
|
-
"PEPPOL-EN16931-R080"
|
|
4078
|
-
];
|
|
4373
|
+
/**
|
|
4374
|
+
* Delete a transport.
|
|
4375
|
+
*
|
|
4376
|
+
* @example
|
|
4377
|
+
* ```ts
|
|
4378
|
+
* await peppol.transports.delete("peppol");
|
|
4379
|
+
* ```
|
|
4380
|
+
*/
|
|
4381
|
+
async delete(code) {
|
|
4382
|
+
return this.adapter.deleteTransport(code);
|
|
4383
|
+
}
|
|
4384
|
+
};
|
|
4079
4385
|
|
|
4080
4386
|
// src/commands/validate.ts
|
|
4081
4387
|
function runValidation(input) {
|
|
@@ -4148,14 +4454,17 @@ var INVOICE_TEMPLATE = {
|
|
|
4148
4454
|
description: "Conseil en transformation num\xE9rique",
|
|
4149
4455
|
quantity: 10,
|
|
4150
4456
|
unitPrice: 950,
|
|
4151
|
-
vatRate:
|
|
4457
|
+
vatRate: 0,
|
|
4458
|
+
vatCategory: "O",
|
|
4459
|
+
taxExemptReason: "Integration test"
|
|
4152
4460
|
},
|
|
4153
4461
|
{
|
|
4154
4462
|
description: "Software license \u2014 annual subscription",
|
|
4155
4463
|
quantity: 1,
|
|
4156
4464
|
unitPrice: 2400,
|
|
4157
4465
|
vatRate: 0,
|
|
4158
|
-
vatCategory: "
|
|
4466
|
+
vatCategory: "O",
|
|
4467
|
+
taxExemptReason: "Integration test"
|
|
4159
4468
|
}
|
|
4160
4469
|
],
|
|
4161
4470
|
paymentTerms: "Net 30 days",
|
|
@@ -4191,7 +4500,9 @@ var CREDIT_NOTE_TEMPLATE = {
|
|
|
4191
4500
|
description: "Avoir partiel \u2014 Conseil en transformation num\xE9rique",
|
|
4192
4501
|
quantity: 2,
|
|
4193
4502
|
unitPrice: 950,
|
|
4194
|
-
vatRate:
|
|
4503
|
+
vatRate: 0,
|
|
4504
|
+
vatCategory: "O",
|
|
4505
|
+
taxExemptReason: "Integration test"
|
|
4195
4506
|
}
|
|
4196
4507
|
],
|
|
4197
4508
|
note: "Avoir pour prestations non r\xE9alis\xE9es \u2014 r\xE9f. INV-2026-001"
|
|
@@ -4225,9 +4536,9 @@ function registerInitCommand(program2) {
|
|
|
4225
4536
|
3. Convert to XML: getpeppr convert ${filename}
|
|
4226
4537
|
4. Send: getpeppr send ${filename}
|
|
4227
4538
|
|
|
4228
|
-
${pc2.dim("Sandbox note:")}
|
|
4229
|
-
|
|
4230
|
-
|
|
4539
|
+
${pc2.dim("Sandbox note:")} ready for a fresh sandbox account. This template
|
|
4540
|
+
targets the test receiver and uses O/0 tax lines. Replace the fixture tax
|
|
4541
|
+
treatment with the real category after registering the sender's VAT details.
|
|
4231
4542
|
`);
|
|
4232
4543
|
process.exit(0);
|
|
4233
4544
|
}
|
|
@@ -4790,11 +5101,14 @@ function buildDefaultSendPayload(overrides = {}) {
|
|
|
4790
5101
|
quantity: 1,
|
|
4791
5102
|
unitPrice: amount,
|
|
4792
5103
|
vatRate: 0,
|
|
4793
|
-
// vatCategory "O" =
|
|
4794
|
-
//
|
|
4795
|
-
//
|
|
4796
|
-
//
|
|
4797
|
-
|
|
5104
|
+
// vatCategory "O" = outside the scope of VAT (UBL 2.1 / EN 16931).
|
|
5105
|
+
// A fresh sandbox sender has no VAT identifier, so this integration
|
|
5106
|
+
// fixture deliberately uses O/0. The SDK pre-validator requires the
|
|
5107
|
+
// builder-only reason; the send transport strips it so Storecove can
|
|
5108
|
+
// derive its own provider-specific text.
|
|
5109
|
+
// Do not fall back to VAT-bearing category "S": a fresh sandbox sender has no VAT number.
|
|
5110
|
+
vatCategory: "O",
|
|
5111
|
+
taxExemptReason: "Not subject to VAT"
|
|
4798
5112
|
}
|
|
4799
5113
|
]
|
|
4800
5114
|
};
|