@getpeppr/cli 0.4.7 → 0.5.1

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
@@ -418,17 +418,16 @@ function calculateTaxSubtotals(lines, allowances, charges) {
418
418
  const groups = /* @__PURE__ */ new Map();
419
419
  function addToGroup(vatCategory, vatRate, amount) {
420
420
  const key = `${vatCategory}-${vatRate}`;
421
- const lineTax = round2(amount * (vatRate / 100));
422
421
  const existing = groups.get(key);
423
422
  if (existing) {
424
423
  existing.taxableAmount = round2(existing.taxableAmount + amount);
425
- existing.taxAmount = round2(existing.taxAmount + lineTax);
426
424
  } else {
427
425
  groups.set(key, {
428
426
  vatRate,
429
427
  vatCategory,
430
428
  taxableAmount: amount,
431
- taxAmount: lineTax
429
+ taxAmount: 0
430
+ // computed once per group below (BR-CO-17)
432
431
  });
433
432
  }
434
433
  }
@@ -441,7 +440,10 @@ function calculateTaxSubtotals(lines, allowances, charges) {
441
440
  for (const c of charges ?? []) {
442
441
  addToGroup(c.vatCategory ?? "S", c.vatRate, c.amount);
443
442
  }
444
- return Array.from(groups.values());
443
+ return Array.from(groups.values()).map((subtotal) => ({
444
+ ...subtotal,
445
+ taxAmount: round2(subtotal.taxableAmount * (subtotal.vatRate / 100))
446
+ }));
445
447
  }
446
448
  function calculateDocumentTotals(lines, allowances, charges) {
447
449
  const taxSubtotals = calculateTaxSubtotals(lines, allowances, charges);
@@ -486,10 +488,13 @@ function buildTaxCurrencyTotalXml(totalTax, taxCurrency, rate) {
486
488
  <cbc:TaxAmount currencyID="${escapeXml(taxCurrency)}">${formatAmount(convertedAmount)}</cbc:TaxAmount>
487
489
  </cac:TaxTotal>`;
488
490
  }
491
+ function payableFromTaxInclusive(taxInclusiveAmount, prepaidAmount, roundingAmount) {
492
+ return Number((taxInclusiveAmount - (prepaidAmount ?? 0) + (roundingAmount ?? 0)).toFixed(2));
493
+ }
489
494
  function buildLegalMonetaryTotalXml(totals, currency, options) {
490
495
  const prepaid = options?.prepaidAmount;
491
496
  const rounding = options?.roundingAmount;
492
- const payableAmount = totals.taxInclusiveAmount - (prepaid ?? 0) + (rounding ?? 0);
497
+ const payableAmount = payableFromTaxInclusive(totals.taxInclusiveAmount, prepaid, rounding);
493
498
  return `<cac:LegalMonetaryTotal>
494
499
  <cbc:LineExtensionAmount currencyID="${escapeXml(currency)}">${formatAmount(totals.lineExtensionAmount)}</cbc:LineExtensionAmount>
495
500
  <cbc:TaxExclusiveAmount currencyID="${escapeXml(currency)}">${formatAmount(totals.taxExclusiveAmount)}</cbc:TaxExclusiveAmount>
@@ -1058,8 +1063,32 @@ function validateInvoice(input) {
1058
1063
  };
1059
1064
  }
1060
1065
 
1066
+ // ../sdk/dist/core/status-precedence.js
1067
+ var STATUS_PRECEDENCE = [
1068
+ { status: "failed", family: "terminal-failure" },
1069
+ { status: "rejected", family: "terminal-failure" },
1070
+ { status: "paid", family: "terminal-success" },
1071
+ { status: "partially_paid", family: "progress" },
1072
+ { status: "accepted", family: "progress" },
1073
+ { status: "conditionally_accepted", family: "progress" },
1074
+ { status: "under_query", family: "progress" },
1075
+ { status: "in_process", family: "progress" },
1076
+ { status: "cleared", family: "progress" },
1077
+ { status: "delivered", family: "progress" },
1078
+ { status: "acknowledged", family: "progress" },
1079
+ // Terminal for developer wait semantics only — stays rank 40 (non-terminal)
1080
+ // in the projection guard (§3.12 two-level terminality).
1081
+ { status: "no_action", family: "terminal-failure" },
1082
+ { status: "submitted", family: "progress" },
1083
+ { status: "unknown", family: "fallback" }
1084
+ ];
1085
+ function statusFamily(status) {
1086
+ return STATUS_PRECEDENCE.find((e) => e.status === status)?.family ?? "fallback";
1087
+ }
1088
+ var TERMINAL_FAILURE_STATUSES = STATUS_PRECEDENCE.filter((e) => e.family === "terminal-failure").map((e) => e.status);
1089
+
1061
1090
  // ../sdk/dist/version.js
1062
- var SDK_VERSION = "2.0.0";
1091
+ var SDK_VERSION = "2.3.0";
1063
1092
 
1064
1093
  // ../sdk/dist/core/client.js
1065
1094
  function findHeaderCaseInsensitive(headers, name) {
@@ -1636,14 +1665,21 @@ function detectMimeType(filename) {
1636
1665
  }
1637
1666
  }
1638
1667
  function parseSendResult(result) {
1639
- return {
1668
+ const rawStatus = result.status == null ? void 0 : String(result.status);
1669
+ const sendResult = {
1640
1670
  id: String(result.id ?? ""),
1641
- status: mapStatus(String(result.status ?? "submitted")),
1671
+ status: mapStatus(rawStatus ?? "submitted"),
1642
1672
  peppolMessageId: result.peppolMessageId ?? result.peppol_message_id,
1643
1673
  createdAt: String(result.createdAt ?? result.updatedAt ?? result.created_at ?? (/* @__PURE__ */ new Date()).toISOString()),
1644
1674
  ublXml: result.ublXml,
1645
1675
  warnings: Array.isArray(result.warnings) ? result.warnings : void 0
1646
1676
  };
1677
+ if (rawStatus !== void 0)
1678
+ sendResult.rawStatus = rawStatus;
1679
+ const detail = parseStatusDetail(result.detail);
1680
+ if (detail)
1681
+ sendResult.detail = detail;
1682
+ return sendResult;
1647
1683
  }
1648
1684
  function parseDirectoryEntry(result) {
1649
1685
  const participant = isRecord(result.participant) ? result.participant : result;
@@ -1679,6 +1715,51 @@ function formatPeppolId(scheme, id) {
1679
1715
  function isRecord(value) {
1680
1716
  return typeof value === "object" && value !== null && !Array.isArray(value);
1681
1717
  }
1718
+ var STATUS_DETAIL_AXES = ["platformFiscal", "delivery", "businessDisposition", "settlement"];
1719
+ function parseStatusDetailEntry(raw) {
1720
+ if (!isRecord(raw) || typeof raw.axis !== "string" || typeof raw.jurisdiction !== "string" || typeof raw.code !== "string" || typeof raw.label !== "string" || typeof raw.codeSystem !== "string" || typeof raw.codeVersion !== "string") {
1721
+ return void 0;
1722
+ }
1723
+ const entry = {
1724
+ axis: raw.axis,
1725
+ jurisdiction: raw.jurisdiction,
1726
+ code: raw.code,
1727
+ label: raw.label,
1728
+ codeSystem: raw.codeSystem,
1729
+ codeVersion: raw.codeVersion
1730
+ };
1731
+ if (isRecord(raw.standardCode) && typeof raw.standardCode.system === "string" && typeof raw.standardCode.code === "string") {
1732
+ entry.standardCode = { system: raw.standardCode.system, code: raw.standardCode.code };
1733
+ }
1734
+ if (typeof raw.reason === "string")
1735
+ entry.reason = raw.reason;
1736
+ if (Array.isArray(raw.warnings) && raw.warnings.every((w) => typeof w === "string")) {
1737
+ entry.warnings = [...raw.warnings];
1738
+ }
1739
+ if (typeof raw.failureCategory === "string") {
1740
+ entry.failureCategory = raw.failureCategory;
1741
+ }
1742
+ if (isRecord(raw.payment) && typeof raw.payment.amount === "number" && typeof raw.payment.currency === "string" && typeof raw.payment.date === "string") {
1743
+ entry.payment = { amount: raw.payment.amount, currency: raw.payment.currency, date: raw.payment.date };
1744
+ }
1745
+ if (typeof raw.paymentSemantics === "string") {
1746
+ entry.paymentSemantics = raw.paymentSemantics;
1747
+ }
1748
+ if (typeof raw.actor === "string")
1749
+ entry.actor = raw.actor;
1750
+ return entry;
1751
+ }
1752
+ function parseStatusDetail(raw) {
1753
+ if (!isRecord(raw))
1754
+ return void 0;
1755
+ const detail = {};
1756
+ for (const axis of STATUS_DETAIL_AXES) {
1757
+ const entry = parseStatusDetailEntry(raw[axis]);
1758
+ if (entry)
1759
+ detail[axis] = entry;
1760
+ }
1761
+ return Object.keys(detail).length > 0 ? detail : void 0;
1762
+ }
1682
1763
  function parseContact(raw) {
1683
1764
  const contact = {
1684
1765
  id: String(raw.id ?? ""),
@@ -1734,11 +1815,17 @@ function parseLegalEntity(raw) {
1734
1815
  return le;
1735
1816
  }
1736
1817
  function parseInvoiceSummary(raw) {
1818
+ const rawStatus = (raw.state ?? raw.status) == null ? void 0 : String(raw.state ?? raw.status);
1737
1819
  const summary = {
1738
1820
  id: String(raw.id ?? ""),
1739
1821
  number: String(raw.invoiceNumber ?? raw.number ?? ""),
1740
- status: mapStatus(String(raw.state ?? raw.status ?? "submitted"))
1822
+ status: mapStatus(rawStatus ?? "submitted")
1741
1823
  };
1824
+ if (rawStatus !== void 0)
1825
+ summary.rawStatus = rawStatus;
1826
+ const detail = parseStatusDetail(raw.detail);
1827
+ if (detail)
1828
+ summary.detail = detail;
1742
1829
  if (raw.createdAt != null)
1743
1830
  summary.createdAt = String(raw.createdAt);
1744
1831
  if (typeof raw.isCreditNote === "boolean")
@@ -2166,14 +2253,19 @@ ${validation.errors.map((e) => ` - ${e.field}: ${e.message}${e.suggestion ? ` (
2166
2253
  const timeout = options?.timeout ?? 12e4;
2167
2254
  const interval = options?.interval ?? 5e3;
2168
2255
  const targets = Array.isArray(targetStatus) ? targetStatus : [targetStatus];
2169
- const terminalFailures = ["failed", "rejected"];
2170
2256
  const startTime = Date.now();
2171
2257
  while (true) {
2172
2258
  const result = await this.getStatus(documentId);
2173
2259
  if (targets.includes(result.status)) {
2174
2260
  return result;
2175
2261
  }
2176
- if (terminalFailures.includes(result.status)) {
2262
+ if (TERMINAL_FAILURE_STATUSES.includes(result.status)) {
2263
+ throw new PeppolError(`Document ${documentId} reached terminal status "${result.status}" while waiting for "${targets.join('" or "')}"`);
2264
+ }
2265
+ if (statusFamily(result.status) === "terminal-success") {
2266
+ if (targets.every((t) => statusFamily(t) === "progress")) {
2267
+ return result;
2268
+ }
2177
2269
  throw new PeppolError(`Document ${documentId} reached terminal status "${result.status}" while waiting for "${targets.join('" or "')}"`);
2178
2270
  }
2179
2271
  if (Date.now() - startTime >= timeout) {
@@ -3090,7 +3182,7 @@ function registerInitCommand(program2) {
3090
3182
  4. Send: getpeppr send ${filename}
3091
3183
 
3092
3184
  ${pc2.dim("Sandbox note:")} this template includes VAT. To send it on a sandbox
3093
- account, first register your VAT in Settings \u2192 Peppol Identity, or set each
3185
+ account, first register your VAT on the Peppol identity page, or set each
3094
3186
  line's "vatCategory" to "O" (outside the scope of VAT) for a no-VAT test send.
3095
3187
  `);
3096
3188
  process.exit(0);
@@ -3748,7 +3840,8 @@ var TERMINAL_STATES = /* @__PURE__ */ new Set([
3748
3840
  "delivered",
3749
3841
  "accepted",
3750
3842
  "rejected",
3751
- "failed"
3843
+ "failed",
3844
+ "no_action"
3752
3845
  ]);
3753
3846
  var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
3754
3847
  async function pollUntilTerminal(client, documentId, options = {}) {
@@ -3797,7 +3890,7 @@ var API_BASE = "https://api.getpeppr.dev/v1";
3797
3890
  var LOCAL_BASE = "http://localhost:3001/api/v1";
3798
3891
  var DASHBOARD_BASE = "https://console.getpeppr.dev/invoices";
3799
3892
  function registerSendCommand(program2) {
3800
- program2.command("send").description("Send an invoice to the Peppol network via getpeppr API").argument("[file]", "optional path to invoice JSON (mutex with --to/--amount/...)").option("--prod", "target production (live keys + confirmation)").option("--local", "target localhost:3001 dev server").option("--key <key>", "override API key \u2014 for CI/scripted use only; visible in `ps` and shell history. Prefer GETPEPPR_API_KEY env var.").option("--to <peppol-id>", "recipient peppol id (e.g., 9925:BE0314595348)").option("--country <iso>", "recipient ISO 3166-1 alpha-2 country override (e.g., BE)").option("--amount <number>", "line amount in major currency units (decimal allowed)").option("--currency <iso>", "ISO 4217 currency (default EUR)").option("--desc <text>", "line description").option("--attachment", "attach the test PDF").option("--watch", "poll status until delivered (60s timeout)").option("-y, --yes", "skip --prod confirmation prompt").option("--no-validate", "skip pre-validation locally").option("--json", "output JSON").option("--quiet", "exit code only, no output").action(async (file, flags) => {
3893
+ program2.command("send").description("Send an invoice to the Peppol network via getpeppr API").argument("[file]", "optional path to invoice JSON (mutex with --to/--amount/...)").option("--prod", "target production (live keys + confirmation)").option("--local", "target localhost:3001 dev server").option("--key <key>", "override API key \u2014 for CI/scripted use only; visible in `ps` and shell history. Prefer GETPEPPR_API_KEY env var.").option("--to <peppol-id>", "recipient peppol id (e.g., 9925:BE0314595348)").option("--country <iso>", "recipient ISO 3166-1 alpha-2 country override (e.g., BE)").option("--amount <number>", "line amount in major currency units (decimal allowed)").option("--currency <iso>", "ISO 4217 currency (default EUR)").option("--desc <text>", "line description").option("--attachment", "attach the test PDF").option("--watch", "poll status until a terminal state (60s timeout)").option("-y, --yes", "skip --prod confirmation prompt").option("--no-validate", "skip pre-validation locally").option("--json", "output JSON").option("--quiet", "exit code only, no output").action(async (file, flags) => {
3801
3894
  let auth;
3802
3895
  try {
3803
3896
  auth = resolveApiKey({
@@ -3907,7 +4000,7 @@ function registerSendCommand(program2) {
3907
4000
  mode
3908
4001
  );
3909
4002
  if (output) process.stdout.write(output + "\n");
3910
- if (finalStatus === "rejected" || finalStatus === "failed") {
4003
+ if (finalStatus === "rejected" || finalStatus === "failed" || finalStatus === "no_action") {
3911
4004
  process.exit(1);
3912
4005
  }
3913
4006
  process.exit(0);