@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/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
- <cbc:Percent>${item.vatRate}</cbc:Percent>
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
- <cbc:Percent>${line.vatRate}</cbc:Percent>
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
- const key = `${vatCategory}-${vatRate}`;
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
- <cbc:Percent>${st.vatRate}</cbc:Percent>
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
- if (!input.lines || input.lines.length === 0) {
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 (let i = 0; i < input.lines.length; i++) {
1614
- errors.push(...validateLine(input.lines[i], i, input.isCreditNote));
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/status-precedence.js
1677
- var STATUS_PRECEDENCE = [
1678
- { status: "failed", family: "terminal-failure" },
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
- function parseRetryAfter(headerValue) {
1726
- if (!headerValue)
1727
- return void 0;
1728
- const seconds = Number(headerValue);
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 void 0;
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
- function readSentence(source, key) {
1747
- const value = readOwn(source, key);
1748
- if (typeof value !== "string")
1749
- return null;
1750
- const cleaned = stripControls(value).trim();
1751
- return cleaned === "" ? null : cleaned;
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
- function safeDocsUrl(value) {
1754
- if (typeof value !== "string")
1755
- return null;
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
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
1763
- return null;
1764
- return parsed.href;
1765
- }
1766
- function formatApiErrorMessage(status, rawBody) {
1767
- const verbatim = `getpeppr API error (${status}): ${stripControls(rawBody)}`;
1768
- let parsed;
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
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1775
- return verbatim;
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
- const sentence = readSentence(parsed, "message") ?? readSentence(parsed, "error");
1778
- if (sentence === null)
1779
- return verbatim;
1780
- const link = safeDocsUrl(readOwn(parsed, "docs"));
1781
- return `getpeppr API error (${status}): ${sentence}${link ? ` See ${link}` : ""}`;
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
- if (error2 instanceof Error && error2.name === "AbortError") {
1788
- return true;
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
- if (error2 instanceof TypeError && /fetch failed|network|ECONNREFUSED|ECONNRESET|ENOTFOUND|ETIMEDOUT/i.test(error2.message)) {
1791
- return true;
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 false;
1794
- }
1795
- var DEFAULT_BASE_URL = "https://api.getpeppr.dev/v1";
1796
- var GetpepprAdapter = class {
1797
- name = "getpeppr";
1798
- baseUrl;
1799
- apiKey;
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
- async request(method, path, body, extraHeaders) {
1817
- const { maxRetries, initialDelayMs, maxDelayMs } = this.retryConfig;
1818
- let lastError;
1819
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
1820
- try {
1821
- return await this.doRequest(method, path, body, extraHeaders);
1822
- } catch (err) {
1823
- lastError = err;
1824
- const is429 = err instanceof PeppolApiError && err.statusCode === 429;
1825
- const isSafeMethod = /^(GET|DELETE|HEAD)$/i.test(method);
1826
- const hasIdempotencyKey = !!findHeaderCaseInsensitive(extraHeaders, "Idempotency-Key");
1827
- const canRetry = is429 || isSafeMethod || hasIdempotencyKey;
1828
- if (attempt < maxRetries && canRetry && isRetryableError(err)) {
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
- async doRequest(method, path, body, extraHeaders) {
1839
- const url = `${this.baseUrl}${path}`;
1840
- const controller = new AbortController();
1841
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
1842
- const requestHeaders = {
1843
- Authorization: `Bearer ${this.apiKey}`,
1844
- "Content-Type": "application/json",
1845
- Accept: "application/json",
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 an invoice without sending it.
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 validation = validateInvoice(input);
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
- if (input.isCreditNote) {
2854
- return buildCreditNoteXml(input);
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
- * for await (const account of peppol.bankAccounts.listAll()) {
3649
- * console.log(account.name, account.iban);
3650
- * }
4213
+ * await peppol.legalEntities.requestAttestation(le.id, { contactEmail: "owner@acme.example" });
3651
4214
  * ```
3652
4215
  */
3653
- listAll(options) {
3654
- return paginate((offset, limit) => this.adapter.listBankAccounts({ ...options, offset, limit }), options);
4216
+ async requestAttestation(id, input, options) {
4217
+ return this.adapter.requestLegalEntityAttestation(id, input, options);
3655
4218
  }
3656
4219
  };
3657
- var TransportOperations = class {
4220
+ var BankAccountOperations = class {
3658
4221
  adapter;
3659
4222
  constructor(adapter) {
3660
4223
  this.adapter = adapter;
3661
4224
  }
3662
4225
  /**
3663
- * List all available transport types in the network.
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 transports = await peppol.transports.list();
3681
- * console.log(transports); // [{ id: "t-1", transportTypeCode: "peppol", name: "..." }, ...]
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.listTransports();
4234
+ async list(options) {
4235
+ return this.adapter.listBankAccounts(options);
3686
4236
  }
3687
4237
  /**
3688
- * Get a single transport by code.
4238
+ * Get a single bank account by ID.
3689
4239
  *
3690
4240
  * @example
3691
4241
  * ```ts
3692
- * const transport = await peppol.transports.get("peppol");
4242
+ * const account = await peppol.bankAccounts.get("123");
4243
+ * console.log(account.name, account.iban);
3693
4244
  * ```
3694
4245
  */
3695
- async get(code) {
3696
- return this.adapter.getTransport(code);
4246
+ async get(id) {
4247
+ return this.adapter.getBankAccount(id);
3697
4248
  }
3698
4249
  /**
3699
- * Create a new transport.
4250
+ * Create a new bank account.
3700
4251
  *
3701
4252
  * @example
3702
4253
  * ```ts
3703
- * const transport = await peppol.transports.create({
3704
- * transportTypeCode: "peppol",
3705
- * email: "billing@acme.com",
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.createTransport(input);
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
- * Delete a transport.
4266
+ * Update an existing bank account.
3725
4267
  *
3726
4268
  * @example
3727
4269
  * ```ts
3728
- * await peppol.transports.delete("peppol");
4270
+ * const updated = await peppol.bankAccounts.update("123", { name: "Updated Name" });
3729
4271
  * ```
3730
4272
  */
3731
- async delete(code) {
3732
- return this.adapter.deleteTransport(code);
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
- 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
+ /**
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
- if (payable < -0.01) {
3916
- return [
3917
- violation("BR-CO-16", "warning", `Computed payable amount is negative (${payable.toFixed(2)}). Prepaid amount (${prepaid}) exceeds the invoice total.`)
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 brS05 = (input) => {
3923
- const violations = [];
3924
- if (!input.lines)
3925
- return violations;
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
- return violations;
3934
- };
3935
- var brZ05 = (input) => {
3936
- const violations = [];
3937
- if (!input.lines)
3938
- return violations;
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
- }
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
- return violations;
3946
- };
3947
- var brE05 = (input) => {
3948
- const violations = [];
3949
- if (!input.lines)
3950
- return violations;
3951
- for (let i = 0; i < input.lines.length; i++) {
3952
- const line = input.lines[i];
3953
- if (line.vatCategory === "E" && line.vatRate !== 0) {
3954
- violations.push(violation("BR-E-05", "error", `Line ${i}: exempt (E) requires vatRate = 0, got ${line.vatRate}.`, `lines[${i}].vatRate`));
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
- return violations;
3958
- };
3959
- var brAe05 = (input) => {
3960
- const violations = [];
3961
- if (!input.lines)
3962
- return violations;
3963
- for (let i = 0; i < input.lines.length; i++) {
3964
- const line = input.lines[i];
3965
- if (line.vatCategory === "AE" && line.vatRate !== 0) {
3966
- violations.push(violation("BR-AE-05", "error", `Line ${i}: reverse charge (AE) requires vatRate = 0, got ${line.vatRate}.`, `lines[${i}].vatRate`));
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
- return violations;
3970
- };
3971
- var peppolR004 = (input) => {
3972
- if (!input.to?.peppolId) {
3973
- return [
3974
- violation("PEPPOL-EN16931-R004", "error", "Buyer electronic address (peppolId) is required for Peppol delivery.", "to.peppolId")
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
- return [];
3978
- };
3979
- var vatCategoryCodes = (input) => {
3980
- const violations = [];
3981
- const sendable = SENDABLE_VAT_CATEGORIES.join(", ");
3982
- const echo = (v) => v.length <= 16 ? v : `${v.slice(0, 16)}\u2026`;
3983
- const check = (cat, field, label) => {
3984
- if (cat === void 0 || cat === null)
3985
- return;
3986
- if (!VALID_VAT_CATEGORIES.has(cat)) {
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
- return violations;
4014
- };
4015
- var ALL_RULES = [
4016
- // Required fields (BR)
4017
- br02,
4018
- br03,
4019
- br06,
4020
- br07,
4021
- br08,
4022
- br09,
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
- return {
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. No `taxExemptReason` field
4795
- // exists in InvoiceLine Storecove derives exemption from the category code.
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
  };