@final-commerce/common 2.1.2 → 2.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1296,6 +1296,8 @@ var STATE_SCHEMA = Object.freeze({
1296
1296
  // order-state-machine/financial-invariants.ts
1297
1297
  var REFUND_ALLOWED_FULFILLMENT = /* @__PURE__ */ new Set(["fulfilled", "returned", "partially_returned", "cancelled"]);
1298
1298
  var PAYMENT_COLLECTION_ORDER = ["unpaid", "partially_paid", "paid"];
1299
+ var UNRETURNED_MONEY_STATES = /* @__PURE__ */ new Set(["partially_paid", "paid", "partially_refunded"]);
1300
+ var REFUNDED_FULFILLMENT_LANDINGS = /* @__PURE__ */ new Set(["returned", "cancelled"]);
1299
1301
  var FINANCIAL_INVARIANTS = [
1300
1302
  {
1301
1303
  id: "no-leave-refunded",
@@ -1326,6 +1328,26 @@ var FINANCIAL_INVARIANTS = [
1326
1328
  const toIdx = PAYMENT_COLLECTION_ORDER.indexOf(to.payment);
1327
1329
  return fromIdx !== -1 && toIdx !== -1 && fromIdx > toIdx;
1328
1330
  }
1331
+ },
1332
+ {
1333
+ id: "no-cancel-with-unreturned-payments",
1334
+ description: "Fulfillment cannot enter cancelled while the payment state carries unreturned captured money (paid / partially_paid / partially_refunded) \u2014 return the money first: a full refund of an unfulfilled order lands refunded \xD7 cancelled, a void lands voided \xD7 cancelled. Scoped to the cancelling MOVE (from.fulfillment !== cancelled) so payment-axis edges evaluated against an already-cancelled fulfillment do not trip it",
1335
+ check: (from, to) => from !== null && from.fulfillment !== "cancelled" && to.fulfillment === "cancelled" && UNRETURNED_MONEY_STATES.has(to.payment)
1336
+ },
1337
+ {
1338
+ id: "no-draft-regression-with-captured-payments",
1339
+ description: "Fulfillment cannot regress to draft once money has been captured \u2014 a money-carrying order is no longer a cart (bug #31: resume lands deposit orders on in_progress for the same reason). New orders and draft-to-draft moves are exempt, so partially_paid \xD7 draft remains declarable as an initial state",
1340
+ check: (from, to) => from !== null && from.fulfillment !== "draft" && to.fulfillment === "draft" && to.payment !== "unpaid"
1341
+ },
1342
+ {
1343
+ id: "no-reopen-voided-fulfillment",
1344
+ description: "A voided order exists only at cancelled fulfillment: a voided order cannot re-enter fulfillment, and a voided \xD7 non-cancelled pair cannot be materialized from nothing (complements no-leave-voided, which only freezes the payment axis). Moves INTO voided from a live payment state are the void flow\u2019s concern, not this rule\u2019s",
1345
+ check: (from, to) => (from === null || from.payment === "voided") && to.payment === "voided" && to.fulfillment !== "cancelled"
1346
+ },
1347
+ {
1348
+ id: "no-reopen-refunded-fulfillment",
1349
+ description: "A fully refunded order cannot re-enter fulfillment \u2014 it may only relabel between its canonical landings, returned and cancelled (complements no-leave-refunded, which only freezes the payment axis)",
1350
+ check: (from, to) => from?.payment === "refunded" && !REFUNDED_FULFILLMENT_LANDINGS.has(to.fulfillment)
1329
1351
  }
1330
1352
  ];
1331
1353
  function getFinancialInvariantViolations(from, to) {
@@ -1413,24 +1435,17 @@ var DISPLAY_LABEL_OVERRIDES = {
1413
1435
  "paid|on_hold": "Parked - Paid",
1414
1436
  "paid|fulfilled": "Completed",
1415
1437
  "partially_refunded|fulfilled": "Partially Refunded",
1416
- "partially_refunded|partially_returned": "Partially Refunded",
1417
- "refunded|fulfilled": "Refunded",
1418
- "refunded|returned": "Refunded",
1419
- // Full refund of a never-fulfilled order (kaching refund op lands on
1420
- // cancelled when nothing was delivered). Terminal payment states emit no
1421
- // generic rows, so without this the pair fell through to the raw
1422
- // "refunded / cancelled" engine fallback.
1423
- "refunded|cancelled": "Refunded",
1424
- "voided|cancelled": "Cancelled"
1438
+ "partially_refunded|partially_returned": "Partially Refunded"
1439
+ };
1440
+ var TERMINAL_LABEL_OVERRIDES = {
1441
+ voided: "Cancelled"
1425
1442
  };
1426
1443
  function buildDefaultDisplayStateMap() {
1427
1444
  const rules = [];
1428
1445
  for (const p of PAYMENT_STATES) {
1446
+ const terminalLabel = TERMINAL_PAYMENT_STATES.has(p) ? TERMINAL_LABEL_OVERRIDES[p] ?? PAYMENT_LABELS.get(p) : void 0;
1429
1447
  for (const f of FULFILLMENT_STATES) {
1430
- const key = `${p}|${f}`;
1431
- const override = DISPLAY_LABEL_OVERRIDES[key];
1432
- if (TERMINAL_PAYMENT_STATES.has(p) && !override) continue;
1433
- const label = override ?? `${PAYMENT_LABELS.get(p)} / ${FULFILLMENT_LABELS.get(f)}`;
1448
+ const label = DISPLAY_LABEL_OVERRIDES[`${p}|${f}`] ?? terminalLabel ?? `${PAYMENT_LABELS.get(p)} / ${FULFILLMENT_LABELS.get(f)}`;
1434
1449
  rules.push({ paymentState: p, fulfillmentState: f, label });
1435
1450
  }
1436
1451
  }
@@ -1553,6 +1568,9 @@ function buildOrderContext(order, extras) {
1553
1568
  }
1554
1569
 
1555
1570
  // order-state-machine/display-state.ts
1571
+ function atomLabel(atom, defs) {
1572
+ return defs.find((d) => d.id === atom)?.label ?? atom;
1573
+ }
1556
1574
  function matchesOptional(actual, rule) {
1557
1575
  if (rule === void 0) return true;
1558
1576
  return Array.isArray(rule) ? rule.includes(actual) : rule === actual;
@@ -1563,7 +1581,9 @@ function deriveDisplayState(payment, fulfillment, config) {
1563
1581
  return { label: rule.label, color: rule.color, icon: rule.icon };
1564
1582
  }
1565
1583
  }
1566
- return { label: `${payment} / ${fulfillment}` };
1584
+ return {
1585
+ label: `${atomLabel(payment, STATE_SCHEMA.paymentStates)} / ${atomLabel(fulfillment, STATE_SCHEMA.fulfillmentStates)}`
1586
+ };
1567
1587
  }
1568
1588
  function displayRuleMatchesPair(rule, payment, fulfillment) {
1569
1589
  return matchesOptional(payment, rule.paymentState) && matchesOptional(fulfillment, rule.fulfillmentState);
@@ -1799,13 +1819,11 @@ function labelForFulfillment(id, config) {
1799
1819
  return config.fulfillmentStates.find((s) => s.id === id)?.label ?? id;
1800
1820
  }
1801
1821
  function transitionDisplayLabel(to, config) {
1802
- const pay = labelForPayment(to.payment, config);
1803
- const ful = labelForFulfillment(to.fulfillment, config);
1804
1822
  const derived = deriveDisplayState(to.payment, to.fulfillment, config).label;
1805
- if (derived && derived !== `${to.payment} / ${to.fulfillment}`) {
1823
+ if (derived) {
1806
1824
  return derived;
1807
1825
  }
1808
- return `${pay} \xB7 ${ful}`;
1826
+ return `${labelForPayment(to.payment, config)} \xB7 ${labelForFulfillment(to.fulfillment, config)}`;
1809
1827
  }
1810
1828
  function conditionMetLines(config, from, to, context) {
1811
1829
  const lines = [];
@@ -1834,20 +1852,12 @@ function conditionMetLines(config, from, to, context) {
1834
1852
  }
1835
1853
  function getAvailableTransitions(current, config, context) {
1836
1854
  const candidates = [];
1837
- for (const s of config.paymentStates) {
1838
- if (s.id === current.payment) continue;
1839
- candidates.push({ payment: s.id, fulfillment: current.fulfillment });
1840
- }
1841
1855
  for (const s of config.fulfillmentStates) {
1842
1856
  if (s.id === current.fulfillment) continue;
1843
1857
  candidates.push({ payment: current.payment, fulfillment: s.id });
1844
1858
  }
1845
1859
  const out = [];
1846
- const seen = /* @__PURE__ */ new Set();
1847
1860
  for (const to of candidates) {
1848
- const key = `${to.payment}:${to.fulfillment}`;
1849
- if (seen.has(key)) continue;
1850
- seen.add(key);
1851
1861
  const result = canTransition({
1852
1862
  from: current,
1853
1863
  to,