@happyvertical/smrt-commerce 0.30.0 → 0.31.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.
Files changed (32) hide show
  1. package/dist/collections/ContractLineItemCollection.d.ts +33 -0
  2. package/dist/collections/ContractLineItemCollection.d.ts.map +1 -0
  3. package/dist/collections/FulfillmentLineItemCollection.d.ts +50 -0
  4. package/dist/collections/FulfillmentLineItemCollection.d.ts.map +1 -0
  5. package/dist/collections/index.d.ts +2 -0
  6. package/dist/collections/index.d.ts.map +1 -1
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +636 -248
  10. package/dist/index.js.map +1 -1
  11. package/dist/manifest.json +284 -3
  12. package/dist/models/Fulfillment.d.ts +40 -3
  13. package/dist/models/Fulfillment.d.ts.map +1 -1
  14. package/dist/models/FulfillmentLineItem.d.ts +24 -0
  15. package/dist/models/FulfillmentLineItem.d.ts.map +1 -1
  16. package/dist/models/Invoice.d.ts +38 -2
  17. package/dist/models/Invoice.d.ts.map +1 -1
  18. package/dist/models/PaymentAllocation.d.ts +15 -3
  19. package/dist/models/PaymentAllocation.d.ts.map +1 -1
  20. package/dist/smrt-knowledge.json +57 -7
  21. package/dist/svelte/components/InvoiceCard.svelte +4 -3
  22. package/dist/svelte/components/InvoiceCard.svelte.d.ts.map +1 -1
  23. package/dist/svelte/components/InvoiceLineItems.svelte +4 -3
  24. package/dist/svelte/components/InvoiceLineItems.svelte.d.ts.map +1 -1
  25. package/dist/svelte/components/InvoiceTotals.svelte +12 -11
  26. package/dist/svelte/components/InvoiceTotals.svelte.d.ts +4 -4
  27. package/dist/svelte/components/InvoiceTotals.svelte.d.ts.map +1 -1
  28. package/dist/svelte/components/UnbilledItems.svelte +4 -3
  29. package/dist/svelte/components/UnbilledItems.svelte.d.ts.map +1 -1
  30. package/dist/svelte/types.d.ts +3 -3
  31. package/dist/svelte/types.d.ts.map +1 -1
  32. package/package.json +7 -7
package/dist/index.js CHANGED
@@ -1081,6 +1081,178 @@ class ContractCollection extends SmrtCollection {
1081
1081
  );
1082
1082
  }
1083
1083
  }
