@final-commerce/common 2.2.0-staging.1 → 2.2.0-staging.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.mjs CHANGED
@@ -1422,6 +1422,8 @@ var STATE_SCHEMA = Object.freeze({
1422
1422
  // order-state-machine/financial-invariants.ts
1423
1423
  var REFUND_ALLOWED_FULFILLMENT = /* @__PURE__ */ new Set(["fulfilled", "returned", "partially_returned", "cancelled"]);
1424
1424
  var PAYMENT_COLLECTION_ORDER = ["unpaid", "partially_paid", "paid"];
1425
+ var UNRETURNED_MONEY_STATES = /* @__PURE__ */ new Set(["partially_paid", "paid", "partially_refunded"]);
1426
+ var REFUNDED_FULFILLMENT_LANDINGS = /* @__PURE__ */ new Set(["returned", "cancelled"]);
1425
1427
  var FINANCIAL_INVARIANTS = [
1426
1428
  {
1427
1429
  id: "no-leave-refunded",
@@ -1452,6 +1454,26 @@ var FINANCIAL_INVARIANTS = [
1452
1454
  const toIdx = PAYMENT_COLLECTION_ORDER.indexOf(to.payment);
1453
1455
  return fromIdx !== -1 && toIdx !== -1 && fromIdx > toIdx;
1454
1456
  }
1457
+ },
1458
+ {
1459
+ id: "no-cancel-with-unreturned-payments",
1460
+ 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",
1461
+ check: (from, to) => from !== null && from.fulfillment !== "cancelled" && to.fulfillment === "cancelled" && UNRETURNED_MONEY_STATES.has(to.payment)
1462
+ },
1463
+ {
1464
+ id: "no-draft-regression-with-captured-payments",
1465
+ 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",
1466
+ check: (from, to) => from !== null && from.fulfillment !== "draft" && to.fulfillment === "draft" && to.payment !== "unpaid"
1467
+ },
1468
+ {
1469
+ id: "no-reopen-voided-fulfillment",
1470
+ 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",
1471
+ check: (from, to) => (from === null || from.payment === "voided") && to.payment === "voided" && to.fulfillment !== "cancelled"
1472
+ },
1473
+ {
1474
+ id: "no-reopen-refunded-fulfillment",
1475
+ 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)",
1476
+ check: (from, to) => from?.payment === "refunded" && !REFUNDED_FULFILLMENT_LANDINGS.has(to.fulfillment)
1455
1477
  }
1456
1478
  ];
1457
1479
  function getFinancialInvariantViolations(from, to) {
@@ -1539,24 +1561,17 @@ var DISPLAY_LABEL_OVERRIDES = {
1539
1561
  "paid|on_hold": "Parked - Paid",
1540
1562
  "paid|fulfilled": "Completed",
1541
1563
  "partially_refunded|fulfilled": "Partially Refunded",
1542
- "partially_refunded|partially_returned": "Partially Refunded",
1543
- "refunded|fulfilled": "Refunded",
1544
- "refunded|returned": "Refunded",
1545
- // Full refund of a never-fulfilled order (kaching refund op lands on
1546
- // cancelled when nothing was delivered). Terminal payment states emit no
1547
- // generic rows, so without this the pair fell through to the raw
1548
- // "refunded / cancelled" engine fallback.
1549
- "refunded|cancelled": "Refunded",
1550
- "voided|cancelled": "Cancelled"
1564
+ "partially_refunded|partially_returned": "Partially Refunded"
1565
+ };
1566
+ var TERMINAL_LABEL_OVERRIDES = {
1567
+ voided: "Cancelled"
1551
1568
  };
1552
1569
  function buildDefaultDisplayStateMap() {
1553
1570
  const rules = [];
1554
1571
  for (const p of PAYMENT_STATES) {
1572
+ const terminalLabel = TERMINAL_PAYMENT_STATES.has(p) ? TERMINAL_LABEL_OVERRIDES[p] ?? PAYMENT_LABELS.get(p) : void 0;
1555
1573
  for (const f of FULFILLMENT_STATES) {
1556
- const key = `${p}|${f}`;
1557
- const override = DISPLAY_LABEL_OVERRIDES[key];
1558
- if (TERMINAL_PAYMENT_STATES.has(p) && !override) continue;
1559
- const label = override ?? `${PAYMENT_LABELS.get(p)} / ${FULFILLMENT_LABELS.get(f)}`;
1574
+ const label = DISPLAY_LABEL_OVERRIDES[`${p}|${f}`] ?? terminalLabel ?? `${PAYMENT_LABELS.get(p)} / ${FULFILLMENT_LABELS.get(f)}`;
1560
1575
  rules.push({ paymentState: p, fulfillmentState: f, label });
1561
1576
  }
1562
1577
  }
@@ -1679,6 +1694,9 @@ function buildOrderContext(order, extras) {
1679
1694
  }
1680
1695
 
1681
1696
  // order-state-machine/display-state.ts
1697
+ function atomLabel(atom, defs) {
1698
+ return defs.find((d) => d.id === atom)?.label ?? atom;
1699
+ }
1682
1700
  function matchesOptional(actual, rule) {
1683
1701
  if (rule === void 0) return true;
1684
1702
  return Array.isArray(rule) ? rule.includes(actual) : rule === actual;
@@ -1689,7 +1707,9 @@ function deriveDisplayState(payment, fulfillment, config) {
1689
1707
  return { label: rule.label, color: rule.color, icon: rule.icon };
1690
1708
  }
1691
1709
  }
1692
- return { label: `${payment} / ${fulfillment}` };
1710
+ return {
1711
+ label: `${atomLabel(payment, STATE_SCHEMA.paymentStates)} / ${atomLabel(fulfillment, STATE_SCHEMA.fulfillmentStates)}`
1712
+ };
1693
1713
  }
1694
1714
  function displayRuleMatchesPair(rule, payment, fulfillment) {
1695
1715
  return matchesOptional(payment, rule.paymentState) && matchesOptional(fulfillment, rule.fulfillmentState);
@@ -1925,13 +1945,11 @@ function labelForFulfillment(id, config) {
1925
1945
  return config.fulfillmentStates.find((s) => s.id === id)?.label ?? id;
1926
1946
  }
1927
1947
  function transitionDisplayLabel(to, config) {
1928
- const pay = labelForPayment(to.payment, config);
1929
- const ful = labelForFulfillment(to.fulfillment, config);
1930
1948
  const derived = deriveDisplayState(to.payment, to.fulfillment, config).label;
1931
- if (derived && derived !== `${to.payment} / ${to.fulfillment}`) {
1949
+ if (derived) {
1932
1950
  return derived;
1933
1951
  }
1934
- return `${pay} \xB7 ${ful}`;
1952
+ return `${labelForPayment(to.payment, config)} \xB7 ${labelForFulfillment(to.fulfillment, config)}`;
1935
1953
  }
1936
1954
  function conditionMetLines(config, from, to, context) {
1937
1955
  const lines = [];
@@ -1960,20 +1978,12 @@ function conditionMetLines(config, from, to, context) {
1960
1978
  }
1961
1979
  function getAvailableTransitions(current, config, context) {
1962
1980
  const candidates = [];
1963
- for (const s of config.paymentStates) {
1964
- if (s.id === current.payment) continue;
1965
- candidates.push({ payment: s.id, fulfillment: current.fulfillment });
1966
- }
1967
1981
  for (const s of config.fulfillmentStates) {
1968
1982
  if (s.id === current.fulfillment) continue;
1969
1983
  candidates.push({ payment: current.payment, fulfillment: s.id });
1970
1984
  }
1971
1985
  const out = [];
1972
- const seen = /* @__PURE__ */ new Set();
1973
1986
  for (const to of candidates) {
1974
- const key = `${to.payment}:${to.fulfillment}`;
1975
- if (seen.has(key)) continue;
1976
- seen.add(key);
1977
1987
  const result = canTransition({
1978
1988
  from: current,
1979
1989
  to,