@getpeppr/cli 0.8.2 → 0.8.3
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 +13 -0
- package/dist/index.js +967 -665
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -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,29 @@ 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
|
+
}
|
|
696
720
|
function round2(n) {
|
|
697
721
|
return Math.round(n * 100) / 100;
|
|
698
722
|
}
|
|
723
|
+
function normalizedTaxExemptReason(vatCategory, reason) {
|
|
724
|
+
if (!EXEMPTION_REASON_CATEGORIES.has(vatCategory) || typeof reason !== "string") {
|
|
725
|
+
return void 0;
|
|
726
|
+
}
|
|
727
|
+
const normalized = reason.trim();
|
|
728
|
+
for (const character of normalized) {
|
|
729
|
+
const codePoint = character.codePointAt(0);
|
|
730
|
+
const allowed = codePoint === 9 || codePoint === 10 || codePoint === 13 || codePoint >= 32 && codePoint <= 55295 || codePoint >= 57344 && codePoint <= 65533 || codePoint >= 65536 && codePoint <= 1114111;
|
|
731
|
+
if (!allowed) {
|
|
732
|
+
throw new UblBuilderInputError("taxExemptReason contains an invalid XML character.", "taxExemptReason");
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return normalized || void 0;
|
|
736
|
+
}
|
|
699
737
|
function buildPartyXml(party, role) {
|
|
700
738
|
const { scheme: endpointScheme, id: endpointId } = parsePeppolId(party.peppolId);
|
|
701
739
|
return `
|
|
@@ -876,8 +914,8 @@ function buildDocumentAllowanceChargeXml(item, isCharge, currency) {
|
|
|
876
914
|
<cbc:AllowanceChargeReason>${escapeXml(item.reason)}</cbc:AllowanceChargeReason>
|
|
877
915
|
<cbc:Amount currencyID="${escapeXml(currency)}">${formatAmount(item.amount)}</cbc:Amount>
|
|
878
916
|
<cac:TaxCategory>
|
|
879
|
-
<cbc:ID>${vatCategory}</cbc:ID>
|
|
880
|
-
|
|
917
|
+
<cbc:ID>${escapeXml(vatCategory)}</cbc:ID>
|
|
918
|
+
${vatCategory === "O" ? "" : `<cbc:Percent>${formatVatRate(item.vatRate, "vatRate")}</cbc:Percent>`}
|
|
881
919
|
<cac:TaxScheme>
|
|
882
920
|
<cbc:ID>VAT</cbc:ID>
|
|
883
921
|
</cac:TaxScheme>
|
|
@@ -917,8 +955,8 @@ function buildDocumentLineXml(line, index, currency, lineTag, qtyTag) {
|
|
|
917
955
|
<cbc:ID>${escapeXml(line.itemId)}</cbc:ID>
|
|
918
956
|
</cac:SellersItemIdentification>` : ""}
|
|
919
957
|
<cac:ClassifiedTaxCategory>
|
|
920
|
-
<cbc:ID>${vatCategory}</cbc:ID>
|
|
921
|
-
|
|
958
|
+
<cbc:ID>${escapeXml(vatCategory)}</cbc:ID>
|
|
959
|
+
${vatCategory === "O" ? "" : `<cbc:Percent>${formatVatRate(line.vatRate, "vatRate")}</cbc:Percent>`}
|
|
922
960
|
<cac:TaxScheme>
|
|
923
961
|
<cbc:ID>VAT</cbc:ID>
|
|
924
962
|
</cac:TaxScheme>
|
|
@@ -943,39 +981,60 @@ function buildDocumentLineXml(line, index, currency, lineTag, qtyTag) {
|
|
|
943
981
|
function buildInvoiceLineXml(line, index, currency) {
|
|
944
982
|
return buildDocumentLineXml(line, index, currency, "InvoiceLine", "InvoicedQuantity");
|
|
945
983
|
}
|
|
946
|
-
function calculateTaxSubtotals(lines, allowances, charges) {
|
|
984
|
+
function calculateTaxSubtotals(lines, allowances, charges, options = {}) {
|
|
947
985
|
const groups = /* @__PURE__ */ new Map();
|
|
948
|
-
function addToGroup(vatCategory, vatRate, amount) {
|
|
949
|
-
|
|
986
|
+
function addToGroup(vatCategory, vatRate, amount, taxExemptReason, field = "taxExemptReason") {
|
|
987
|
+
if (typeof vatCategory !== "string") {
|
|
988
|
+
throw new UblBuilderInputError("vatCategory must be a string.", `${field}.vatCategory`);
|
|
989
|
+
}
|
|
990
|
+
formatVatRate(vatRate, `${field}.vatRate`);
|
|
991
|
+
if (options.forUbl && vatCategory === "O" && vatRate !== 0) {
|
|
992
|
+
throw new UblBuilderInputError("Category O must use vatRate 0 in SDK input.", `${field}.vatRate`);
|
|
993
|
+
}
|
|
994
|
+
const effectiveVatRate = options.forUbl && vatCategory === "O" ? 0 : vatRate;
|
|
995
|
+
const reason = options.forUbl ? normalizedTaxExemptReason(vatCategory, taxExemptReason) : void 0;
|
|
996
|
+
const key = `${vatCategory}-${effectiveVatRate}`;
|
|
950
997
|
const existing = groups.get(key);
|
|
951
998
|
if (existing) {
|
|
999
|
+
if (reason && existing.taxExemptReason && reason !== existing.taxExemptReason) {
|
|
1000
|
+
throw new UblBuilderInputError(`Conflicting taxExemptReason values for VAT group ${vatCategory}/${effectiveVatRate}.`, "taxExemptReason");
|
|
1001
|
+
}
|
|
1002
|
+
existing.taxExemptReason ??= reason;
|
|
952
1003
|
existing.taxableAmount = round2(existing.taxableAmount + amount);
|
|
953
1004
|
} else {
|
|
954
1005
|
groups.set(key, {
|
|
955
|
-
vatRate,
|
|
1006
|
+
vatRate: effectiveVatRate,
|
|
956
1007
|
vatCategory,
|
|
1008
|
+
taxExemptReason: reason,
|
|
957
1009
|
taxableAmount: amount,
|
|
958
1010
|
taxAmount: 0
|
|
959
1011
|
// computed once per group below (BR-CO-17)
|
|
960
1012
|
});
|
|
961
1013
|
}
|
|
962
1014
|
}
|
|
963
|
-
for (const line of lines) {
|
|
964
|
-
addToGroup(line.vatCategory ?? "S", line.vatRate, calculateLineExtensionAmount(line));
|
|
1015
|
+
for (const [index, line] of lines.entries()) {
|
|
1016
|
+
addToGroup(line.vatCategory ?? "S", line.vatRate, calculateLineExtensionAmount(line), line.taxExemptReason, `lines[${index}]`);
|
|
965
1017
|
}
|
|
966
|
-
for (const a of allowances ?? []) {
|
|
967
|
-
addToGroup(a.vatCategory ?? "S", a.vatRate, -a.amount);
|
|
1018
|
+
for (const [index, a] of (allowances ?? []).entries()) {
|
|
1019
|
+
addToGroup(a.vatCategory ?? "S", a.vatRate, -a.amount, a.taxExemptReason, `allowances[${index}]`);
|
|
968
1020
|
}
|
|
969
|
-
for (const c of charges ?? []) {
|
|
970
|
-
addToGroup(c.vatCategory ?? "S", c.vatRate, c.amount);
|
|
1021
|
+
for (const [index, c] of (charges ?? []).entries()) {
|
|
1022
|
+
addToGroup(c.vatCategory ?? "S", c.vatRate, c.amount, c.taxExemptReason, `charges[${index}]`);
|
|
1023
|
+
}
|
|
1024
|
+
if (options.forUbl) {
|
|
1025
|
+
for (const subtotal of groups.values()) {
|
|
1026
|
+
if (EXEMPTION_REASON_CATEGORIES.has(subtotal.vatCategory) && !subtotal.taxExemptReason) {
|
|
1027
|
+
throw new UblBuilderInputError(`VAT category ${subtotal.vatCategory} requires a non-empty taxExemptReason.`, "taxExemptReason", EXEMPTION_REASON_RULES[subtotal.vatCategory]);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
971
1030
|
}
|
|
972
1031
|
return Array.from(groups.values()).map((subtotal) => ({
|
|
973
1032
|
...subtotal,
|
|
974
1033
|
taxAmount: round2(subtotal.taxableAmount * (subtotal.vatRate / 100))
|
|
975
1034
|
}));
|
|
976
1035
|
}
|
|
977
|
-
function calculateDocumentTotals(lines, allowances, charges) {
|
|
978
|
-
const taxSubtotals = calculateTaxSubtotals(lines, allowances, charges);
|
|
1036
|
+
function calculateDocumentTotals(lines, allowances, charges, options = {}) {
|
|
1037
|
+
const taxSubtotals = calculateTaxSubtotals(lines, allowances, charges, options);
|
|
979
1038
|
const lineExtensionAmount = lines.reduce((sum, line) => sum + calculateLineExtensionAmount(line), 0);
|
|
980
1039
|
const allowanceTotalAmount = (allowances ?? []).reduce((sum, a) => sum + a.amount, 0);
|
|
981
1040
|
const chargeTotalAmount = (charges ?? []).reduce((sum, c) => sum + c.amount, 0);
|
|
@@ -999,8 +1058,9 @@ function buildTaxTotalXml(taxSubtotals, totalTax, currency) {
|
|
|
999
1058
|
<cbc:TaxableAmount currencyID="${escapeXml(currency)}">${formatAmount(st.taxableAmount)}</cbc:TaxableAmount>
|
|
1000
1059
|
<cbc:TaxAmount currencyID="${escapeXml(currency)}">${formatAmount(st.taxAmount)}</cbc:TaxAmount>
|
|
1001
1060
|
<cac:TaxCategory>
|
|
1002
|
-
<cbc:ID>${st.vatCategory}</cbc:ID>
|
|
1003
|
-
|
|
1061
|
+
<cbc:ID>${escapeXml(st.vatCategory)}</cbc:ID>
|
|
1062
|
+
${st.vatCategory === "O" ? "" : `<cbc:Percent>${formatVatRate(st.vatRate, "vatRate")}</cbc:Percent>`}
|
|
1063
|
+
${st.taxExemptReason ? `<cbc:TaxExemptionReason>${escapeXml(st.taxExemptReason)}</cbc:TaxExemptionReason>` : ""}
|
|
1004
1064
|
<cac:TaxScheme>
|
|
1005
1065
|
<cbc:ID>VAT</cbc:ID>
|
|
1006
1066
|
</cac:TaxScheme>
|
|
@@ -1069,7 +1129,7 @@ function buildInvoiceXml(input) {
|
|
|
1069
1129
|
const date = formatDate(input.date);
|
|
1070
1130
|
const dueDate = input.dueDate ? formatDate(input.dueDate) : void 0;
|
|
1071
1131
|
const hasTaxCurrency = input.taxCurrency && input.taxCurrency !== currency;
|
|
1072
|
-
const totals = calculateDocumentTotals(input.lines, input.allowances, input.charges);
|
|
1132
|
+
const totals = calculateDocumentTotals(input.lines, input.allowances, input.charges, { forUbl: true });
|
|
1073
1133
|
const linesXml = input.lines.map((line, i) => buildInvoiceLineXml(line, i, currency)).join("");
|
|
1074
1134
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
1075
1135
|
<Invoice xmlns="${UBL_NS}"
|
|
@@ -1116,7 +1176,7 @@ function buildCreditNoteXml(input) {
|
|
|
1116
1176
|
const date = formatDate(input.date);
|
|
1117
1177
|
const dueDate = input.dueDate ? formatDate(input.dueDate) : void 0;
|
|
1118
1178
|
const hasTaxCurrency = input.taxCurrency && input.taxCurrency !== currency;
|
|
1119
|
-
const totals = calculateDocumentTotals(input.lines, input.allowances, input.charges);
|
|
1179
|
+
const totals = calculateDocumentTotals(input.lines, input.allowances, input.charges, { forUbl: true });
|
|
1120
1180
|
const linesXml = input.lines.map((line, i) => buildCreditNoteLineXml(line, i, currency)).join("");
|
|
1121
1181
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
1122
1182
|
<CreditNote xmlns="${CREDIT_NOTE_NS}"
|
|
@@ -1607,11 +1667,32 @@ function validateInvoice(input) {
|
|
|
1607
1667
|
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
1668
|
}
|
|
1609
1669
|
}
|
|
1610
|
-
|
|
1670
|
+
const linesValue = input.lines;
|
|
1671
|
+
if (!Array.isArray(linesValue)) {
|
|
1672
|
+
errors.push(error("lines", "Line items must be an array", void 0));
|
|
1673
|
+
} else if (linesValue.length === 0) {
|
|
1611
1674
|
errors.push(error("lines", "At least one line item is required", "BR-16", "Add items to the lines array"));
|
|
1612
1675
|
} else {
|
|
1613
|
-
for (
|
|
1614
|
-
|
|
1676
|
+
for (const [i, line] of linesValue.entries()) {
|
|
1677
|
+
if (line === null || typeof line !== "object") {
|
|
1678
|
+
errors.push(error(`lines[${i}]`, `Line item ${i} must be an object`, void 0));
|
|
1679
|
+
continue;
|
|
1680
|
+
}
|
|
1681
|
+
errors.push(...validateLine(line, i, input.isCreditNote));
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
for (const field of ["allowances", "charges"]) {
|
|
1685
|
+
const value = input[field];
|
|
1686
|
+
if (value === void 0)
|
|
1687
|
+
continue;
|
|
1688
|
+
if (!Array.isArray(value)) {
|
|
1689
|
+
errors.push(error(field, `${field} must be an array`, void 0));
|
|
1690
|
+
continue;
|
|
1691
|
+
}
|
|
1692
|
+
for (const [index, item] of value.entries()) {
|
|
1693
|
+
if (item === null || typeof item !== "object") {
|
|
1694
|
+
errors.push(error(`${field}[${index}]`, `${field}[${index}] must be an object`, void 0));
|
|
1695
|
+
}
|
|
1615
1696
|
}
|
|
1616
1697
|
}
|
|
1617
1698
|
if (input.date) {
|
|
@@ -1673,176 +1754,701 @@ function validateInvoice(input) {
|
|
|
1673
1754
|
};
|
|
1674
1755
|
}
|
|
1675
1756
|
|
|
1676
|
-
// ../sdk/dist/core/
|
|
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);
|
|
1757
|
+
// ../sdk/dist/core/schematron.js
|
|
1758
|
+
function violation(ruleId, severity, message, field) {
|
|
1759
|
+
return { ruleId, severity, message, field };
|
|
1724
1760
|
}
|
|
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;
|
|
1761
|
+
var _knownUnitCodes;
|
|
1762
|
+
function getKnownUnitCodes() {
|
|
1763
|
+
if (!_knownUnitCodes) {
|
|
1764
|
+
_knownUnitCodes = new Set(getAllUnits().map((u) => u.code));
|
|
1736
1765
|
}
|
|
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;
|
|
1766
|
+
return _knownUnitCodes;
|
|
1745
1767
|
}
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
const
|
|
1751
|
-
|
|
1768
|
+
var VALID_VAT_CATEGORIES = /* @__PURE__ */ new Set(["S", "Z", "E", "AE", "K", "G", "O", "L", "M", "B"]);
|
|
1769
|
+
var SENDABLE_VAT_CATEGORIES = ["S", "Z", "E", "AE", "K", "G", "O"];
|
|
1770
|
+
var UNROUTABLE_VAT_CATEGORIES = /* @__PURE__ */ new Set(["L", "M", "B"]);
|
|
1771
|
+
function computeLineNet(line) {
|
|
1772
|
+
const baseQty = line.baseQuantity ?? 1;
|
|
1773
|
+
if (baseQty === 0)
|
|
1774
|
+
return NaN;
|
|
1775
|
+
const baseAmount = line.quantity * line.unitPrice / baseQty;
|
|
1776
|
+
const chargeTotal = (line.charges ?? []).reduce((sum, c) => sum + c.amount, 0);
|
|
1777
|
+
const allowanceTotal = (line.allowances ?? []).reduce((sum, a) => sum + a.amount, 0);
|
|
1778
|
+
return baseAmount + chargeTotal - allowanceTotal;
|
|
1752
1779
|
}
|
|
1753
|
-
|
|
1754
|
-
if (
|
|
1755
|
-
return
|
|
1756
|
-
let parsed;
|
|
1757
|
-
try {
|
|
1758
|
-
parsed = new URL(value);
|
|
1759
|
-
} catch {
|
|
1760
|
-
return null;
|
|
1780
|
+
var br02 = (input) => {
|
|
1781
|
+
if (!input.number?.trim()) {
|
|
1782
|
+
return [violation("BR-02", "error", "Invoice number is required.", "number")];
|
|
1761
1783
|
}
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
try {
|
|
1770
|
-
parsed = JSON.parse(rawBody);
|
|
1771
|
-
} catch {
|
|
1772
|
-
return verbatim;
|
|
1784
|
+
return [];
|
|
1785
|
+
};
|
|
1786
|
+
var br03 = (input) => {
|
|
1787
|
+
if (!input.date) {
|
|
1788
|
+
return [
|
|
1789
|
+
violation("BR-03", "warning", "Invoice issue date is not set. The SDK will default to today's date.", "date")
|
|
1790
|
+
];
|
|
1773
1791
|
}
|
|
1774
|
-
|
|
1775
|
-
|
|
1792
|
+
return [];
|
|
1793
|
+
};
|
|
1794
|
+
var br06 = (input) => {
|
|
1795
|
+
if (input.from && !input.from.vatNumber) {
|
|
1796
|
+
return [
|
|
1797
|
+
violation("BR-06", "warning", "Seller party has no VAT number. The gateway will use the account's VAT registration.", "from.vatNumber")
|
|
1798
|
+
];
|
|
1776
1799
|
}
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
}
|
|
1783
|
-
function isRetryableError(error2) {
|
|
1784
|
-
if (error2 instanceof PeppolApiError) {
|
|
1785
|
-
return RETRYABLE_STATUS_CODES.has(error2.statusCode);
|
|
1800
|
+
return [];
|
|
1801
|
+
};
|
|
1802
|
+
var br07 = (input) => {
|
|
1803
|
+
if (!input.to?.name?.trim()) {
|
|
1804
|
+
return [violation("BR-07", "error", "Buyer name is required.", "to.name")];
|
|
1786
1805
|
}
|
|
1787
|
-
|
|
1788
|
-
|
|
1806
|
+
return [];
|
|
1807
|
+
};
|
|
1808
|
+
var br08 = (input) => {
|
|
1809
|
+
if (!input.lines || input.lines.length === 0) {
|
|
1810
|
+
return [violation("BR-08", "error", "Invoice must have at least one line item.", "lines")];
|
|
1789
1811
|
}
|
|
1790
|
-
|
|
1791
|
-
|
|
1812
|
+
return [];
|
|
1813
|
+
};
|
|
1814
|
+
var br09 = (input) => {
|
|
1815
|
+
if (!input.dueDate && !input.paymentTerms) {
|
|
1816
|
+
return [
|
|
1817
|
+
violation("BR-09", "warning", "Neither due date nor payment terms specified. At least one is recommended.", "dueDate")
|
|
1818
|
+
];
|
|
1792
1819
|
}
|
|
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;
|
|
1820
|
+
return [];
|
|
1821
|
+
};
|
|
1822
|
+
var br10 = (input) => {
|
|
1823
|
+
if (!input.buyerReference && !input.orderReference) {
|
|
1824
|
+
return [
|
|
1825
|
+
violation("BR-10", "warning", "Neither buyerReference nor orderReference specified. Peppol BIS 3.0 requires at least one.", "buyerReference")
|
|
1826
|
+
];
|
|
1815
1827
|
}
|
|
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
|
-
}
|
|
1828
|
+
return [];
|
|
1829
|
+
};
|
|
1830
|
+
var brCo10 = (input) => {
|
|
1831
|
+
const violations = [];
|
|
1832
|
+
if (!input.lines)
|
|
1833
|
+
return violations;
|
|
1834
|
+
for (let i = 0; i < input.lines.length; i++) {
|
|
1835
|
+
const line = input.lines[i];
|
|
1836
|
+
const net = computeLineNet(line);
|
|
1837
|
+
if (!Number.isFinite(net)) {
|
|
1838
|
+
const baseQty = line.baseQuantity ?? 1;
|
|
1839
|
+
const detail = baseQty === 0 ? "baseQuantity is 0, causing division by zero." : "Computed line amount is not a finite number.";
|
|
1840
|
+
violations.push(violation("BR-CO-10", "error", `Line ${i}: invalid net amount. ${detail}`, `lines[${i}]`));
|
|
1835
1841
|
}
|
|
1836
|
-
throw lastError;
|
|
1837
1842
|
}
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1843
|
+
return violations;
|
|
1844
|
+
};
|
|
1845
|
+
var brCo13 = (input) => {
|
|
1846
|
+
if (!input.lines || input.lines.length === 0)
|
|
1847
|
+
return [];
|
|
1848
|
+
let totalVat = 0;
|
|
1849
|
+
for (let i = 0; i < input.lines.length; i++) {
|
|
1850
|
+
const line = input.lines[i];
|
|
1851
|
+
const net = computeLineNet(line);
|
|
1852
|
+
if (!Number.isFinite(net))
|
|
1853
|
+
continue;
|
|
1854
|
+
totalVat += net * (line.vatRate / 100);
|
|
1855
|
+
}
|
|
1856
|
+
for (const allowance of input.allowances ?? []) {
|
|
1857
|
+
totalVat -= allowance.amount * (allowance.vatRate / 100);
|
|
1858
|
+
}
|
|
1859
|
+
for (const charge of input.charges ?? []) {
|
|
1860
|
+
totalVat += charge.amount * (charge.vatRate / 100);
|
|
1861
|
+
}
|
|
1862
|
+
if (!Number.isFinite(totalVat)) {
|
|
1863
|
+
return [
|
|
1864
|
+
violation("BR-CO-13", "error", "Computed total VAT amount is not a finite number. Check line amounts and VAT rates.")
|
|
1865
|
+
];
|
|
1866
|
+
}
|
|
1867
|
+
if (totalVat < -0.01) {
|
|
1868
|
+
return [
|
|
1869
|
+
violation("BR-CO-13", "warning", `Computed total VAT is negative (${totalVat.toFixed(2)}). This is unusual for an invoice.`)
|
|
1870
|
+
];
|
|
1871
|
+
}
|
|
1872
|
+
return [];
|
|
1873
|
+
};
|
|
1874
|
+
var brCo15 = (input) => {
|
|
1875
|
+
if (!input.lines || input.lines.length === 0)
|
|
1876
|
+
return [];
|
|
1877
|
+
let lineTotal = 0;
|
|
1878
|
+
let vatTotal = 0;
|
|
1879
|
+
for (const line of input.lines) {
|
|
1880
|
+
const net = computeLineNet(line);
|
|
1881
|
+
if (!Number.isFinite(net))
|
|
1882
|
+
continue;
|
|
1883
|
+
lineTotal += net;
|
|
1884
|
+
vatTotal += net * (line.vatRate / 100);
|
|
1885
|
+
}
|
|
1886
|
+
for (const allowance of input.allowances ?? []) {
|
|
1887
|
+
lineTotal -= allowance.amount;
|
|
1888
|
+
vatTotal -= allowance.amount * (allowance.vatRate / 100);
|
|
1889
|
+
}
|
|
1890
|
+
for (const charge of input.charges ?? []) {
|
|
1891
|
+
lineTotal += charge.amount;
|
|
1892
|
+
vatTotal += charge.amount * (charge.vatRate / 100);
|
|
1893
|
+
}
|
|
1894
|
+
const taxInclusive = lineTotal + vatTotal;
|
|
1895
|
+
if (!Number.isFinite(taxInclusive)) {
|
|
1896
|
+
return [
|
|
1897
|
+
violation("BR-CO-15", "error", "Computed tax-inclusive amount is not a finite number.")
|
|
1898
|
+
];
|
|
1899
|
+
}
|
|
1900
|
+
if (taxInclusive < -0.01) {
|
|
1901
|
+
return [
|
|
1902
|
+
violation("BR-CO-15", "warning", `Computed tax-inclusive amount is negative (${taxInclusive.toFixed(2)}). Consider using a credit note instead.`)
|
|
1903
|
+
];
|
|
1904
|
+
}
|
|
1905
|
+
return [];
|
|
1906
|
+
};
|
|
1907
|
+
var brCo16 = (input) => {
|
|
1908
|
+
if (!input.lines || input.lines.length === 0)
|
|
1909
|
+
return [];
|
|
1910
|
+
let lineTotal = 0;
|
|
1911
|
+
let vatTotal = 0;
|
|
1912
|
+
for (const line of input.lines) {
|
|
1913
|
+
const net = computeLineNet(line);
|
|
1914
|
+
if (!Number.isFinite(net))
|
|
1915
|
+
continue;
|
|
1916
|
+
lineTotal += net;
|
|
1917
|
+
vatTotal += net * (line.vatRate / 100);
|
|
1918
|
+
}
|
|
1919
|
+
for (const allowance of input.allowances ?? []) {
|
|
1920
|
+
lineTotal -= allowance.amount;
|
|
1921
|
+
vatTotal -= allowance.amount * (allowance.vatRate / 100);
|
|
1922
|
+
}
|
|
1923
|
+
for (const charge of input.charges ?? []) {
|
|
1924
|
+
lineTotal += charge.amount;
|
|
1925
|
+
vatTotal += charge.amount * (charge.vatRate / 100);
|
|
1926
|
+
}
|
|
1927
|
+
const taxInclusive = lineTotal + vatTotal;
|
|
1928
|
+
const prepaid = input.prepaidAmount ?? 0;
|
|
1929
|
+
const rounding = input.roundingAmount ?? 0;
|
|
1930
|
+
const payable = taxInclusive - prepaid + rounding;
|
|
1931
|
+
if (!Number.isFinite(payable)) {
|
|
1932
|
+
return [
|
|
1933
|
+
violation("BR-CO-16", "error", "Computed payable amount is not a finite number.")
|
|
1934
|
+
];
|
|
1935
|
+
}
|
|
1936
|
+
if (payable < -0.01) {
|
|
1937
|
+
return [
|
|
1938
|
+
violation("BR-CO-16", "warning", `Computed payable amount is negative (${payable.toFixed(2)}). Prepaid amount (${prepaid}) exceeds the invoice total.`)
|
|
1939
|
+
];
|
|
1940
|
+
}
|
|
1941
|
+
return [];
|
|
1942
|
+
};
|
|
1943
|
+
var brS05 = (input) => {
|
|
1944
|
+
const violations = [];
|
|
1945
|
+
if (!input.lines)
|
|
1946
|
+
return violations;
|
|
1947
|
+
for (let i = 0; i < input.lines.length; i++) {
|
|
1948
|
+
const line = input.lines[i];
|
|
1949
|
+
const category = line.vatCategory ?? "S";
|
|
1950
|
+
if (category === "S" && (line.vatRate === void 0 || line.vatRate <= 0)) {
|
|
1951
|
+
violations.push(violation("BR-S-05", "error", `Line ${i}: standard rate (S) requires vatRate > 0, got ${line.vatRate ?? "undefined"}.`, `lines[${i}].vatRate`));
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
return violations;
|
|
1955
|
+
};
|
|
1956
|
+
var brZ05 = (input) => {
|
|
1957
|
+
const violations = [];
|
|
1958
|
+
if (!input.lines)
|
|
1959
|
+
return violations;
|
|
1960
|
+
for (let i = 0; i < input.lines.length; i++) {
|
|
1961
|
+
const line = input.lines[i];
|
|
1962
|
+
if (line.vatCategory === "Z" && line.vatRate !== 0) {
|
|
1963
|
+
violations.push(violation("BR-Z-05", "error", `Line ${i}: zero-rated (Z) requires vatRate = 0, got ${line.vatRate}.`, `lines[${i}].vatRate`));
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
return violations;
|
|
1967
|
+
};
|
|
1968
|
+
var brE05 = (input) => {
|
|
1969
|
+
const violations = [];
|
|
1970
|
+
if (!input.lines)
|
|
1971
|
+
return violations;
|
|
1972
|
+
for (let i = 0; i < input.lines.length; i++) {
|
|
1973
|
+
const line = input.lines[i];
|
|
1974
|
+
if (line.vatCategory === "E" && line.vatRate !== 0) {
|
|
1975
|
+
violations.push(violation("BR-E-05", "error", `Line ${i}: exempt (E) requires vatRate = 0, got ${line.vatRate}.`, `lines[${i}].vatRate`));
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
return violations;
|
|
1979
|
+
};
|
|
1980
|
+
var brAe05 = (input) => {
|
|
1981
|
+
const violations = [];
|
|
1982
|
+
if (!input.lines)
|
|
1983
|
+
return violations;
|
|
1984
|
+
for (let i = 0; i < input.lines.length; i++) {
|
|
1985
|
+
const line = input.lines[i];
|
|
1986
|
+
if (line.vatCategory === "AE" && line.vatRate !== 0) {
|
|
1987
|
+
violations.push(violation("BR-AE-05", "error", `Line ${i}: reverse charge (AE) requires vatRate = 0, got ${line.vatRate}.`, `lines[${i}].vatRate`));
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
return violations;
|
|
1991
|
+
};
|
|
1992
|
+
var brG05 = (input) => {
|
|
1993
|
+
const violations = [];
|
|
1994
|
+
for (let i = 0; i < (input.lines ?? []).length; i++) {
|
|
1995
|
+
const line = input.lines[i];
|
|
1996
|
+
if (line.vatCategory === "G" && line.vatRate !== 0) {
|
|
1997
|
+
violations.push(violation("BR-G-05", "error", `Line ${i}: export outside the EU (G) requires vatRate = 0, got ${line.vatRate}.`, `lines[${i}].vatRate`));
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
return violations;
|
|
2001
|
+
};
|
|
2002
|
+
var brIc05 = (input) => {
|
|
2003
|
+
const violations = [];
|
|
2004
|
+
for (let i = 0; i < (input.lines ?? []).length; i++) {
|
|
2005
|
+
const line = input.lines[i];
|
|
2006
|
+
if (line.vatCategory === "K" && line.vatRate !== 0) {
|
|
2007
|
+
violations.push(violation("BR-IC-05", "error", `Line ${i}: intra-community supply (K) requires vatRate = 0, got ${line.vatRate}.`, `lines[${i}].vatRate`));
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
return violations;
|
|
2011
|
+
};
|
|
2012
|
+
var documentAdjustmentVatRates = (input) => {
|
|
2013
|
+
const violations = [];
|
|
2014
|
+
const configs = /* @__PURE__ */ new Map([
|
|
2015
|
+
["S", { allowanceRule: "BR-S-06", chargeRule: "BR-S-07", valid: (rate) => rate > 0, label: "standard rate (S)" }],
|
|
2016
|
+
["Z", { allowanceRule: "BR-Z-06", chargeRule: "BR-Z-07", valid: (rate) => rate === 0, label: "zero-rated (Z)" }],
|
|
2017
|
+
["E", { allowanceRule: "BR-E-06", chargeRule: "BR-E-07", valid: (rate) => rate === 0, label: "exempt (E)" }],
|
|
2018
|
+
["AE", { allowanceRule: "BR-AE-06", chargeRule: "BR-AE-07", valid: (rate) => rate === 0, label: "reverse charge (AE)" }],
|
|
2019
|
+
["G", { allowanceRule: "BR-G-06", chargeRule: "BR-G-07", valid: (rate) => rate === 0, label: "export outside the EU (G)" }],
|
|
2020
|
+
["K", { allowanceRule: "BR-IC-06", chargeRule: "BR-IC-07", valid: (rate) => rate === 0, label: "intra-community supply (K)" }]
|
|
2021
|
+
]);
|
|
2022
|
+
const check = (items, kind) => {
|
|
2023
|
+
(items ?? []).forEach((item, index) => {
|
|
2024
|
+
const category = item.vatCategory ?? "S";
|
|
2025
|
+
const config = configs.get(category);
|
|
2026
|
+
if (!config || config.valid(item.vatRate))
|
|
2027
|
+
return;
|
|
2028
|
+
const ruleId = kind === "allowance" ? config.allowanceRule : config.chargeRule;
|
|
2029
|
+
violations.push(violation(ruleId, "error", `Document ${kind} ${index}: ${config.label} has an invalid vatRate (${item.vatRate}).`, `${kind === "allowance" ? "allowances" : "charges"}[${index}].vatRate`));
|
|
2030
|
+
});
|
|
2031
|
+
};
|
|
2032
|
+
check(input.allowances, "allowance");
|
|
2033
|
+
check(input.charges, "charge");
|
|
2034
|
+
return violations;
|
|
2035
|
+
};
|
|
2036
|
+
function exemptionReasonRule(vatCategory, ruleId, label) {
|
|
2037
|
+
return (input) => {
|
|
2038
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2039
|
+
const add = (category, rate, reason, field) => {
|
|
2040
|
+
if (category !== vatCategory)
|
|
2041
|
+
return;
|
|
2042
|
+
const effectiveRate = category === "O" ? 0 : rate;
|
|
2043
|
+
const key = `${category}-${effectiveRate}`;
|
|
2044
|
+
const hasReason = typeof reason === "string" && reason.trim().length > 0;
|
|
2045
|
+
const existing = groups.get(key);
|
|
2046
|
+
if (existing) {
|
|
2047
|
+
existing.hasReason ||= hasReason;
|
|
2048
|
+
} else {
|
|
2049
|
+
groups.set(key, { hasReason, field });
|
|
2050
|
+
}
|
|
2051
|
+
};
|
|
2052
|
+
(input.lines ?? []).forEach((line, index) => {
|
|
2053
|
+
add(line.vatCategory ?? "S", line.vatRate, line.taxExemptReason, `lines[${index}].taxExemptReason`);
|
|
2054
|
+
});
|
|
2055
|
+
(input.allowances ?? []).forEach((item, index) => {
|
|
2056
|
+
add(item.vatCategory ?? "S", item.vatRate, item.taxExemptReason, `allowances[${index}].taxExemptReason`);
|
|
2057
|
+
});
|
|
2058
|
+
(input.charges ?? []).forEach((item, index) => {
|
|
2059
|
+
add(item.vatCategory ?? "S", item.vatRate, item.taxExemptReason, `charges[${index}].taxExemptReason`);
|
|
2060
|
+
});
|
|
2061
|
+
return [...groups.values()].filter((group) => !group.hasReason).map((group) => violation(ruleId, "error", `${label} requires a non-empty taxExemptReason in its VAT breakdown.`, group.field));
|
|
2062
|
+
};
|
|
2063
|
+
}
|
|
2064
|
+
var brE10 = exemptionReasonRule("E", "BR-E-10", "Exempt from VAT (E)");
|
|
2065
|
+
var brAe10 = exemptionReasonRule("AE", "BR-AE-10", "Reverse charge (AE)");
|
|
2066
|
+
var brG10 = exemptionReasonRule("G", "BR-G-10", "Export outside the EU (G)");
|
|
2067
|
+
var brO10 = exemptionReasonRule("O", "BR-O-10", "Not subject to VAT (O)");
|
|
2068
|
+
var brIc10 = exemptionReasonRule("K", "BR-IC-10", "Intra-community supply (K)");
|
|
2069
|
+
var builderVatCategoryValidity = (input) => {
|
|
2070
|
+
const violations = [];
|
|
2071
|
+
const check = (category, field) => {
|
|
2072
|
+
if (category !== void 0 && !VALID_VAT_CATEGORIES.has(category)) {
|
|
2073
|
+
violations.push(violation("BR-CL-17", "error", "VAT category is not an EN 16931 code.", field));
|
|
2074
|
+
}
|
|
2075
|
+
};
|
|
2076
|
+
(input.lines ?? []).forEach((item, index) => check(item.vatCategory, `lines[${index}].vatCategory`));
|
|
2077
|
+
(input.allowances ?? []).forEach((item, index) => check(item.vatCategory, `allowances[${index}].vatCategory`));
|
|
2078
|
+
(input.charges ?? []).forEach((item, index) => check(item.vatCategory, `charges[${index}].vatCategory`));
|
|
2079
|
+
return violations;
|
|
2080
|
+
};
|
|
2081
|
+
var UBL_BUILDER_VAT_RULES = [
|
|
2082
|
+
builderVatCategoryValidity,
|
|
2083
|
+
brS05,
|
|
2084
|
+
brZ05,
|
|
2085
|
+
brE05,
|
|
2086
|
+
brAe05,
|
|
2087
|
+
brG05,
|
|
2088
|
+
brIc05,
|
|
2089
|
+
documentAdjustmentVatRates,
|
|
2090
|
+
brE10,
|
|
2091
|
+
brAe10,
|
|
2092
|
+
brG10,
|
|
2093
|
+
brO10,
|
|
2094
|
+
brIc10
|
|
2095
|
+
];
|
|
2096
|
+
function validateUblBuilderVat(input) {
|
|
2097
|
+
const shapeViolations = [];
|
|
2098
|
+
const checkCollection = (value, field) => {
|
|
2099
|
+
if (field === "lines" || value !== void 0) {
|
|
2100
|
+
if (!Array.isArray(value)) {
|
|
2101
|
+
shapeViolations.push(violation("SDK-INPUT", "error", `${field} must be an array.`, field));
|
|
2102
|
+
return;
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
if (!Array.isArray(value))
|
|
2106
|
+
return;
|
|
2107
|
+
for (const [index, item] of value.entries()) {
|
|
2108
|
+
const itemField = `${field}[${index}]`;
|
|
2109
|
+
if (item === null || typeof item !== "object") {
|
|
2110
|
+
shapeViolations.push(violation("SDK-INPUT", "error", `${itemField} must be an object.`, itemField));
|
|
2111
|
+
continue;
|
|
2112
|
+
}
|
|
2113
|
+
const candidate = item;
|
|
2114
|
+
if (typeof candidate.vatRate !== "number" || !Number.isFinite(candidate.vatRate)) {
|
|
2115
|
+
shapeViolations.push(violation("SDK-INPUT", "error", `${itemField}.vatRate must be a finite number.`, `${itemField}.vatRate`));
|
|
2116
|
+
}
|
|
2117
|
+
if (candidate.vatCategory !== void 0 && typeof candidate.vatCategory !== "string") {
|
|
2118
|
+
shapeViolations.push(violation("SDK-INPUT", "error", `${itemField}.vatCategory must be a string.`, `${itemField}.vatCategory`));
|
|
2119
|
+
}
|
|
2120
|
+
if (candidate.taxExemptReason !== void 0 && typeof candidate.taxExemptReason !== "string") {
|
|
2121
|
+
shapeViolations.push(violation("SDK-INPUT", "error", `${itemField}.taxExemptReason must be a string.`, `${itemField}.taxExemptReason`));
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
};
|
|
2125
|
+
checkCollection(input.lines, "lines");
|
|
2126
|
+
checkCollection(input.allowances, "allowances");
|
|
2127
|
+
checkCollection(input.charges, "charges");
|
|
2128
|
+
if (shapeViolations.length > 0)
|
|
2129
|
+
return shapeViolations;
|
|
2130
|
+
const oRateViolations = [];
|
|
2131
|
+
const checkORates = (items, field) => {
|
|
2132
|
+
(items ?? []).forEach((item, index) => {
|
|
2133
|
+
if (item.vatCategory === "O" && item.vatRate !== 0) {
|
|
2134
|
+
oRateViolations.push(violation("SDK-INPUT", "error", `Category O must use vatRate 0 in SDK input.`, `${field}[${index}].vatRate`));
|
|
2135
|
+
}
|
|
2136
|
+
});
|
|
2137
|
+
};
|
|
2138
|
+
checkORates(input.lines, "lines");
|
|
2139
|
+
checkORates(input.allowances, "allowances");
|
|
2140
|
+
checkORates(input.charges, "charges");
|
|
2141
|
+
return [
|
|
2142
|
+
...oRateViolations,
|
|
2143
|
+
...UBL_BUILDER_VAT_RULES.flatMap((rule) => rule(input))
|
|
2144
|
+
];
|
|
2145
|
+
}
|
|
2146
|
+
var peppolR004 = (input) => {
|
|
2147
|
+
if (!input.to?.peppolId) {
|
|
2148
|
+
return [
|
|
2149
|
+
violation("PEPPOL-EN16931-R004", "error", "Buyer electronic address (peppolId) is required for Peppol delivery.", "to.peppolId")
|
|
2150
|
+
];
|
|
2151
|
+
}
|
|
2152
|
+
return [];
|
|
2153
|
+
};
|
|
2154
|
+
var vatCategoryCodes = (input) => {
|
|
2155
|
+
const violations = [];
|
|
2156
|
+
const sendable = SENDABLE_VAT_CATEGORIES.join(", ");
|
|
2157
|
+
const echo = (v) => v.length <= 16 ? v : `${v.slice(0, 16)}\u2026`;
|
|
2158
|
+
const check = (cat, field, label) => {
|
|
2159
|
+
if (cat === void 0 || cat === null)
|
|
2160
|
+
return;
|
|
2161
|
+
if (!VALID_VAT_CATEGORIES.has(cat)) {
|
|
2162
|
+
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));
|
|
2163
|
+
return;
|
|
2164
|
+
}
|
|
2165
|
+
if (UNROUTABLE_VAT_CATEGORIES.has(cat)) {
|
|
2166
|
+
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));
|
|
2167
|
+
}
|
|
2168
|
+
};
|
|
2169
|
+
(input.lines ?? []).forEach((line, i) => check(line.vatCategory, `lines[${i}].vatCategory`, `Line ${i}`));
|
|
2170
|
+
(input.allowances ?? []).forEach((a, i) => check(a.vatCategory, `allowances[${i}].vatCategory`, `Allowance ${i}`));
|
|
2171
|
+
(input.charges ?? []).forEach((c, i) => check(c.vatCategory, `charges[${i}].vatCategory`, `Charge ${i}`));
|
|
2172
|
+
return violations;
|
|
2173
|
+
};
|
|
2174
|
+
var peppolR080 = (input) => {
|
|
2175
|
+
const violations = [];
|
|
2176
|
+
if (!input.lines)
|
|
2177
|
+
return violations;
|
|
2178
|
+
const knownCodes = getKnownUnitCodes();
|
|
2179
|
+
for (let i = 0; i < input.lines.length; i++) {
|
|
2180
|
+
const line = input.lines[i];
|
|
2181
|
+
if (line.unit) {
|
|
2182
|
+
const resolved = resolveUnit(line.unit);
|
|
2183
|
+
if (!knownCodes.has(resolved)) {
|
|
2184
|
+
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`));
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
return violations;
|
|
2189
|
+
};
|
|
2190
|
+
var ALL_RULES = [
|
|
2191
|
+
// Required fields (BR)
|
|
2192
|
+
br02,
|
|
2193
|
+
br03,
|
|
2194
|
+
br06,
|
|
2195
|
+
br07,
|
|
2196
|
+
br08,
|
|
2197
|
+
br09,
|
|
2198
|
+
br10,
|
|
2199
|
+
// Calculations (BR-CO)
|
|
2200
|
+
brCo10,
|
|
2201
|
+
brCo13,
|
|
2202
|
+
brCo15,
|
|
2203
|
+
brCo16,
|
|
2204
|
+
// Tax categories (one family per category)
|
|
2205
|
+
brS05,
|
|
2206
|
+
brZ05,
|
|
2207
|
+
brE05,
|
|
2208
|
+
brAe05,
|
|
2209
|
+
brG05,
|
|
2210
|
+
brIc05,
|
|
2211
|
+
documentAdjustmentVatRates,
|
|
2212
|
+
brE10,
|
|
2213
|
+
brAe10,
|
|
2214
|
+
brG10,
|
|
2215
|
+
brO10,
|
|
2216
|
+
brIc10,
|
|
2217
|
+
// Peppol-specific
|
|
2218
|
+
peppolR004,
|
|
2219
|
+
vatCategoryCodes,
|
|
2220
|
+
peppolR080
|
|
2221
|
+
];
|
|
2222
|
+
function validateSchematron(input) {
|
|
2223
|
+
const errors = [];
|
|
2224
|
+
const warnings = [];
|
|
2225
|
+
for (const rule of ALL_RULES) {
|
|
2226
|
+
const violations = rule(input);
|
|
2227
|
+
for (const v of violations) {
|
|
2228
|
+
if (v.severity === "error") {
|
|
2229
|
+
errors.push(v);
|
|
2230
|
+
} else {
|
|
2231
|
+
warnings.push(v);
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
return {
|
|
2236
|
+
valid: errors.length === 0,
|
|
2237
|
+
coverage: { rulesChecked: SDK_SCHEMATRON_RULE_IDS.length, ofNetworkFatalRules: "partial" },
|
|
2238
|
+
errors,
|
|
2239
|
+
warnings
|
|
2240
|
+
};
|
|
2241
|
+
}
|
|
2242
|
+
var SDK_SCHEMATRON_RULE_IDS = [
|
|
2243
|
+
"BR-02",
|
|
2244
|
+
"BR-03",
|
|
2245
|
+
"BR-06",
|
|
2246
|
+
"BR-07",
|
|
2247
|
+
"BR-08",
|
|
2248
|
+
"BR-09",
|
|
2249
|
+
"BR-10",
|
|
2250
|
+
"BR-CL-17",
|
|
2251
|
+
"BR-CO-10",
|
|
2252
|
+
"BR-CO-13",
|
|
2253
|
+
"BR-CO-15",
|
|
2254
|
+
"BR-CO-16",
|
|
2255
|
+
"BR-S-05",
|
|
2256
|
+
"BR-Z-05",
|
|
2257
|
+
"BR-E-05",
|
|
2258
|
+
"BR-AE-05",
|
|
2259
|
+
"BR-G-05",
|
|
2260
|
+
"BR-IC-05",
|
|
2261
|
+
"BR-S-06",
|
|
2262
|
+
"BR-S-07",
|
|
2263
|
+
"BR-Z-06",
|
|
2264
|
+
"BR-Z-07",
|
|
2265
|
+
"BR-E-06",
|
|
2266
|
+
"BR-E-07",
|
|
2267
|
+
"BR-AE-06",
|
|
2268
|
+
"BR-AE-07",
|
|
2269
|
+
"BR-G-06",
|
|
2270
|
+
"BR-G-07",
|
|
2271
|
+
"BR-IC-06",
|
|
2272
|
+
"BR-IC-07",
|
|
2273
|
+
"BR-E-10",
|
|
2274
|
+
"BR-AE-10",
|
|
2275
|
+
"BR-G-10",
|
|
2276
|
+
"BR-O-10",
|
|
2277
|
+
"BR-IC-10",
|
|
2278
|
+
"PEPPOL-EN16931-R004",
|
|
2279
|
+
"PEPPOL-EN16931-R080"
|
|
2280
|
+
];
|
|
2281
|
+
|
|
2282
|
+
// ../sdk/dist/core/status-precedence.js
|
|
2283
|
+
var STATUS_PRECEDENCE = [
|
|
2284
|
+
{ status: "failed", family: "terminal-failure" },
|
|
2285
|
+
{ status: "rejected", family: "terminal-failure" },
|
|
2286
|
+
{ status: "paid", family: "terminal-success" },
|
|
2287
|
+
{ status: "partially_paid", family: "progress" },
|
|
2288
|
+
{ status: "accepted", family: "progress" },
|
|
2289
|
+
{ status: "conditionally_accepted", family: "progress" },
|
|
2290
|
+
{ status: "under_query", family: "progress" },
|
|
2291
|
+
{ status: "in_process", family: "progress" },
|
|
2292
|
+
{ status: "cleared", family: "progress" },
|
|
2293
|
+
{ status: "delivered", family: "progress" },
|
|
2294
|
+
{ status: "acknowledged", family: "progress" },
|
|
2295
|
+
// Terminal for developer wait semantics only — stays rank 40 (non-terminal)
|
|
2296
|
+
// in the projection guard (§3.12 two-level terminality).
|
|
2297
|
+
{ status: "no_action", family: "terminal-failure" },
|
|
2298
|
+
{ status: "submitted", family: "progress" },
|
|
2299
|
+
{ status: "unknown", family: "fallback" }
|
|
2300
|
+
];
|
|
2301
|
+
function statusFamily(status) {
|
|
2302
|
+
return STATUS_PRECEDENCE.find((e) => e.status === status)?.family ?? "fallback";
|
|
2303
|
+
}
|
|
2304
|
+
var TERMINAL_FAILURE_STATUSES = STATUS_PRECEDENCE.filter((e) => e.family === "terminal-failure").map((e) => e.status);
|
|
2305
|
+
|
|
2306
|
+
// ../sdk/dist/version.js
|
|
2307
|
+
var SDK_VERSION = "4.6.0";
|
|
2308
|
+
|
|
2309
|
+
// ../sdk/dist/core/client.js
|
|
2310
|
+
function findHeaderCaseInsensitive(headers, name) {
|
|
2311
|
+
if (!headers)
|
|
2312
|
+
return void 0;
|
|
2313
|
+
const target = name.toLowerCase();
|
|
2314
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
2315
|
+
if (key.toLowerCase() === target)
|
|
2316
|
+
return value;
|
|
2317
|
+
}
|
|
2318
|
+
return void 0;
|
|
2319
|
+
}
|
|
2320
|
+
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
2321
|
+
function sleep(ms) {
|
|
2322
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
2323
|
+
}
|
|
2324
|
+
function calculateRetryDelay(attempt, initialDelayMs, maxDelayMs, retryAfterMs) {
|
|
2325
|
+
if (retryAfterMs !== void 0)
|
|
2326
|
+
return Math.min(retryAfterMs, maxDelayMs);
|
|
2327
|
+
const exponentialDelay = initialDelayMs * Math.pow(2, attempt);
|
|
2328
|
+
const jitter = Math.random() * initialDelayMs;
|
|
2329
|
+
return Math.min(exponentialDelay + jitter, maxDelayMs);
|
|
2330
|
+
}
|
|
2331
|
+
function parseRetryAfter(headerValue) {
|
|
2332
|
+
if (!headerValue)
|
|
2333
|
+
return void 0;
|
|
2334
|
+
const seconds = Number(headerValue);
|
|
2335
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
2336
|
+
return seconds * 1e3;
|
|
2337
|
+
}
|
|
2338
|
+
const dateMs = Date.parse(headerValue);
|
|
2339
|
+
if (!Number.isNaN(dateMs)) {
|
|
2340
|
+
const delayMs = dateMs - Date.now();
|
|
2341
|
+
return delayMs > 0 ? delayMs : 0;
|
|
2342
|
+
}
|
|
2343
|
+
return void 0;
|
|
2344
|
+
}
|
|
2345
|
+
var CONTROL_CHARACTERS = /[\u0000-\u001F\u007F-\u009F]/g;
|
|
2346
|
+
function stripControls(value) {
|
|
2347
|
+
return value.replace(CONTROL_CHARACTERS, " ");
|
|
2348
|
+
}
|
|
2349
|
+
function readOwn(source, key) {
|
|
2350
|
+
return Object.hasOwn(source, key) ? source[key] : void 0;
|
|
2351
|
+
}
|
|
2352
|
+
function readSentence(source, key) {
|
|
2353
|
+
const value = readOwn(source, key);
|
|
2354
|
+
if (typeof value !== "string")
|
|
2355
|
+
return null;
|
|
2356
|
+
const cleaned = stripControls(value).trim();
|
|
2357
|
+
return cleaned === "" ? null : cleaned;
|
|
2358
|
+
}
|
|
2359
|
+
function safeDocsUrl(value) {
|
|
2360
|
+
if (typeof value !== "string")
|
|
2361
|
+
return null;
|
|
2362
|
+
let parsed;
|
|
2363
|
+
try {
|
|
2364
|
+
parsed = new URL(value);
|
|
2365
|
+
} catch {
|
|
2366
|
+
return null;
|
|
2367
|
+
}
|
|
2368
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
|
|
2369
|
+
return null;
|
|
2370
|
+
return parsed.href;
|
|
2371
|
+
}
|
|
2372
|
+
function formatApiErrorMessage(status, rawBody) {
|
|
2373
|
+
const verbatim = `getpeppr API error (${status}): ${stripControls(rawBody)}`;
|
|
2374
|
+
let parsed;
|
|
2375
|
+
try {
|
|
2376
|
+
parsed = JSON.parse(rawBody);
|
|
2377
|
+
} catch {
|
|
2378
|
+
return verbatim;
|
|
2379
|
+
}
|
|
2380
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
2381
|
+
return verbatim;
|
|
2382
|
+
}
|
|
2383
|
+
const sentence = readSentence(parsed, "message") ?? readSentence(parsed, "error");
|
|
2384
|
+
if (sentence === null)
|
|
2385
|
+
return verbatim;
|
|
2386
|
+
const link = safeDocsUrl(readOwn(parsed, "docs"));
|
|
2387
|
+
return `getpeppr API error (${status}): ${sentence}${link ? ` See ${link}` : ""}`;
|
|
2388
|
+
}
|
|
2389
|
+
function isRetryableError(error2) {
|
|
2390
|
+
if (error2 instanceof PeppolApiError) {
|
|
2391
|
+
return RETRYABLE_STATUS_CODES.has(error2.statusCode);
|
|
2392
|
+
}
|
|
2393
|
+
if (error2 instanceof Error && error2.name === "AbortError") {
|
|
2394
|
+
return true;
|
|
2395
|
+
}
|
|
2396
|
+
if (error2 instanceof TypeError && /fetch failed|network|ECONNREFUSED|ECONNRESET|ENOTFOUND|ETIMEDOUT/i.test(error2.message)) {
|
|
2397
|
+
return true;
|
|
2398
|
+
}
|
|
2399
|
+
return false;
|
|
2400
|
+
}
|
|
2401
|
+
var DEFAULT_BASE_URL = "https://api.getpeppr.dev/v1";
|
|
2402
|
+
var GetpepprAdapter = class {
|
|
2403
|
+
name = "getpeppr";
|
|
2404
|
+
baseUrl;
|
|
2405
|
+
apiKey;
|
|
2406
|
+
timeout;
|
|
2407
|
+
retryConfig;
|
|
2408
|
+
onRequest;
|
|
2409
|
+
onResponse;
|
|
2410
|
+
constructor(config) {
|
|
2411
|
+
this.apiKey = config.apiKey;
|
|
2412
|
+
this.timeout = config.timeout ?? 3e4;
|
|
2413
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
2414
|
+
this.retryConfig = {
|
|
2415
|
+
maxRetries: config.retry?.maxRetries ?? 3,
|
|
2416
|
+
initialDelayMs: config.retry?.initialDelayMs ?? 500,
|
|
2417
|
+
maxDelayMs: config.retry?.maxDelayMs ?? 3e4
|
|
2418
|
+
};
|
|
2419
|
+
this.onRequest = config.onRequest;
|
|
2420
|
+
this.onResponse = config.onResponse;
|
|
2421
|
+
}
|
|
2422
|
+
async request(method, path, body, extraHeaders) {
|
|
2423
|
+
const { maxRetries, initialDelayMs, maxDelayMs } = this.retryConfig;
|
|
2424
|
+
let lastError;
|
|
2425
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
2426
|
+
try {
|
|
2427
|
+
return await this.doRequest(method, path, body, extraHeaders);
|
|
2428
|
+
} catch (err) {
|
|
2429
|
+
lastError = err;
|
|
2430
|
+
const is429 = err instanceof PeppolApiError && err.statusCode === 429;
|
|
2431
|
+
const isSafeMethod = /^(GET|DELETE|HEAD)$/i.test(method);
|
|
2432
|
+
const hasIdempotencyKey = !!findHeaderCaseInsensitive(extraHeaders, "Idempotency-Key");
|
|
2433
|
+
const canRetry = is429 || isSafeMethod || hasIdempotencyKey;
|
|
2434
|
+
if (attempt < maxRetries && canRetry && isRetryableError(err)) {
|
|
2435
|
+
const retryAfterMs = err instanceof PeppolApiError ? err.retryAfterMs : void 0;
|
|
2436
|
+
await sleep(calculateRetryDelay(attempt, initialDelayMs, maxDelayMs, retryAfterMs));
|
|
2437
|
+
continue;
|
|
2438
|
+
}
|
|
2439
|
+
throw err;
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
throw lastError;
|
|
2443
|
+
}
|
|
2444
|
+
async doRequest(method, path, body, extraHeaders) {
|
|
2445
|
+
const url = `${this.baseUrl}${path}`;
|
|
2446
|
+
const controller = new AbortController();
|
|
2447
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2448
|
+
const requestHeaders = {
|
|
2449
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
2450
|
+
"Content-Type": "application/json",
|
|
2451
|
+
Accept: "application/json",
|
|
1846
2452
|
"User-Agent": `getpeppr-sdk/${SDK_VERSION}`,
|
|
1847
2453
|
...extraHeaders
|
|
1848
2454
|
};
|
|
@@ -2835,8 +3441,9 @@ var Peppol = class {
|
|
|
2835
3441
|
this.legalEntities = new LegalEntityOperations(this.adapter);
|
|
2836
3442
|
}
|
|
2837
3443
|
/**
|
|
2838
|
-
* Validate
|
|
2839
|
-
* Useful for pre-flight checks in your UI
|
|
3444
|
+
* Validate the structured JSON send payload without sending it.
|
|
3445
|
+
* Useful for pre-flight checks in your UI; provider-side normalization still
|
|
3446
|
+
* applies on send. `toXml()` adds the stricter direct-UBL builder checks.
|
|
2840
3447
|
*/
|
|
2841
3448
|
validate(input) {
|
|
2842
3449
|
return validateInvoice(input);
|
|
@@ -2846,14 +3453,39 @@ var Peppol = class {
|
|
|
2846
3453
|
* Useful for debugging or manual submission.
|
|
2847
3454
|
*/
|
|
2848
3455
|
toXml(input) {
|
|
2849
|
-
const
|
|
3456
|
+
const baseValidation = validateInvoice(input);
|
|
3457
|
+
const vatViolations = baseValidation.valid ? validateUblBuilderVat(input) : [];
|
|
3458
|
+
const validation = {
|
|
3459
|
+
valid: baseValidation.valid && vatViolations.every((item) => item.severity !== "error"),
|
|
3460
|
+
errors: [
|
|
3461
|
+
...baseValidation.errors,
|
|
3462
|
+
...vatViolations.filter((item) => item.severity === "error").map(({ field, message, ruleId }) => ({
|
|
3463
|
+
field: field ?? "invoice",
|
|
3464
|
+
message,
|
|
3465
|
+
ruleId: ruleId === "SDK-INPUT" ? void 0 : ruleId
|
|
3466
|
+
}))
|
|
3467
|
+
],
|
|
3468
|
+
warnings: baseValidation.warnings
|
|
3469
|
+
};
|
|
2850
3470
|
if (!validation.valid) {
|
|
2851
3471
|
throw new PeppolValidationError(`Invoice validation failed: ${validation.errors.map((e) => e.message).join("; ")}`, validation);
|
|
2852
3472
|
}
|
|
2853
|
-
|
|
2854
|
-
|
|
3473
|
+
try {
|
|
3474
|
+
if (input.isCreditNote) {
|
|
3475
|
+
return buildCreditNoteXml(input);
|
|
3476
|
+
}
|
|
3477
|
+
return buildInvoiceXml(input);
|
|
3478
|
+
} catch (error2) {
|
|
3479
|
+
if (error2 instanceof UblBuilderInputError) {
|
|
3480
|
+
const builderValidation = {
|
|
3481
|
+
valid: false,
|
|
3482
|
+
errors: [{ field: error2.field, message: error2.message, ruleId: error2.ruleId }],
|
|
3483
|
+
warnings: validation.warnings
|
|
3484
|
+
};
|
|
3485
|
+
throw new PeppolValidationError(`Invoice validation failed: ${error2.message}`, builderValidation);
|
|
3486
|
+
}
|
|
3487
|
+
throw error2;
|
|
2855
3488
|
}
|
|
2856
|
-
return buildInvoiceXml(input);
|
|
2857
3489
|
}
|
|
2858
3490
|
};
|
|
2859
3491
|
async function* paginate(fetchPage, options) {
|
|
@@ -2871,6 +3503,18 @@ async function* paginate(fetchPage, options) {
|
|
|
2871
3503
|
offset += page.data.length;
|
|
2872
3504
|
}
|
|
2873
3505
|
}
|
|
3506
|
+
function toGatewayInvoiceInput(input) {
|
|
3507
|
+
const stripReason = (item) => {
|
|
3508
|
+
const { taxExemptReason: _builderOnly, ...gatewayItem } = item;
|
|
3509
|
+
return gatewayItem;
|
|
3510
|
+
};
|
|
3511
|
+
return {
|
|
3512
|
+
...input,
|
|
3513
|
+
lines: input.lines.map(stripReason),
|
|
3514
|
+
...input.allowances ? { allowances: input.allowances.map(stripReason) } : {},
|
|
3515
|
+
...input.charges ? { charges: input.charges.map(stripReason) } : {}
|
|
3516
|
+
};
|
|
3517
|
+
}
|
|
2874
3518
|
var InvoiceOperations = class {
|
|
2875
3519
|
adapter;
|
|
2876
3520
|
constructor(adapter) {
|
|
@@ -2894,7 +3538,7 @@ var InvoiceOperations = class {
|
|
|
2894
3538
|
throw new PeppolValidationError(`Invoice validation failed:
|
|
2895
3539
|
${validation.errors.map((e) => ` - ${e.field}: ${e.message}${e.suggestion ? ` (${e.suggestion})` : ""}`).join("\n")}`, validation);
|
|
2896
3540
|
}
|
|
2897
|
-
const result = await this.adapter.createInvoice(input, options);
|
|
3541
|
+
const result = await this.adapter.createInvoice(toGatewayInvoiceInput(input), options);
|
|
2898
3542
|
if (validation.warnings.length > 0) {
|
|
2899
3543
|
result.warnings = validation.warnings;
|
|
2900
3544
|
}
|
|
@@ -2934,7 +3578,7 @@ ${validation.errors.map((e) => ` - ${e.field}: ${e.message}${e.suggestion ? ` (
|
|
|
2934
3578
|
throw new PeppolValidationError(`Invoice validation failed:
|
|
2935
3579
|
${validation.errors.map((e) => ` - ${e.field}: ${e.message}${e.suggestion ? ` (${e.suggestion})` : ""}`).join("\n")}`, validation);
|
|
2936
3580
|
}
|
|
2937
|
-
const result = await this.adapter.sendInvoice(input, options);
|
|
3581
|
+
const result = await this.adapter.sendInvoice(toGatewayInvoiceInput(input), options);
|
|
2938
3582
|
if (validation.warnings.length > 0) {
|
|
2939
3583
|
result.warnings = validation.warnings;
|
|
2940
3584
|
}
|
|
@@ -3243,7 +3887,7 @@ var CreditNoteOperations = class {
|
|
|
3243
3887
|
throw new PeppolValidationError(`Credit note validation failed:
|
|
3244
3888
|
${validation.errors.map((e) => ` - ${e.field}: ${e.message}`).join("\n")}`, validation);
|
|
3245
3889
|
}
|
|
3246
|
-
return this.adapter.sendInvoice(invoiceInput);
|
|
3890
|
+
return this.adapter.sendInvoice(toGatewayInvoiceInput(invoiceInput));
|
|
3247
3891
|
}
|
|
3248
3892
|
};
|
|
3249
3893
|
var DirectoryOperations = class {
|
|
@@ -3552,530 +4196,186 @@ var LegalEntityOperations = class {
|
|
|
3552
4196
|
return this.adapter.archiveLegalEntity(id);
|
|
3553
4197
|
}
|
|
3554
4198
|
/**
|
|
3555
|
-
* Request a sub-tenant attestation (production only). Emails the co-branded
|
|
3556
|
-
* confirmation link to the sub-tenant contact and returns the pending status.
|
|
3557
|
-
*
|
|
3558
|
-
* **Platform accounts only — requires a master API key.** In the sandbox,
|
|
3559
|
-
* an organisation admin starts the platform sandbox trial from the console
|
|
3560
|
-
* overview and creates a sandbox master key at
|
|
3561
|
-
* 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.
|
|
4199
|
+
* Request a sub-tenant attestation (production only). Emails the co-branded
|
|
4200
|
+
* confirmation link to the sub-tenant contact and returns the pending status.
|
|
4201
|
+
*
|
|
4202
|
+
* **Platform accounts only — requires a master API key.** In the sandbox,
|
|
4203
|
+
* an organisation admin starts the platform sandbox trial from the console
|
|
4204
|
+
* overview and creates a sandbox master key at
|
|
4205
|
+
* https://console.getpeppr.dev/api-keys; production platform access is set
|
|
4206
|
+
* up with our team (hello@getpeppr.dev).
|
|
4207
|
+
*
|
|
4208
|
+
* Transient failures are NOT auto-retried unless you pass `options.idempotencyKey`;
|
|
4209
|
+
* re-issuing mints a fresh token, so a retried call is safe.
|
|
3645
4210
|
*
|
|
3646
4211
|
* @example
|
|
3647
4212
|
* ```ts
|
|
3648
|
-
*
|
|
3649
|
-
* console.log(account.name, account.iban);
|
|
3650
|
-
* }
|
|
4213
|
+
* await peppol.legalEntities.requestAttestation(le.id, { contactEmail: "owner@acme.example" });
|
|
3651
4214
|
* ```
|
|
3652
4215
|
*/
|
|
3653
|
-
|
|
3654
|
-
return
|
|
4216
|
+
async requestAttestation(id, input, options) {
|
|
4217
|
+
return this.adapter.requestLegalEntityAttestation(id, input, options);
|
|
3655
4218
|
}
|
|
3656
4219
|
};
|
|
3657
|
-
var
|
|
4220
|
+
var BankAccountOperations = class {
|
|
3658
4221
|
adapter;
|
|
3659
4222
|
constructor(adapter) {
|
|
3660
4223
|
this.adapter = adapter;
|
|
3661
4224
|
}
|
|
3662
4225
|
/**
|
|
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.
|
|
4226
|
+
* List bank accounts with optional pagination.
|
|
3677
4227
|
*
|
|
3678
4228
|
* @example
|
|
3679
4229
|
* ```ts
|
|
3680
|
-
* const
|
|
3681
|
-
* console.log(
|
|
4230
|
+
* const result = await peppol.bankAccounts.list({ limit: 10 });
|
|
4231
|
+
* console.log(result.data, result.meta);
|
|
3682
4232
|
* ```
|
|
3683
4233
|
*/
|
|
3684
|
-
async list() {
|
|
3685
|
-
return this.adapter.
|
|
4234
|
+
async list(options) {
|
|
4235
|
+
return this.adapter.listBankAccounts(options);
|
|
3686
4236
|
}
|
|
3687
4237
|
/**
|
|
3688
|
-
* Get a single
|
|
4238
|
+
* Get a single bank account by ID.
|
|
3689
4239
|
*
|
|
3690
4240
|
* @example
|
|
3691
4241
|
* ```ts
|
|
3692
|
-
* const
|
|
4242
|
+
* const account = await peppol.bankAccounts.get("123");
|
|
4243
|
+
* console.log(account.name, account.iban);
|
|
3693
4244
|
* ```
|
|
3694
4245
|
*/
|
|
3695
|
-
async get(
|
|
3696
|
-
return this.adapter.
|
|
4246
|
+
async get(id) {
|
|
4247
|
+
return this.adapter.getBankAccount(id);
|
|
3697
4248
|
}
|
|
3698
4249
|
/**
|
|
3699
|
-
* Create a new
|
|
4250
|
+
* Create a new bank account.
|
|
3700
4251
|
*
|
|
3701
4252
|
* @example
|
|
3702
4253
|
* ```ts
|
|
3703
|
-
* const
|
|
3704
|
-
*
|
|
3705
|
-
*
|
|
4254
|
+
* const account = await peppol.bankAccounts.create({
|
|
4255
|
+
* name: "Main Account",
|
|
4256
|
+
* iban: "BE68539007547034",
|
|
4257
|
+
* bic: "BBRUBEBB",
|
|
4258
|
+
* country: "BE",
|
|
3706
4259
|
* });
|
|
3707
4260
|
* ```
|
|
3708
4261
|
*/
|
|
3709
4262
|
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);
|
|
4263
|
+
return this.adapter.createBankAccount(input);
|
|
3722
4264
|
}
|
|
3723
4265
|
/**
|
|
3724
|
-
*
|
|
4266
|
+
* Update an existing bank account.
|
|
3725
4267
|
*
|
|
3726
4268
|
* @example
|
|
3727
4269
|
* ```ts
|
|
3728
|
-
* await peppol.
|
|
4270
|
+
* const updated = await peppol.bankAccounts.update("123", { name: "Updated Name" });
|
|
3729
4271
|
* ```
|
|
3730
4272
|
*/
|
|
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);
|
|
4273
|
+
async update(id, input) {
|
|
4274
|
+
return this.adapter.updateBankAccount(id, input);
|
|
3905
4275
|
}
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
4276
|
+
/**
|
|
4277
|
+
* Delete a bank account.
|
|
4278
|
+
*
|
|
4279
|
+
* @example
|
|
4280
|
+
* ```ts
|
|
4281
|
+
* await peppol.bankAccounts.delete("123");
|
|
4282
|
+
* ```
|
|
4283
|
+
*/
|
|
4284
|
+
async delete(id) {
|
|
4285
|
+
return this.adapter.deleteBankAccount(id);
|
|
3914
4286
|
}
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
4287
|
+
/**
|
|
4288
|
+
* Async iterator over all bank accounts, automatically handling pagination.
|
|
4289
|
+
*
|
|
4290
|
+
* @example
|
|
4291
|
+
* ```ts
|
|
4292
|
+
* for await (const account of peppol.bankAccounts.listAll()) {
|
|
4293
|
+
* console.log(account.name, account.iban);
|
|
4294
|
+
* }
|
|
4295
|
+
* ```
|
|
4296
|
+
*/
|
|
4297
|
+
listAll(options) {
|
|
4298
|
+
return paginate((offset, limit) => this.adapter.listBankAccounts({ ...options, offset, limit }), options);
|
|
3919
4299
|
}
|
|
3920
|
-
return [];
|
|
3921
4300
|
};
|
|
3922
|
-
var
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
for (let i = 0; i < input.lines.length; i++) {
|
|
3927
|
-
const line = input.lines[i];
|
|
3928
|
-
const category = line.vatCategory ?? "S";
|
|
3929
|
-
if (category === "S" && (line.vatRate === void 0 || line.vatRate <= 0)) {
|
|
3930
|
-
violations.push(violation("BR-S-05", "error", `Line ${i}: standard rate (S) requires vatRate > 0, got ${line.vatRate ?? "undefined"}.`, `lines[${i}].vatRate`));
|
|
3931
|
-
}
|
|
4301
|
+
var TransportOperations = class {
|
|
4302
|
+
adapter;
|
|
4303
|
+
constructor(adapter) {
|
|
4304
|
+
this.adapter = adapter;
|
|
3932
4305
|
}
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
4306
|
+
/**
|
|
4307
|
+
* List all available transport types in the network.
|
|
4308
|
+
* Returns global transport types (not account-scoped).
|
|
4309
|
+
*
|
|
4310
|
+
* @example
|
|
4311
|
+
* ```ts
|
|
4312
|
+
* const types = await peppol.transports.listTypes();
|
|
4313
|
+
* console.log(types); // [{ code: "peppol", name: "Peppol BIS 3.0" }, ...]
|
|
4314
|
+
* ```
|
|
4315
|
+
*/
|
|
4316
|
+
async listTypes() {
|
|
4317
|
+
return this.adapter.listTransportTypes();
|
|
3944
4318
|
}
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
4319
|
+
/**
|
|
4320
|
+
* List configured transports for this account.
|
|
4321
|
+
*
|
|
4322
|
+
* @example
|
|
4323
|
+
* ```ts
|
|
4324
|
+
* const transports = await peppol.transports.list();
|
|
4325
|
+
* console.log(transports); // [{ id: "t-1", transportTypeCode: "peppol", name: "..." }, ...]
|
|
4326
|
+
* ```
|
|
4327
|
+
*/
|
|
4328
|
+
async list() {
|
|
4329
|
+
return this.adapter.listTransports();
|
|
3956
4330
|
}
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
}
|
|
4331
|
+
/**
|
|
4332
|
+
* Get a single transport by code.
|
|
4333
|
+
*
|
|
4334
|
+
* @example
|
|
4335
|
+
* ```ts
|
|
4336
|
+
* const transport = await peppol.transports.get("peppol");
|
|
4337
|
+
* ```
|
|
4338
|
+
*/
|
|
4339
|
+
async get(code) {
|
|
4340
|
+
return this.adapter.getTransport(code);
|
|
3968
4341
|
}
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
4342
|
+
/**
|
|
4343
|
+
* Create a new transport.
|
|
4344
|
+
*
|
|
4345
|
+
* @example
|
|
4346
|
+
* ```ts
|
|
4347
|
+
* const transport = await peppol.transports.create({
|
|
4348
|
+
* transportTypeCode: "peppol",
|
|
4349
|
+
* email: "billing@acme.com",
|
|
4350
|
+
* });
|
|
4351
|
+
* ```
|
|
4352
|
+
*/
|
|
4353
|
+
async create(input) {
|
|
4354
|
+
return this.adapter.createTransport(input);
|
|
3976
4355
|
}
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
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));
|
|
3988
|
-
return;
|
|
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
|
-
}
|
|
4356
|
+
/**
|
|
4357
|
+
* Update an existing transport.
|
|
4358
|
+
*
|
|
4359
|
+
* @example
|
|
4360
|
+
* ```ts
|
|
4361
|
+
* const transport = await peppol.transports.update("peppol", { email: "new@acme.com" });
|
|
4362
|
+
* ```
|
|
4363
|
+
*/
|
|
4364
|
+
async update(code, input) {
|
|
4365
|
+
return this.adapter.updateTransport(code, input);
|
|
4012
4366
|
}
|
|
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
|
-
}
|
|
4367
|
+
/**
|
|
4368
|
+
* Delete a transport.
|
|
4369
|
+
*
|
|
4370
|
+
* @example
|
|
4371
|
+
* ```ts
|
|
4372
|
+
* await peppol.transports.delete("peppol");
|
|
4373
|
+
* ```
|
|
4374
|
+
*/
|
|
4375
|
+
async delete(code) {
|
|
4376
|
+
return this.adapter.deleteTransport(code);
|
|
4051
4377
|
}
|
|
4052
|
-
|
|
4053
|
-
valid: errors.length === 0,
|
|
4054
|
-
coverage: { rulesChecked: SDK_SCHEMATRON_RULE_IDS.length, ofNetworkFatalRules: "partial" },
|
|
4055
|
-
errors,
|
|
4056
|
-
warnings
|
|
4057
|
-
};
|
|
4058
|
-
}
|
|
4059
|
-
var SDK_SCHEMATRON_RULE_IDS = [
|
|
4060
|
-
"BR-02",
|
|
4061
|
-
"BR-03",
|
|
4062
|
-
"BR-06",
|
|
4063
|
-
"BR-07",
|
|
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
|
-
];
|
|
4378
|
+
};
|
|
4079
4379
|
|
|
4080
4380
|
// src/commands/validate.ts
|
|
4081
4381
|
function runValidation(input) {
|
|
@@ -4791,10 +5091,12 @@ function buildDefaultSendPayload(overrides = {}) {
|
|
|
4791
5091
|
unitPrice: amount,
|
|
4792
5092
|
vatRate: 0,
|
|
4793
5093
|
// vatCategory "O" = "Services outside scope of tax" (UBL 2.1 / EN 16931).
|
|
4794
|
-
// Public entities (SPF Economie) are VAT-exempt.
|
|
4795
|
-
//
|
|
5094
|
+
// Public entities (SPF Economie) are VAT-exempt. The SDK pre-validator
|
|
5095
|
+
// requires the builder-only reason; the send transport strips it so
|
|
5096
|
+
// Storecove can still derive its own provider-specific text.
|
|
4796
5097
|
// If sandbox returns 422 on this combination, fallback is vatRate: 21 + vatCategory: "S".
|
|
4797
|
-
vatCategory: "O"
|
|
5098
|
+
vatCategory: "O",
|
|
5099
|
+
taxExemptReason: "Not subject to VAT"
|
|
4798
5100
|
}
|
|
4799
5101
|
]
|
|
4800
5102
|
};
|