1084
+ var __defProp$8 = Object.defineProperty;
1085
+ var __getOwnPropDesc$8 = Object.getOwnPropertyDescriptor;
1086
+ var __decorateClass$8 = (decorators, target, key, kind) => {
1087
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$8(target, key) : target;
1088
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
1089
+ if (decorator = decorators[i])
1090
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
1091
+ if (kind && result) __defProp$8(target, key, result);
1092
+ return result;
1093
+ };
1094
+ let ContractLineItem = class extends SmrtObject {
1095
+ tenantId = null;
1096
+ contractId = "";
1097
+ /**
1098
+ * Item description
1099
+ */
1100
+ description = "";
1101
+ /**
1102
+ * Quantity ordered
1103
+ */
1104
+ quantity = 1;
1105
+ /**
1106
+ * Unit price before discount
1107
+ */
1108
+ unitPrice = 0;
1109
+ /**
1110
+ * Discount amount (flat, not percentage)
1111
+ */
1112
+ discount = 0;
1113
+ /**
1114
+ * Tax rate as decimal (e.g., 0.08 for 8%)
1115
+ */
1116
+ taxRate = 0;
1117
+ /**
1118
+ * Calculated line amount (qty * price - discount + tax)
1119
+ */
1120
+ amount = 0;
1121
+ productId = "";
1122
+ /**
1123
+ * SKU or item code
1124
+ */
1125
+ sku = "";
1126
+ /**
1127
+ * Start date (for lease/subscription items)
1128
+ */
1129
+ startDate = null;
1130
+ /**
1131
+ * End date (for lease/subscription items)
1132
+ */
1133
+ endDate = null;
1134
+ /**
1135
+ * Billing period (for subscriptions: 'monthly', 'yearly', etc.)
1136
+ */
1137
+ billingPeriod = "";
1138
+ /**
1139
+ * Sort order within the contract
1140
+ */
1141
+ sortOrder = 0;
1142
+ constructor(options = {}) {
1143
+ super(options);
1144
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
1145
+ if (options.contractId !== void 0) this.contractId = options.contractId;
1146
+ if (options.description !== void 0)
1147
+ this.description = options.description;
1148
+ if (options.quantity !== void 0) this.quantity = options.quantity;
1149
+ if (options.unitPrice !== void 0) this.unitPrice = options.unitPrice;
1150
+ if (options.discount !== void 0) this.discount = options.discount;
1151
+ if (options.taxRate !== void 0) this.taxRate = options.taxRate;
1152
+ if (options.amount !== void 0) this.amount = options.amount;
1153
+ if (options.productId !== void 0) this.productId = options.productId;
1154
+ if (options.sku !== void 0) this.sku = options.sku;
1155
+ if (options.startDate !== void 0) this.startDate = options.startDate;
1156
+ if (options.endDate !== void 0) this.endDate = options.endDate;
1157
+ if (options.billingPeriod !== void 0)
1158
+ this.billingPeriod = options.billingPeriod;
1159
+ if (options.sortOrder !== void 0) this.sortOrder = options.sortOrder;
1160
+ }
1161
+ /**
1162
+ * Calculate the line amount
1163
+ */
1164
+ calculateAmount() {
1165
+ const subtotal = this.quantity * this.unitPrice - this.discount;
1166
+ const tax = subtotal * this.taxRate;
1167
+ return subtotal + tax;
1168
+ }
1169
+ /**
1170
+ * Check if this is a subscription/recurring item
1171
+ */
1172
+ isRecurring() {
1173
+ return !!this.billingPeriod && !!this.startDate;
1174
+ }
1175
+ /**
1176
+ * Check if subscription is active
1177
+ */
1178
+ isSubscriptionActive() {
1179
+ if (!this.isRecurring()) return false;
1180
+ if (!this.startDate) return false;
1181
+ const now = /* @__PURE__ */ new Date();
1182
+ if (now < this.startDate) return false;
1183
+ if (this.endDate && now > this.endDate) return false;
1184
+ return true;
1185
+ }
1186
+ };
1187
+ __decorateClass$8([
1188
+ tenantId({ nullable: true })
1189
+ ], ContractLineItem.prototype, "tenantId", 2);
1190
+ __decorateClass$8([
1191
+ foreignKey("Contract")
1192
+ ], ContractLineItem.prototype, "contractId", 2);
1193
+ __decorateClass$8([
1194
+ crossPackageRef("@happyvertical/smrt-products:Product")
1195
+ ], ContractLineItem.prototype, "productId", 2);
1196
+ ContractLineItem = __decorateClass$8([
1197
+ TenantScoped({ mode: "optional" }),
1198
+ smrt({
1199
+ tableStrategy: "sti",
1200
+ api: { include: ["list", "get", "create", "update", "delete"] },
1201
+ mcp: { include: ["list", "get"] },
1202
+ cli: true
1203
+ })
1204
+ ], ContractLineItem);
1205
+ class ContractLineItemCollection extends SmrtCollection {
1206
+ static _itemClass = ContractLineItem;
1207
+ /**
1208
+ * Find contract line items by their parent contract.
1209
+ *
1210
+ * @param contractId - Contract ID
1211
+ * @returns Array of contract line items, sorted by sortOrder
1212
+ */
1213
+ async findByContract(contractId) {
1214
+ return await this.list({
1215
+ where: { contractId },
1216
+ orderBy: "sortOrder ASC"
1217
+ });
1218
+ }
1219
+ // ============================================================================
1220
+ // Tenant Helper Methods
1221
+ // ============================================================================
1222
+ /**
1223
+ * Find all contract line items belonging to a specific tenant
1224
+ *
1225
+ * @param tenantId - Tenant ID
1226
+ * @returns Array of contract line items for the tenant
1227
+ */
1228
+ async findByTenant(tenantId2) {
1229
+ return this.list({ where: { tenantId: tenantId2 } });
1230
+ }
1231
+ /**
1232
+ * Find all global contract line items (not associated with any tenant)
1233
+ *
1234
+ * @returns Array of global contract line items
1235
+ */
1236
+ async findGlobal() {
1237
+ return this.list({ where: { tenantId: null } });
1238
+ }
1239
+ /**
1240
+ * Find contract line items for a tenant including global line items
1241
+ *
1242
+ * @param tenantId - Tenant ID
1243
+ * @returns Array of tenant-specific and global contract line items
1244
+ */
1245
+ async findWithGlobals(tenantId2) {
1246
+ return this.query(
1247
+ `SELECT * FROM ${this.tableName} WHERE tenant_id = ? OR tenant_id IS NULL`,
1248
+ [tenantId2]
1249
+ );
1250
+ }
1251
+ }
1252
+ const ContractLineItemCollection$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1253
+ __proto__: null,
1254
+ ContractLineItemCollection
1255
+ }, Symbol.toStringTag, { value: "Module" }));
1084
1256
  class CustomerCollection extends SmrtCollection {
1085
1257
  static _itemClass = Customer;
1086
1258
  /**
@@ -1172,16 +1344,37 @@ class CustomerCollection extends SmrtCollection {
1172
1344
  );
1173
1345
  }
1174
1346
  }
1175
- var __defProp$8 = Object.defineProperty;
1176
- var __getOwnPropDesc$8 = Object.getOwnPropertyDescriptor;
1177
- var __decorateClass$8 = (decorators, target, key, kind) => {
1178
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$8(target, key) : target;
1347
+ var __defProp$7 = Object.defineProperty;
1348
+ var __getOwnPropDesc$7 = Object.getOwnPropertyDescriptor;
1349
+ var __decorateClass$7 = (decorators, target, key, kind) => {
1350
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$7(target, key) : target;
1179
1351
  for (var i = decorators.length - 1, decorator; i >= 0; i--)
1180
1352
  if (decorator = decorators[i])
1181
1353
  result = (kind ? decorator(target, key, result) : decorator(result)) || result;
1182
- if (kind && result) __defProp$8(target, key, result);
1354
+ if (kind && result) __defProp$7(target, key, result);
1183
1355
  return result;
1184
1356
  };
1357
+ const FULFILLMENT_STATUS_TRANSITIONS = {
1358
+ [FulfillmentStatus.PENDING]: [
1359
+ FulfillmentStatus.PROCESSING,
1360
+ FulfillmentStatus.SHIPPED,
1361
+ FulfillmentStatus.DELIVERED,
1362
+ FulfillmentStatus.CANCELLED
1363
+ ],
1364
+ [FulfillmentStatus.PROCESSING]: [
1365
+ FulfillmentStatus.SHIPPED,
1366
+ FulfillmentStatus.DELIVERED,
1367
+ FulfillmentStatus.CANCELLED
1368
+ ],
1369
+ [FulfillmentStatus.SHIPPED]: [
1370
+ FulfillmentStatus.DELIVERED,
1371
+ FulfillmentStatus.CANCELLED
1372
+ ],
1373
+ // Terminal states: no outbound transitions.
1374
+ [FulfillmentStatus.DELIVERED]: [],
1375
+ [FulfillmentStatus.CANCELLED]: []
1376
+ };
1377
+ const loadedFulfillmentStatus = /* @__PURE__ */ new WeakMap();
1185
1378
  let Fulfillment = class extends SmrtObject {
1186
1379
  tenantId = null;
1187
1380
  contractId = "";
@@ -1240,6 +1433,67 @@ let Fulfillment = class extends SmrtObject {
1240
1433
  this.estimatedDelivery = options.estimatedDelivery;
1241
1434
  if (options.notes !== void 0) this.notes = options.notes;
1242
1435
  }
1436
+ /**
1437
+ * Capture the status the row was loaded with so the save-time transition
1438
+ * guard can reject illegal status flips made via raw field assignment
1439
+ * (mass-assignment on the generated update route, a stale caller, etc.).
1440
+ * Only persisted rows carry a prior status.
1441
+ */
1442
+ async initialize() {
1443
+ await super.initialize();
1444
+ if (await this.isSaved()) {
1445
+ loadedFulfillmentStatus.set(this, this.status);
1446
+ }
1447
+ return this;
1448
+ }
1449
+ /**
1450
+ * Validate the status transition before persisting, then save. Blocks a
1451
+ * forged `status` written via raw mass-assignment on the open
1452
+ * `api:{create,update}` surface. See S5 audit #1390 follow-up.
1453
+ */
1454
+ async save() {
1455
+ const prior = await this.resolvePriorStatus();
1456
+ this.assertStatusTransition(prior);
1457
+ const result = await super.save();
1458
+ loadedFulfillmentStatus.set(this, this.status);
1459
+ return result;
1460
+ }
1461
+ /**
1462
+ * Reject an illegal status flip done via raw assignment. No-op transitions
1463
+ * and brand-new rows are always allowed.
1464
+ */
1465
+ assertStatusTransition(prior) {
1466
+ if (prior === void 0) return;
1467
+ if (prior === this.status) return;
1468
+ const allowed = FULFILLMENT_STATUS_TRANSITIONS[prior] ?? [];
1469
+ if (!allowed.includes(this.status)) {
1470
+ throw new Error(
1471
+ `Fulfillment ${this.trackingNumber || this.id}: illegal status transition '${prior}' → '${this.status}'. Use the guarded helpers (markShipped / markDelivered / cancel).`
1472
+ );
1473
+ }
1474
+ }
1475
+ /**
1476
+ * Resolve the AUTHORITATIVE prior status. The WeakMap is only populated when
1477
+ * {@link initialize} loaded the row; it is empty for an instance built via
1478
+ * `collection.create({ id: <existing>, _skipLoad: true })` (the upsert path
1479
+ * that writes onto an existing row without hydrating it). Trusting an empty
1480
+ * WeakMap there would treat the write as a brand-new row and skip the guard.
1481
+ * So when this instance carries an `id`, read the persisted row straight from
1482
+ * the DB and treat its `status` as the prior. `undefined` = genuinely new.
1483
+ * Mirrors the authoritative-prior-load on Contract/Payment/Invoice/Payout.
1484
+ */
1485
+ async resolvePriorStatus() {
1486
+ if (this.id) {
1487
+ try {
1488
+ const row = await this.db.get(this.tableName, { id: this.id });
1489
+ if (row && row.status != null) {
1490
+ return row.status;
1491
+ }
1492
+ } catch {
1493
+ }
1494
+ }
1495
+ return loadedFulfillmentStatus.get(this);
1496
+ }
1243
1497
  /**
1244
1498
  * Check if fulfillment is complete
1245
1499
  */
@@ -1259,35 +1513,54 @@ let Fulfillment = class extends SmrtObject {
1259
1513
  return this.status === FulfillmentStatus.PENDING;
1260
1514
  }
1261
1515
  /**
1262
- * Mark as shipped
1516
+ * Assert the current in-memory status may legally transition to `next`,
1517
+ * surfacing the illegal-transition error at the mutation call site rather
1518
+ * than deferring it to save(). A no-op (already in `next`) is allowed.
1519
+ */
1520
+ assertCanTransitionTo(next) {
1521
+ if (this.status === next) return;
1522
+ const allowed = FULFILLMENT_STATUS_TRANSITIONS[this.status] ?? [];
1523
+ if (!allowed.includes(next)) {
1524
+ throw new Error(
1525
+ `Fulfillment ${this.trackingNumber || this.id || "<new>"}: cannot move from '${this.status}' to '${next}'.`
1526
+ );
1527
+ }
1528
+ }
1529
+ /**
1530
+ * Mark as shipped. Rejected from a terminal state (DELIVERED / CANCELLED).
1263
1531
  */
1264
1532
  markShipped(trackingNumber, carrier) {
1533
+ this.assertCanTransitionTo(FulfillmentStatus.SHIPPED);
1265
1534
  this.status = FulfillmentStatus.SHIPPED;
1266
1535
  this.shippedAt = /* @__PURE__ */ new Date();
1267
1536
  if (trackingNumber) this.trackingNumber = trackingNumber;
1268
1537
  if (carrier) this.carrier = carrier;
1269
1538
  }
1270
1539
  /**
1271
- * Mark as delivered
1540
+ * Mark as delivered. Rejected from CANCELLED (a cancelled shipment can't be
1541
+ * delivered).
1272
1542
  */
1273
1543
  markDelivered() {
1544
+ this.assertCanTransitionTo(FulfillmentStatus.DELIVERED);
1274
1545
  this.status = FulfillmentStatus.DELIVERED;
1275
1546
  this.deliveredAt = /* @__PURE__ */ new Date();
1276
1547
  }
1277
1548
  /**
1278
- * Cancel fulfillment
1549
+ * Cancel fulfillment. Rejected once DELIVERED (a delivered shipment can't be
1550
+ * cancelled — model a return/refund elsewhere instead).
1279
1551
  */
1280
1552
  cancel() {
1553
+ this.assertCanTransitionTo(FulfillmentStatus.CANCELLED);
1281
1554
  this.status = FulfillmentStatus.CANCELLED;
1282
1555
  }
1283
1556
  };
1284
- __decorateClass$8([
1557
+ __decorateClass$7([
1285
1558
  tenantId({ nullable: true })
1286
1559
  ], Fulfillment.prototype, "tenantId", 2);
1287
- __decorateClass$8([
1560
+ __decorateClass$7([
1288
1561
  foreignKey("Contract")
1289
1562
  ], Fulfillment.prototype, "contractId", 2);
1290
- Fulfillment = __decorateClass$8([
1563
+ Fulfillment = __decorateClass$7([
1291
1564
  TenantScoped({ mode: "optional" }),
1292
1565
  smrt({
1293
1566
  api: { include: ["list", "get", "create", "update"] },
@@ -1411,14 +1684,218 @@ class FulfillmentCollection extends SmrtCollection {
1411
1684
  );
1412
1685
  }
1413
1686
  }
1414
- var __defProp$7 = Object.defineProperty;
1415
- var __getOwnPropDesc$7 = Object.getOwnPropertyDescriptor;
1416
- var __decorateClass$7 = (decorators, target, key, kind) => {
1417
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$7(target, key) : target;
1687
+ const FulfillmentCollection$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1688
+ __proto__: null,
1689
+ FulfillmentCollection
1690
+ }, Symbol.toStringTag, { value: "Module" }));
1691
+ var __defProp$6 = Object.defineProperty;
1692
+ var __getOwnPropDesc$6 = Object.getOwnPropertyDescriptor;
1693
+ var __decorateClass$6 = (decorators, target, key, kind) => {
1694
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$6(target, key) : target;
1418
1695
  for (var i = decorators.length - 1, decorator; i >= 0; i--)
1419
1696
  if (decorator = decorators[i])
1420
1697
  result = (kind ? decorator(target, key, result) : decorator(result)) || result;
1421
- if (kind && result) __defProp$7(target, key, result);
1698
+ if (kind && result) __defProp$6(target, key, result);
1699
+ return result;
1700
+ };
1701
+ const FULFILLMENT_QUANTITY_EPSILON = 0.01;
1702
+ let FulfillmentLineItem = class extends SmrtObject {
1703
+ tenantId = null;
1704
+ fulfillmentId = "";
1705
+ contractLineItemId = "";
1706
+ /**
1707
+ * Quantity fulfilled in this fulfillment
1708
+ */
1709
+ quantityFulfilled = 1;
1710
+ /**
1711
+ * Notes about this line item
1712
+ */
1713
+ notes = "";
1714
+ constructor(options = {}) {
1715
+ super(options);
1716
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
1717
+ if (options.fulfillmentId !== void 0)
1718
+ this.fulfillmentId = options.fulfillmentId;
1719
+ if (options.contractLineItemId !== void 0)
1720
+ this.contractLineItemId = options.contractLineItemId;
1721
+ if (options.quantityFulfilled !== void 0)
1722
+ this.quantityFulfilled = options.quantityFulfilled;
1723
+ if (options.notes !== void 0) this.notes = options.notes;
1724
+ }
1725
+ /**
1726
+ * Save-time over-fulfillment guard (S5 audit #1390 follow-up, round 2):
1727
+ * - `quantityFulfilled` must be a finite, positive number,
1728
+ * - the referenced `ContractLineItem` must resolve **within the caller's
1729
+ * tenant scope** AND belong to the same contract as the parent
1730
+ * Fulfillment, and
1731
+ * - the sum of all FulfillmentLineItems against that ContractLineItem
1732
+ * (this row included) must not exceed the ordered `ContractLineItem.quantity`.
1733
+ *
1734
+ * Without this, a caller could ship 50 of 10 ordered — the direct parallel
1735
+ * to the PaymentAllocation over-allocation hole #1390 closed.
1736
+ *
1737
+ * Tenant isolation (round 2): the ContractLineItem is loaded through
1738
+ * `ContractLineItemCollection`, so it goes through the tenancy
1739
+ * auto-filtering interceptors. A cross-tenant `contractLineItemId` therefore
1740
+ * fails closed — it resolves to `null` and trips the existing "does not
1741
+ * exist" error rather than leaking a foreign-tenant ordered quantity. The
1742
+ * raw `this.db.get('contract_line_items', …)` it replaced bypassed those
1743
+ * filters. We additionally load the parent Fulfillment (also tenant-scoped)
1744
+ * and reject when the line item belongs to a *different* contract than the
1745
+ * Fulfillment is fulfilling, so a valid-but-unrelated line id can't be
1746
+ * used to fulfill against the wrong order.
1747
+ */
1748
+ async save() {
1749
+ if (!Number.isFinite(this.quantityFulfilled) || this.quantityFulfilled <= 0) {
1750
+ throw new Error(
1751
+ `FulfillmentLineItem ${this.id ?? "<new>"}: quantityFulfilled must be a positive number (got ${this.quantityFulfilled}).`
1752
+ );
1753
+ }
1754
+ if (this.contractLineItemId) {
1755
+ const { ContractLineItemCollection: ContractLineItemCollection2 } = await Promise.resolve().then(() => ContractLineItemCollection$1);
1756
+ const lineItems = await ContractLineItemCollection2.create(
1757
+ this.options
1758
+ );
1759
+ const orderedLineItem = await lineItems.get(this.contractLineItemId);
1760
+ if (!orderedLineItem) {
1761
+ throw new Error(
1762
+ `FulfillmentLineItem ${this.id ?? "<new>"}: referenced ContractLineItem '${this.contractLineItemId}' does not exist — refusing to fulfill against a missing line item (the over-fulfillment cap cannot be enforced otherwise).`
1763
+ );
1764
+ }
1765
+ if (this.fulfillmentId) {
1766
+ const { FulfillmentCollection: FulfillmentCollection2 } = await Promise.resolve().then(() => FulfillmentCollection$1);
1767
+ const fulfillments = await FulfillmentCollection2.create(
1768
+ this.options
1769
+ );
1770
+ const fulfillment = await fulfillments.get(this.fulfillmentId);
1771
+ if (fulfillment && orderedLineItem.contractId !== fulfillment.contractId) {
1772
+ throw new Error(
1773
+ `FulfillmentLineItem ${this.id ?? "<new>"}: ContractLineItem '${this.contractLineItemId}' belongs to contract '${orderedLineItem.contractId}', but its Fulfillment '${this.fulfillmentId}' fulfills contract '${fulfillment.contractId}' — refusing to fulfill a line item from a different contract.`
1774
+ );
1775
+ }
1776
+ }
1777
+ const ordered = Number(orderedLineItem.quantity);
1778
+ const { FulfillmentLineItemCollection: FulfillmentLineItemCollection2 } = await Promise.resolve().then(() => FulfillmentLineItemCollection$1);
1779
+ const siblings = await FulfillmentLineItemCollection2.create(
1780
+ this.options
1781
+ );
1782
+ const existing = await siblings.findByContractLineItem(
1783
+ this.contractLineItemId
1784
+ );
1785
+ const otherFulfilled = existing.reduce(
1786
+ (sum, item) => item.id === this.id ? sum : sum + item.quantityFulfilled,
1787
+ 0
1788
+ );
1789
+ if (Number.isFinite(ordered) && otherFulfilled + this.quantityFulfilled - ordered > FULFILLMENT_QUANTITY_EPSILON) {
1790
+ throw new Error(
1791
+ `FulfillmentLineItem ${this.id ?? "<new>"}: fulfilling ${this.quantityFulfilled} would over-fulfill contract line '${this.contractLineItemId}' — already fulfilled ${otherFulfilled} of ${ordered} ordered.`
1792
+ );
1793
+ }
1794
+ }
1795
+ return super.save();
1796
+ }
1797
+ };
1798
+ __decorateClass$6([
1799
+ tenantId({ nullable: true })
1800
+ ], FulfillmentLineItem.prototype, "tenantId", 2);
1801
+ __decorateClass$6([
1802
+ foreignKey("Fulfillment")
1803
+ ], FulfillmentLineItem.prototype, "fulfillmentId", 2);
1804
+ __decorateClass$6([
1805
+ foreignKey("ContractLineItem")
1806
+ ], FulfillmentLineItem.prototype, "contractLineItemId", 2);
1807
+ FulfillmentLineItem = __decorateClass$6([
1808
+ TenantScoped({ mode: "optional" }),
1809
+ smrt({
1810
+ api: { include: ["list", "get", "create", "update", "delete"] },
1811
+ mcp: { include: ["list", "get"] },
1812
+ cli: true
1813
+ })
1814
+ ], FulfillmentLineItem);
1815
+ class FulfillmentLineItemCollection extends SmrtCollection {
1816
+ static _itemClass = FulfillmentLineItem;
1817
+ /**
1818
+ * Find fulfillment line items by their parent fulfillment.
1819
+ *
1820
+ * @param fulfillmentId - Fulfillment ID
1821
+ * @returns Array of fulfillment line items
1822
+ */
1823
+ async findByFulfillment(fulfillmentId) {
1824
+ return await this.list({
1825
+ where: { fulfillmentId },
1826
+ orderBy: "created_at DESC"
1827
+ });
1828
+ }
1829
+ /**
1830
+ * Find every fulfillment line item that fulfills a given contract line item,
1831
+ * across all fulfillments. Used by the over-fulfillment cap to sum how much
1832
+ * of an ordered line has already been fulfilled.
1833
+ *
1834
+ * @param contractLineItemId - Contract line item ID
1835
+ * @returns Array of fulfillment line items targeting that contract line
1836
+ */
1837
+ async findByContractLineItem(contractLineItemId) {
1838
+ return await this.list({
1839
+ where: { contractLineItemId },
1840
+ orderBy: "created_at DESC"
1841
+ });
1842
+ }
1843
+ /**
1844
+ * Sum the quantity already fulfilled for a contract line item across all
1845
+ * fulfillments.
1846
+ *
1847
+ * @param contractLineItemId - Contract line item ID
1848
+ * @returns Total quantity fulfilled
1849
+ */
1850
+ async getTotalFulfilledForContractLine(contractLineItemId) {
1851
+ const items = await this.findByContractLineItem(contractLineItemId);
1852
+ return items.reduce((sum, item) => sum + item.quantityFulfilled, 0);
1853
+ }
1854
+ // ============================================================================
1855
+ // Tenant Helper Methods
1856
+ // ============================================================================
1857
+ /**
1858
+ * Find all fulfillment line items belonging to a specific tenant
1859
+ *
1860
+ * @param tenantId - Tenant ID
1861
+ * @returns Array of fulfillment line items for the tenant
1862
+ */
1863
+ async findByTenant(tenantId2) {
1864
+ return this.list({ where: { tenantId: tenantId2 } });
1865
+ }
1866
+ /**
1867
+ * Find all global fulfillment line items (not associated with any tenant)
1868
+ *
1869
+ * @returns Array of global fulfillment line items
1870
+ */
1871
+ async findGlobal() {
1872
+ return this.list({ where: { tenantId: null } });
1873
+ }
1874
+ /**
1875
+ * Find fulfillment line items for a tenant including global line items
1876
+ *
1877
+ * @param tenantId - Tenant ID
1878
+ * @returns Array of tenant-specific and global fulfillment line items
1879
+ */
1880
+ async findWithGlobals(tenantId2) {
1881
+ return this.query(
1882
+ `SELECT * FROM ${this.tableName} WHERE tenant_id = ? OR tenant_id IS NULL`,
1883
+ [tenantId2]
1884
+ );
1885
+ }
1886
+ }
1887
+ const FulfillmentLineItemCollection$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1888
+ __proto__: null,
1889
+ FulfillmentLineItemCollection
1890
+ }, Symbol.toStringTag, { value: "Module" }));
1891
+ var __defProp$5 = Object.defineProperty;
1892
+ var __getOwnPropDesc$5 = Object.getOwnPropertyDescriptor;
1893
+ var __decorateClass$5 = (decorators, target, key, kind) => {
1894
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$5(target, key) : target;
1895
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
1896
+ if (decorator = decorators[i])
1897
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
1898
+ if (kind && result) __defProp$5(target, key, result);
1422
1899
  return result;
1423
1900
  };
1424
1901
  const INVOICE_EPSILON = 0.01;
@@ -1742,6 +2219,35 @@ let Invoice = class extends SmrtObject {
1742
2219
  }
1743
2220
  }
1744
2221
  }
2222
+ /**
2223
+ * Whether `amountPaid` covers `totalAmount` within the sub-cent rounding
2224
+ * tolerance. This is the SINGLE source of truth for the **PAID** decision —
2225
+ * both {@link updatePaymentStatus} (which decides PAID) and
2226
+ * {@link assertPaymentStatusConsistent} (which validates PAID on save) call
2227
+ * it, so the PAID-deciding comparison and the PAID-validating comparison can
2228
+ * never drift apart. A strict `amountPaid >= totalAmount` here would diverge
2229
+ * from the epsilon-tolerant guard: a float-summed total paid exactly (e.g.
2230
+ * `0.1 × 3` line items paid `0.3`) reads as "not covered" by `>=` but
2231
+ * "covered" by the guard, so `updatePaymentStatus` would set PARTIAL and the
2232
+ * save-time guard would then reject it — leaving a genuinely-paid invoice
2233
+ * unsaveable (S5 audit #1390 follow-up).
2234
+ *
2235
+ * NOTE: the claim is scoped to PAID. The PARTIAL branch is NOT unified the
2236
+ * same way — {@link updatePaymentStatus} treats any `amountPaid > 0` as
2237
+ * PARTIAL, whereas {@link isPartiallyPaid} (used by the save-time guard)
2238
+ * requires `amountPaid > INVOICE_EPSILON`. That pre-existing asymmetry only
2239
+ * matters for a sub-cent dust payment and is intentionally left as-is.
2240
+ */
2241
+ isFullyPaid() {
2242
+ return this.amountPaid - this.totalAmount >= -INVOICE_EPSILON;
2243
+ }
2244
+ /**
2245
+ * Whether `amountPaid` is a non-trivial partial payment of `totalAmount`.
2246
+ * Derived from {@link isFullyPaid} so the two stay mutually exclusive.
2247
+ */
2248
+ isPartiallyPaid() {
2249
+ return this.amountPaid > INVOICE_EPSILON && !this.isFullyPaid();
2250
+ }
1745
2251
  /**
1746
2252
  * After amounts are recomputed and amountPaid is re-derived from allocations,
1747
2253
  * assert the persisted `status` is consistent with the derived
@@ -1757,8 +2263,8 @@ let Invoice = class extends SmrtObject {
1757
2263
  */
1758
2264
  assertPaymentStatusConsistent() {
1759
2265
  const label = this.invoiceNumber || this.id || "<new>";
1760
- const fullyPaid = this.amountPaid - this.totalAmount >= -INVOICE_EPSILON;
1761
- const partiallyPaid = this.amountPaid > INVOICE_EPSILON && !fullyPaid;
2266
+ const fullyPaid = this.isFullyPaid();
2267
+ const partiallyPaid = this.isPartiallyPaid();
1762
2268
  if (this.status === InvoiceStatus.PAID && !fullyPaid) {
1763
2269
  throw new Error(
1764
2270
  `Invoice ${label}: status is PAID but derived amountPaid ${this.amountPaid} does not cover totalAmount ${this.totalAmount}. Payment status is derived from PaymentAllocations — use updatePaymentStatus().`
@@ -1776,8 +2282,19 @@ let Invoice = class extends SmrtObject {
1776
2282
  }
1777
2283
  }
1778
2284
  /**
1779
- * Enforce `totalAmount === subtotal + taxAmount` (within rounding tolerance).
1780
- * Used when the invoice has no line items to recompute from.
2285
+ * Enforce `totalAmount === subtotal + taxAmount` (within rounding tolerance),
2286
+ * then SNAP `totalAmount` to the exact arithmetic. Used when the invoice has
2287
+ * no line items to recompute from.
2288
+ *
2289
+ * The snap closes the invoice-vs-ledger epsilon gap (S5 audit #1390
2290
+ * follow-up): the invoice guard tolerates `INVOICE_EPSILON` (0.01) but the
2291
+ * smrt-ledgers balance check uses a tighter `BALANCE_EPSILON` (0.001). A
2292
+ * no-line-item invoice with, say, subtotal 100.00 / total 100.005 passes this
2293
+ * guard, yet `recognizeRevenue` would build DR 100.005 / CR 100.00 and
2294
+ * `journal.post()` would reject it as unbalanced — voiding the journal and
2295
+ * permanently blocking revenue recognition. Snapping `totalAmount` to
2296
+ * `subtotal + taxAmount` here (after the tolerance check) means the persisted
2297
+ * total and every journal built from it are always ledger-consistent.
1781
2298
  */
1782
2299
  assertTotalArithmetic() {
1783
2300
  const expected = this.subtotal + this.taxAmount;
@@ -1786,6 +2303,7 @@ let Invoice = class extends SmrtObject {
1786
2303
  `Invoice ${this.invoiceNumber || this.id || "<new>"}: totalAmount ${this.totalAmount} must equal subtotal + taxAmount (${expected}).`
1787
2304
  );
1788
2305
  }
2306
+ this.totalAmount = expected;
1789
2307
  }
1790
2308
  /**
1791
2309
  * Reject negative financial values and an amountPaid that exceeds the total
@@ -1911,7 +2429,7 @@ let Invoice = class extends SmrtObject {
1911
2429
  if (this.status === InvoiceStatus.CANCELLED || this.status === InvoiceStatus.WRITTEN_OFF) {
1912
2430
  return;
1913
2431
  }
1914
- if (amountPaid >= this.totalAmount) {
2432
+ if (this.isFullyPaid()) {
1915
2433
  this.status = InvoiceStatus.PAID;
1916
2434
  this.paidDate = /* @__PURE__ */ new Date();
1917
2435
  } else if (amountPaid > 0) {
@@ -2092,22 +2610,22 @@ let Invoice = class extends SmrtObject {
2092
2610
  };
2093
2611
  }
2094
2612
  };
2095
- __decorateClass$7([
2613
+ __decorateClass$5([
2096
2614
  tenantId({ nullable: true })
2097
2615
  ], Invoice.prototype, "tenantId", 2);
2098
- __decorateClass$7([
2616
+ __decorateClass$5([
2099
2617
  foreignKey("Customer")
2100
2618
  ], Invoice.prototype, "customerId", 2);
2101
- __decorateClass$7([
2619
+ __decorateClass$5([
2102
2620
  foreignKey("Contract")
2103
2621
  ], Invoice.prototype, "contractId", 2);
2104
- __decorateClass$7([
2622
+ __decorateClass$5([
2105
2623
  crossPackageRef("@happyvertical/smrt-ledgers:Journal")
2106
2624
  ], Invoice.prototype, "arJournalId", 2);
2107
- __decorateClass$7([
2625
+ __decorateClass$5([
2108
2626
  crossPackageRef("@happyvertical/smrt-ledgers:Journal")
2109
2627
  ], Invoice.prototype, "revenueJournalId", 2);
2110
- Invoice = __decorateClass$7([
2628
+ Invoice = __decorateClass$5([
2111
2629
  TenantScoped({ mode: "optional" }),
2112
2630
  smrt({
2113
2631
  // ROOT FIX (S5 audit #1390 round 4): the generated create/update surface may
@@ -2398,17 +2916,22 @@ class InvoiceCollection extends SmrtCollection {
2398
2916
  return this.query(
2399
2917
  `SELECT * FROM ${this.tableName} WHERE tenant_id = ? OR tenant_id IS NULL`,
2400
2918
  [tenantId2]
2401
- );
2402
- }
2403
- }
2404
- var __defProp$6 = Object.defineProperty;
2405
- var __getOwnPropDesc$6 = Object.getOwnPropertyDescriptor;
2406
- var __decorateClass$6 = (decorators, target, key, kind) => {
2407
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$6(target, key) : target;
2919
+ );
2920
+ }
2921
+ }
2922
+ const InvoiceCollection$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
2923
+ __proto__: null,
2924
+ InvoiceCollection,
2925
+ UNPAID_STATUSES
2926
+ }, Symbol.toStringTag, { value: "Module" }));
2927
+ var __defProp$4 = Object.defineProperty;
2928
+ var __getOwnPropDesc$4 = Object.getOwnPropertyDescriptor;
2929
+ var __decorateClass$4 = (decorators, target, key, kind) => {
2930
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$4(target, key) : target;
2408
2931
  for (var i = decorators.length - 1, decorator; i >= 0; i--)
2409
2932
  if (decorator = decorators[i])
2410
2933
  result = (kind ? decorator(target, key, result) : decorator(result)) || result;
2411
- if (kind && result) __defProp$6(target, key, result);
2934
+ if (kind && result) __defProp$4(target, key, result);
2412
2935
  return result;
2413
2936
  };
2414
2937
  let InvoiceLineItem = class extends SmrtObject {
@@ -2543,16 +3066,16 @@ let InvoiceLineItem = class extends SmrtObject {
2543
3066
  };
2544
3067
  }
2545
3068
  };
2546
- __decorateClass$6([
3069
+ __decorateClass$4([
2547
3070
  tenantId({ nullable: true })
2548
3071
  ], InvoiceLineItem.prototype, "tenantId", 2);
2549
- __decorateClass$6([
3072
+ __decorateClass$4([
2550
3073
  foreignKey("Invoice")
2551
3074
  ], InvoiceLineItem.prototype, "invoiceId", 2);
2552
- __decorateClass$6([
3075
+ __decorateClass$4([
2553
3076
  crossPackageRef("@happyvertical/smrt-ledgers:Account")
2554
3077
  ], InvoiceLineItem.prototype, "revenueAccountId", 2);
2555
- InvoiceLineItem = __decorateClass$6([
3078
+ InvoiceLineItem = __decorateClass$4([
2556
3079
  TenantScoped({ mode: "optional" }),
2557
3080
  smrt({
2558
3081
  api: { include: ["list", "get", "create", "update", "delete"] },
@@ -2702,14 +3225,14 @@ const InvoiceLineItemCollection$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ *
2702
3225
  __proto__: null,
2703
3226
  InvoiceLineItemCollection
2704
3227
  }, Symbol.toStringTag, { value: "Module" }));
2705
- var __defProp$5 = Object.defineProperty;
2706
- var __getOwnPropDesc$5 = Object.getOwnPropertyDescriptor;
2707
- var __decorateClass$5 = (decorators, target, key, kind) => {
2708
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$5(target, key) : target;
3228
+ var __defProp$3 = Object.defineProperty;
3229
+ var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
3230
+ var __decorateClass$3 = (decorators, target, key, kind) => {
3231
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
2709
3232
  for (var i = decorators.length - 1, decorator; i >= 0; i--)
2710
3233
  if (decorator = decorators[i])
2711
3234
  result = (kind ? decorator(target, key, result) : decorator(result)) || result;
2712
- if (kind && result) __defProp$5(target, key, result);
3235
+ if (kind && result) __defProp$3(target, key, result);
2713
3236
  return result;
2714
3237
  };
2715
3238
  const ALLOCATION_EPSILON = 0.01;
@@ -2746,12 +3269,14 @@ let PaymentAllocation = class extends SmrtObject {
2746
3269
  if (options.notes !== void 0) this.notes = options.notes;
2747
3270
  }
2748
3271
  /**
2749
- * Save-time integrity guard (S5 audit #1390):
2750
- * - allocation `amount` must be a finite, positive number, and
3272
+ * Save-time integrity guard (S5 audit #1390 + follow-up):
3273
+ * - allocation `amount` must be a finite, positive number,
2751
3274
  * - the sum of all allocations against the referenced Payment (this row
2752
3275
  * included) must not exceed the Payment's amount — over-applying a
2753
3276
  * payment across invoices would falsify both payment and invoice
2754
- * balances.
3277
+ * balances, and
3278
+ * - the sum of all allocations against the referenced Invoice (this row
3279
+ * included) must not exceed the Invoice's `totalAmount`.
2755
3280
  *
2756
3281
  * The Payment-amount cap is enforced against the persisted Payment row. An
2757
3282
  * allocation always carries a `@foreignKey('Payment')` paymentId, so a
@@ -2760,6 +3285,16 @@ let PaymentAllocation = class extends SmrtObject {
2760
3285
  * entirely, letting a caller over-apply (or fabricate) funds simply by
2761
3286
  * pointing at a non-existent payment. An empty `paymentId` is still rejected
2762
3287
  * by the underlying FK requirement; the positivity check always applies.
3288
+ *
3289
+ * The Invoice-total cap (follow-up) closes a complementary hole: the
3290
+ * per-Payment cap lets allocations from *different* payments each pass their
3291
+ * own check while jointly summing above the invoice total. The next
3292
+ * `Invoice.save()` would then recompute `amountPaid` from these allocations,
3293
+ * trip `assertNonNegativeAmounts` (amountPaid > totalAmount), and leave the
3294
+ * invoice permanently unsaveable while the over-allocations persist. Capping
3295
+ * here keeps allocations from ever exceeding what the invoice owes. The cap
3296
+ * is skipped only when the invoice row can't be resolved (e.g. ledger-less /
3297
+ * not-yet-persisted) so it never blocks an otherwise-valid allocation.
2763
3298
  */
2764
3299
  async save() {
2765
3300
  if (!Number.isFinite(this.amount) || this.amount <= 0) {
@@ -2791,19 +3326,42 @@ let PaymentAllocation = class extends SmrtObject {
2791
3326
  );
2792
3327
  }
2793
3328
  }
3329
+ if (this.invoiceId) {
3330
+ const { InvoiceCollection: InvoiceCollection2 } = await Promise.resolve().then(() => InvoiceCollection$1);
3331
+ const invoices = await InvoiceCollection2.create(this.options);
3332
+ const invoice = await invoices.get({ id: this.invoiceId });
3333
+ if (invoice && invoice.totalAmount > ALLOCATION_EPSILON) {
3334
+ const { PaymentAllocationCollection: PaymentAllocationCollection2 } = await Promise.resolve().then(() => PaymentAllocationCollection$1);
3335
+ const allocations = await PaymentAllocationCollection2.create(
3336
+ this.options
3337
+ );
3338
+ const existingForInvoice = await allocations.findByInvoice(
3339
+ this.invoiceId
3340
+ );
3341
+ const otherInvoiceTotal = existingForInvoice.reduce(
3342
+ (sum, alloc) => alloc.id === this.id ? sum : sum + alloc.amount,
3343
+ 0
3344
+ );
3345
+ if (otherInvoiceTotal + this.amount - invoice.totalAmount > ALLOCATION_EPSILON) {
3346
+ throw new Error(
3347
+ `PaymentAllocation ${this.id ?? "<new>"}: allocating ${this.amount} would over-pay invoice '${this.invoiceId}' — already allocated ${otherInvoiceTotal} of ${invoice.totalAmount}.`
3348
+ );
3349
+ }
3350
+ }
3351
+ }
2794
3352
  return super.save();
2795
3353
  }
2796
3354
  };
2797
- __decorateClass$5([
3355
+ __decorateClass$3([
2798
3356
  tenantId({ nullable: true })
2799
3357
  ], PaymentAllocation.prototype, "tenantId", 2);
2800
- __decorateClass$5([
3358
+ __decorateClass$3([
2801
3359
  foreignKey("Payment")
2802
3360
  ], PaymentAllocation.prototype, "paymentId", 2);
2803
- __decorateClass$5([
3361
+ __decorateClass$3([
2804
3362
  foreignKey("Invoice")
2805
3363
  ], PaymentAllocation.prototype, "invoiceId", 2);
2806
- PaymentAllocation = __decorateClass$5([
3364
+ PaymentAllocation = __decorateClass$3([
2807
3365
  TenantScoped({ mode: "optional" }),
2808
3366
  smrt({
2809
3367
  // NOTE: `create` and `delete` are intentionally NOT exposed over the
@@ -2968,14 +3526,14 @@ const PaymentAllocationCollection$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__
2968
3526
  __proto__: null,
2969
3527
  PaymentAllocationCollection
2970
3528
  }, Symbol.toStringTag, { value: "Module" }));
2971
- var __defProp$4 = Object.defineProperty;
2972
- var __getOwnPropDesc$4 = Object.getOwnPropertyDescriptor;
2973
- var __decorateClass$4 = (decorators, target, key, kind) => {
2974
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$4(target, key) : target;
3529
+ var __defProp$2 = Object.defineProperty;
3530
+ var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
3531
+ var __decorateClass$2 = (decorators, target, key, kind) => {
3532
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
2975
3533
  for (var i = decorators.length - 1, decorator; i >= 0; i--)
2976
3534
  if (decorator = decorators[i])
2977
3535
  result = (kind ? decorator(target, key, result) : decorator(result)) || result;
2978
- if (kind && result) __defProp$4(target, key, result);
3536
+ if (kind && result) __defProp$2(target, key, result);
2979
3537
  return result;
2980
3538
  };
2981
3539
  const PAYMENT_STATUS_TRANSITIONS = {
@@ -3409,19 +3967,19 @@ let Payment = class extends SmrtObject {
3409
3967
  this.status = PaymentStatus.CANCELLED;
3410
3968
  }
3411
3969
  };
3412
- __decorateClass$4([
3970
+ __decorateClass$2([
3413
3971
  tenantId({ nullable: true })
3414
3972
  ], Payment.prototype, "tenantId", 2);
3415
- __decorateClass$4([
3973
+ __decorateClass$2([
3416
3974
  foreignKey("Contract")
3417
3975
  ], Payment.prototype, "contractId", 2);
3418
- __decorateClass$4([
3976
+ __decorateClass$2([
3419
3977
  foreignKey("Customer")
3420
3978
  ], Payment.prototype, "customerId", 2);
3421
- __decorateClass$4([
3979
+ __decorateClass$2([
3422
3980
  crossPackageRef("@happyvertical/smrt-ledgers:Journal")
3423
3981
  ], Payment.prototype, "journalId", 2);
3424
- Payment = __decorateClass$4([
3982
+ Payment = __decorateClass$2([
3425
3983
  TenantScoped({ mode: "optional" }),
3426
3984
  smrt({
3427
3985
  // ROOT FIX (S5 audit #1390 round 4): restrict the generated create/update
@@ -3629,14 +4187,14 @@ const PaymentCollection$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object
3629
4187
  __proto__: null,
3630
4188
  PaymentCollection
3631
4189
  }, Symbol.toStringTag, { value: "Module" }));
3632
- var __defProp$3 = Object.defineProperty;
3633
- var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
3634
- var __decorateClass$3 = (decorators, target, key, kind) => {
3635
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
4190
+ var __defProp$1 = Object.defineProperty;
4191
+ var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
4192
+ var __decorateClass$1 = (decorators, target, key, kind) => {
4193
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
3636
4194
  for (var i = decorators.length - 1, decorator; i >= 0; i--)
3637
4195
  if (decorator = decorators[i])
3638
4196
  result = (kind ? decorator(target, key, result) : decorator(result)) || result;
3639
- if (kind && result) __defProp$3(target, key, result);
4197
+ if (kind && result) __defProp$1(target, key, result);
3640
4198
  return result;
3641
4199
  };
3642
4200
  const PAYMENT_INTENT_EPSILON = 0.01;
@@ -4333,10 +4891,10 @@ Retired: ${reason}` : `Retired: ${reason}`;
4333
4891
  return null;
4334
4892
  }
4335
4893
  };
4336
- __decorateClass$3([
4894
+ __decorateClass$1([
4337
4895
  tenantId({ nullable: true })
4338
4896
  ], PaymentIntent.prototype, "tenantId", 2);
4339
- PaymentIntent = __decorateClass$3([
4897
+ PaymentIntent = __decorateClass$1([
4340
4898
  TenantScoped({ mode: "optional" }),
4341
4899
  smrt({
4342
4900
  // Natural-key idempotency: a consumer that retries `create` with the
@@ -4488,14 +5046,14 @@ class PaymentIntentCollection extends SmrtCollection {
4488
5046
  );
4489
5047
  }
4490
5048
  }
4491
- var __defProp$2 = Object.defineProperty;
4492
- var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
4493
- var __decorateClass$2 = (decorators, target, key, kind) => {
4494
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
5049
+ var __defProp = Object.defineProperty;
5050
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5051
+ var __decorateClass = (decorators, target, key, kind) => {
5052
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
4495
5053
  for (var i = decorators.length - 1, decorator; i >= 0; i--)
4496
5054
  if (decorator = decorators[i])
4497
5055
  result = (kind ? decorator(target, key, result) : decorator(result)) || result;
4498
- if (kind && result) __defProp$2(target, key, result);
5056
+ if (kind && result) __defProp(target, key, result);
4499
5057
  return result;
4500
5058
  };
4501
5059
  const PAYOUT_EPSILON = 0.01;
@@ -4863,13 +5421,13 @@ let Payout = class extends SmrtObject {
4863
5421
  return null;
4864
5422
  }
4865
5423
  };
4866
- __decorateClass$2([
5424
+ __decorateClass([
4867
5425
  tenantId({ nullable: true })
4868
5426
  ], Payout.prototype, "tenantId", 2);
4869
- __decorateClass$2([
5427
+ __decorateClass([
4870
5428
  foreignKey(Vendor)
4871
5429
  ], Payout.prototype, "vendorId", 2);
4872
- Payout = __decorateClass$2([
5430
+ Payout = __decorateClass([
4873
5431
  TenantScoped({ mode: "optional" }),
4874
5432
  smrt({
4875
5433
  // ROOT FIX (S5 audit #1390 round 4): a Payout has NO safe generated write.
@@ -5085,178 +5643,6 @@ class VendorCollection extends SmrtCollection {
5085
5643
  );
5086
5644
  }
5087
5645
  }
5088
- var __defProp$1 = Object.defineProperty;
5089
- var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
5090
- var __decorateClass$1 = (decorators, target, key, kind) => {
5091
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
5092
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
5093
- if (decorator = decorators[i])
5094
- result = (kind ? decorator(target, key, result) : decorator(result)) || result;
5095
- if (kind && result) __defProp$1(target, key, result);
5096
- return result;
5097
- };
5098
- let ContractLineItem = class extends SmrtObject {
5099
- tenantId = null;
5100
- contractId = "";
5101
- /**
5102
- * Item description
5103
- */
5104
- description = "";
5105
- /**
5106
- * Quantity ordered
5107
- */
5108
- quantity = 1;
5109
- /**
5110
- * Unit price before discount
5111
- */
5112
- unitPrice = 0;
5113
- /**
5114
- * Discount amount (flat, not percentage)
5115
- */
5116
- discount = 0;
5117
- /**
5118
- * Tax rate as decimal (e.g., 0.08 for 8%)
5119
- */
5120
- taxRate = 0;
5121
- /**
5122
- * Calculated line amount (qty * price - discount + tax)
5123
- */
5124
- amount = 0;
5125
- productId = "";
5126
- /**
5127
- * SKU or item code
5128
- */
5129
- sku = "";
5130
- /**
5131
- * Start date (for lease/subscription items)
5132
- */
5133
- startDate = null;
5134
- /**
5135
- * End date (for lease/subscription items)
5136
- */
5137
- endDate = null;
5138
- /**
5139
- * Billing period (for subscriptions: 'monthly', 'yearly', etc.)
5140
- */
5141
- billingPeriod = "";
5142
- /**
5143
- * Sort order within the contract
5144
- */
5145
- sortOrder = 0;
5146
- constructor(options = {}) {
5147
- super(options);
5148
- if (options.tenantId !== void 0) this.tenantId = options.tenantId;
5149
- if (options.contractId !== void 0) this.contractId = options.contractId;
5150
- if (options.description !== void 0)
5151
- this.description = options.description;
5152
- if (options.quantity !== void 0) this.quantity = options.quantity;
5153
- if (options.unitPrice !== void 0) this.unitPrice = options.unitPrice;
5154
- if (options.discount !== void 0) this.discount = options.discount;
5155
- if (options.taxRate !== void 0) this.taxRate = options.taxRate;
5156
- if (options.amount !== void 0) this.amount = options.amount;
5157
- if (options.productId !== void 0) this.productId = options.productId;
5158
- if (options.sku !== void 0) this.sku = options.sku;
5159
- if (options.startDate !== void 0) this.startDate = options.startDate;
5160
- if (options.endDate !== void 0) this.endDate = options.endDate;
5161
- if (options.billingPeriod !== void 0)
5162
- this.billingPeriod = options.billingPeriod;
5163
- if (options.sortOrder !== void 0) this.sortOrder = options.sortOrder;
5164
- }
5165
- /**
5166
- * Calculate the line amount
5167
- */
5168
- calculateAmount() {
5169
- const subtotal = this.quantity * this.unitPrice - this.discount;
5170
- const tax = subtotal * this.taxRate;
5171
- return subtotal + tax;
5172
- }
5173
- /**
5174
- * Check if this is a subscription/recurring item
5175
- */
5176
- isRecurring() {
5177
- return !!this.billingPeriod && !!this.startDate;
5178
- }
5179
- /**
5180
- * Check if subscription is active
5181
- */
5182
- isSubscriptionActive() {
5183
- if (!this.isRecurring()) return false;
5184
- if (!this.startDate) return false;
5185
- const now = /* @__PURE__ */ new Date();
5186
- if (now < this.startDate) return false;
5187
- if (this.endDate && now > this.endDate) return false;
5188
- return true;
5189
- }
5190
- };
5191
- __decorateClass$1([
5192
- tenantId({ nullable: true })
5193
- ], ContractLineItem.prototype, "tenantId", 2);
5194
- __decorateClass$1([
5195
- foreignKey("Contract")
5196
- ], ContractLineItem.prototype, "contractId", 2);
5197
- __decorateClass$1([
5198
- crossPackageRef("@happyvertical/smrt-products:Product")
5199
- ], ContractLineItem.prototype, "productId", 2);
5200
- ContractLineItem = __decorateClass$1([
5201
- TenantScoped({ mode: "optional" }),
5202
- smrt({
5203
- tableStrategy: "sti",
5204
- api: { include: ["list", "get", "create", "update", "delete"] },
5205
- mcp: { include: ["list", "get"] },
5206
- cli: true
5207
- })
5208
- ], ContractLineItem);
5209
- var __defProp = Object.defineProperty;
5210
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5211
- var __decorateClass = (decorators, target, key, kind) => {
5212
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5213
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
5214
- if (decorator = decorators[i])
5215
- result = (kind ? decorator(target, key, result) : decorator(result)) || result;
5216
- if (kind && result) __defProp(target, key, result);
5217
- return result;
5218
- };
5219
- let FulfillmentLineItem = class extends SmrtObject {
5220
- tenantId = null;
5221
- fulfillmentId = "";
5222
- contractLineItemId = "";
5223
- /**
5224
- * Quantity fulfilled in this fulfillment
5225
- */
5226
- quantityFulfilled = 1;
5227
- /**
5228
- * Notes about this line item
5229
- */
5230
- notes = "";
5231
- constructor(options = {}) {
5232
- super(options);
5233
- if (options.tenantId !== void 0) this.tenantId = options.tenantId;
5234
- if (options.fulfillmentId !== void 0)
5235
- this.fulfillmentId = options.fulfillmentId;
5236
- if (options.contractLineItemId !== void 0)
5237
- this.contractLineItemId = options.contractLineItemId;
5238
- if (options.quantityFulfilled !== void 0)
5239
- this.quantityFulfilled = options.quantityFulfilled;
5240
- if (options.notes !== void 0) this.notes = options.notes;
5241
- }
5242
- };
5243
- __decorateClass([
5244
- tenantId({ nullable: true })
5245
- ], FulfillmentLineItem.prototype, "tenantId", 2);
5246
- __decorateClass([
5247
- foreignKey("Fulfillment")
5248
- ], FulfillmentLineItem.prototype, "fulfillmentId", 2);
5249
- __decorateClass([
5250
- foreignKey("ContractLineItem")
5251
- ], FulfillmentLineItem.prototype, "contractLineItemId", 2);
5252
- FulfillmentLineItem = __decorateClass([
5253
- TenantScoped({ mode: "optional" }),
5254
- smrt({
5255
- api: { include: ["list", "get", "create", "update", "delete"] },
5256
- mcp: { include: ["list", "get"] },
5257
- cli: true
5258
- })
5259
- ], FulfillmentLineItem);
5260
5646
  export {
5261
5647
  Agreement,
5262
5648
  COMMERCE_MODULE_META,
@@ -5265,6 +5651,7 @@ export {
5265
5651
  Contract,
5266
5652
  ContractCollection,
5267
5653
  ContractLineItem,
5654
+ ContractLineItemCollection,
5268
5655
  ContractStatus,
5269
5656
  ContractType,
5270
5657
  Customer,
@@ -5275,6 +5662,7 @@ export {
5275
5662
  Fulfillment,
5276
5663
  FulfillmentCollection,
5277
5664
  FulfillmentLineItem,
5665
+ FulfillmentLineItemCollection,
5278
5666
  FulfillmentStatus,
5279
5667
  FulfillmentType,
5280
5668
  Invoice,