@happyvertical/smrt-commerce 0.31.0 → 0.32.0

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
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "version": "1.0.0",
3
- "timestamp": 1782183792503,
3
+ "timestamp": 1782198546055,
4
4
  "packageName": "@happyvertical/smrt-commerce",
5
- "packageVersion": "0.31.0",
5
+ "packageVersion": "0.32.0",
6
6
  "objects": {
7
7
  "@happyvertical/smrt-commerce:ContractCollection": {
8
8
  "name": "contractcollection",
@@ -211,6 +211,120 @@
211
211
  "version": "c2abc9f0"
212
212
  }
213
213
  },
214
+ "@happyvertical/smrt-commerce:ContractLineItemCollection": {
215
+ "name": "contractlineitemcollection",
216
+ "className": "ContractLineItemCollection",
217
+ "qualifiedName": "@happyvertical/smrt-commerce:ContractLineItemCollection",
218
+ "collection": "contractlineitems",
219
+ "filePath": "/home/runner/_work/smrt/smrt/packages/commerce/src/collections/ContractLineItemCollection.ts",
220
+ "packageName": "@happyvertical/smrt-commerce",
221
+ "fields": {},
222
+ "methods": {
223
+ "findByContract": {
224
+ "name": "findByContract",
225
+ "async": true,
226
+ "parameters": [
227
+ {
228
+ "name": "contractId",
229
+ "type": "string",
230
+ "optional": false
231
+ }
232
+ ],
233
+ "returnType": "Promise<ContractLineItem[]>",
234
+ "isStatic": false,
235
+ "isPublic": true
236
+ },
237
+ "findByTenant": {
238
+ "name": "findByTenant",
239
+ "async": true,
240
+ "parameters": [
241
+ {
242
+ "name": "tenantId",
243
+ "type": "string",
244
+ "optional": false
245
+ }
246
+ ],
247
+ "returnType": "Promise<ContractLineItem[]>",
248
+ "isStatic": false,
249
+ "isPublic": true
250
+ },
251
+ "findGlobal": {
252
+ "name": "findGlobal",
253
+ "async": true,
254
+ "parameters": [],
255
+ "returnType": "Promise<ContractLineItem[]>",
256
+ "isStatic": false,
257
+ "isPublic": true
258
+ },
259
+ "findWithGlobals": {
260
+ "name": "findWithGlobals",
261
+ "async": true,
262
+ "parameters": [
263
+ {
264
+ "name": "tenantId",
265
+ "type": "string",
266
+ "optional": false
267
+ }
268
+ ],
269
+ "returnType": "Promise<ContractLineItem[]>",
270
+ "isStatic": false,
271
+ "isPublic": true
272
+ }
273
+ },
274
+ "decoratorConfig": {},
275
+ "extends": "SmrtCollection",
276
+ "extendsTypeArg": "ContractLineItem",
277
+ "exportName": "ContractLineItemCollection",
278
+ "collectionExportName": "ContractLineItemCollectionCollection",
279
+ "schema": {
280
+ "tableName": "contract_line_item_collections",
281
+ "ddl": "CREATE TABLE IF NOT EXISTS \"contract_line_item_collections\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"slug\" TEXT NOT NULL,\n \"context\" TEXT NOT NULL DEFAULT '',\n \"created_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"updated_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp\n);",
282
+ "columns": {
283
+ "id": {
284
+ "type": "UUID",
285
+ "primaryKey": true,
286
+ "referenceKind": "id",
287
+ "notNull": true
288
+ },
289
+ "slug": {
290
+ "type": "TEXT",
291
+ "notNull": true
292
+ },
293
+ "context": {
294
+ "type": "TEXT",
295
+ "notNull": true,
296
+ "default": ""
297
+ },
298
+ "created_at": {
299
+ "type": "TIMESTAMP",
300
+ "notNull": true,
301
+ "default": "current_timestamp"
302
+ },
303
+ "updated_at": {
304
+ "type": "TIMESTAMP",
305
+ "notNull": true,
306
+ "default": "current_timestamp"
307
+ }
308
+ },
309
+ "indexes": [
310
+ {
311
+ "name": "contract_line_item_collections_id_idx",
312
+ "columns": [
313
+ "id"
314
+ ]
315
+ },
316
+ {
317
+ "name": "contract_line_item_collections_slug_context_idx",
318
+ "columns": [
319
+ "slug",
320
+ "context"
321
+ ],
322
+ "unique": true
323
+ }
324
+ ],
325
+ "version": "410887d1"
326
+ }
327
+ },
214
328
  "@happyvertical/smrt-commerce:CustomerCollection": {
215
329
  "name": "customercollection",
216
330
  "className": "CustomerCollection",
@@ -557,6 +671,148 @@
557
671
  "version": "0cb8f9e1"
558
672
  }
559
673
  },
674
+ "@happyvertical/smrt-commerce:FulfillmentLineItemCollection": {
675
+ "name": "fulfillmentlineitemcollection",
676
+ "className": "FulfillmentLineItemCollection",
677
+ "qualifiedName": "@happyvertical/smrt-commerce:FulfillmentLineItemCollection",
678
+ "collection": "fulfillmentlineitems",
679
+ "filePath": "/home/runner/_work/smrt/smrt/packages/commerce/src/collections/FulfillmentLineItemCollection.ts",
680
+ "packageName": "@happyvertical/smrt-commerce",
681
+ "fields": {},
682
+ "methods": {
683
+ "findByFulfillment": {
684
+ "name": "findByFulfillment",
685
+ "async": true,
686
+ "parameters": [
687
+ {
688
+ "name": "fulfillmentId",
689
+ "type": "string",
690
+ "optional": false
691
+ }
692
+ ],
693
+ "returnType": "Promise<FulfillmentLineItem[]>",
694
+ "isStatic": false,
695
+ "isPublic": true
696
+ },
697
+ "findByContractLineItem": {
698
+ "name": "findByContractLineItem",
699
+ "async": true,
700
+ "parameters": [
701
+ {
702
+ "name": "contractLineItemId",
703
+ "type": "string",
704
+ "optional": false
705
+ }
706
+ ],
707
+ "returnType": "Promise<FulfillmentLineItem[]>",
708
+ "isStatic": false,
709
+ "isPublic": true
710
+ },
711
+ "getTotalFulfilledForContractLine": {
712
+ "name": "getTotalFulfilledForContractLine",
713
+ "async": true,
714
+ "parameters": [
715
+ {
716
+ "name": "contractLineItemId",
717
+ "type": "string",
718
+ "optional": false
719
+ }
720
+ ],
721
+ "returnType": "Promise<number>",
722
+ "isStatic": false,
723
+ "isPublic": true
724
+ },
725
+ "findByTenant": {
726
+ "name": "findByTenant",
727
+ "async": true,
728
+ "parameters": [
729
+ {
730
+ "name": "tenantId",
731
+ "type": "string",
732
+ "optional": false
733
+ }
734
+ ],
735
+ "returnType": "Promise<FulfillmentLineItem[]>",
736
+ "isStatic": false,
737
+ "isPublic": true
738
+ },
739
+ "findGlobal": {
740
+ "name": "findGlobal",
741
+ "async": true,
742
+ "parameters": [],
743
+ "returnType": "Promise<FulfillmentLineItem[]>",
744
+ "isStatic": false,
745
+ "isPublic": true
746
+ },
747
+ "findWithGlobals": {
748
+ "name": "findWithGlobals",
749
+ "async": true,
750
+ "parameters": [
751
+ {
752
+ "name": "tenantId",
753
+ "type": "string",
754
+ "optional": false
755
+ }
756
+ ],
757
+ "returnType": "Promise<FulfillmentLineItem[]>",
758
+ "isStatic": false,
759
+ "isPublic": true
760
+ }
761
+ },
762
+ "decoratorConfig": {},
763
+ "extends": "SmrtCollection",
764
+ "extendsTypeArg": "FulfillmentLineItem",
765
+ "exportName": "FulfillmentLineItemCollection",
766
+ "collectionExportName": "FulfillmentLineItemCollectionCollection",
767
+ "schema": {
768
+ "tableName": "fulfillment_line_item_collections",
769
+ "ddl": "CREATE TABLE IF NOT EXISTS \"fulfillment_line_item_collections\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"slug\" TEXT NOT NULL,\n \"context\" TEXT NOT NULL DEFAULT '',\n \"created_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"updated_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp\n);",
770
+ "columns": {
771
+ "id": {
772
+ "type": "UUID",
773
+ "primaryKey": true,
774
+ "referenceKind": "id",
775
+ "notNull": true
776
+ },
777
+ "slug": {
778
+ "type": "TEXT",
779
+ "notNull": true
780
+ },
781
+ "context": {
782
+ "type": "TEXT",
783
+ "notNull": true,
784
+ "default": ""
785
+ },
786
+ "created_at": {
787
+ "type": "TIMESTAMP",
788
+ "notNull": true,
789
+ "default": "current_timestamp"
790
+ },
791
+ "updated_at": {
792
+ "type": "TIMESTAMP",
793
+ "notNull": true,
794
+ "default": "current_timestamp"
795
+ }
796
+ },
797
+ "indexes": [
798
+ {
799
+ "name": "fulfillment_line_item_collections_id_idx",
800
+ "columns": [
801
+ "id"
802
+ ]
803
+ },
804
+ {
805
+ "name": "fulfillment_line_item_collections_slug_context_idx",
806
+ "columns": [
807
+ "slug",
808
+ "context"
809
+ ],
810
+ "unique": true
811
+ }
812
+ ],
813
+ "version": "88e7502e"
814
+ }
815
+ },
560
816
  "@happyvertical/smrt-commerce:InvoiceCollection": {
561
817
  "name": "invoicecollection",
562
818
  "className": "InvoiceCollection",
@@ -10894,6 +11150,22 @@
10894
11150
  }
10895
11151
  },
10896
11152
  "methods": {
11153
+ "initialize": {
11154
+ "name": "initialize",
11155
+ "async": true,
11156
+ "parameters": [],
11157
+ "returnType": "Promise",
11158
+ "isStatic": false,
11159
+ "isPublic": true
11160
+ },
11161
+ "save": {
11162
+ "name": "save",
11163
+ "async": true,
11164
+ "parameters": [],
11165
+ "returnType": "Promise",
11166
+ "isStatic": false,
11167
+ "isPublic": true
11168
+ },
10897
11169
  "isDelivered": {
10898
11170
  "name": "isDelivered",
10899
11171
  "async": false,
@@ -11133,7 +11405,16 @@
11133
11405
  "default": ""
11134
11406
  }
11135
11407
  },
11136
- "methods": {},
11408
+ "methods": {
11409
+ "save": {
11410
+ "name": "save",
11411
+ "async": true,
11412
+ "parameters": [],
11413
+ "returnType": "Promise",
11414
+ "isStatic": false,
11415
+ "isPublic": true
11416
+ }
11417
+ },
11137
11418
  "decoratorConfig": {
11138
11419
  "api": {
11139
11420
  "include": [
@@ -70,6 +70,35 @@ export declare class Fulfillment extends SmrtObject {
70
70
  */
71
71
  notes: string;
72
72
  constructor(options?: any);
73
+ /**
74
+ * Capture the status the row was loaded with so the save-time transition
75
+ * guard can reject illegal status flips made via raw field assignment
76
+ * (mass-assignment on the generated update route, a stale caller, etc.).
77
+ * Only persisted rows carry a prior status.
78
+ */
79
+ initialize(): Promise<this>;
80
+ /**
81
+ * Validate the status transition before persisting, then save. Blocks a
82
+ * forged `status` written via raw mass-assignment on the open
83
+ * `api:{create,update}` surface. See S5 audit #1390 follow-up.
84
+ */
85
+ save(): Promise<this>;
86
+ /**
87
+ * Reject an illegal status flip done via raw assignment. No-op transitions
88
+ * and brand-new rows are always allowed.
89
+ */
90
+ private assertStatusTransition;
91
+ /**
92
+ * Resolve the AUTHORITATIVE prior status. The WeakMap is only populated when
93
+ * {@link initialize} loaded the row; it is empty for an instance built via
94
+ * `collection.create({ id: <existing>, _skipLoad: true })` (the upsert path
95
+ * that writes onto an existing row without hydrating it). Trusting an empty
96
+ * WeakMap there would treat the write as a brand-new row and skip the guard.
97
+ * So when this instance carries an `id`, read the persisted row straight from
98
+ * the DB and treat its `status` as the prior. `undefined` = genuinely new.
99
+ * Mirrors the authoritative-prior-load on Contract/Payment/Invoice/Payout.
100
+ */
101
+ private resolvePriorStatus;
73
102
  /**
74
103
  * Check if fulfillment is complete
75
104
  */
@@ -83,15 +112,23 @@ export declare class Fulfillment extends SmrtObject {
83
112
  */
84
113
  isPending(): boolean;
85
114
  /**
86
- * Mark as shipped
115
+ * Assert the current in-memory status may legally transition to `next`,
116
+ * surfacing the illegal-transition error at the mutation call site rather
117
+ * than deferring it to save(). A no-op (already in `next`) is allowed.
118
+ */
119
+ private assertCanTransitionTo;
120
+ /**
121
+ * Mark as shipped. Rejected from a terminal state (DELIVERED / CANCELLED).
87
122
  */
88
123
  markShipped(trackingNumber?: string, carrier?: string): void;
89
124
  /**
90
- * Mark as delivered
125
+ * Mark as delivered. Rejected from CANCELLED (a cancelled shipment can't be
126
+ * delivered).
91
127
  */
92
128
  markDelivered(): void;
93
129
  /**
94
- * Cancel fulfillment
130
+ * Cancel fulfillment. Rejected once DELIVERED (a delivered shipment can't be
131
+ * cancelled — model a return/refund elsewhere instead).
95
132
  */
96
133
  cancel(): void;
97
134
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Fulfillment.d.ts","sourceRoot":"","sources":["../../src/models/Fulfillment.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAc,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AAExE,OAAO,EACL,KAAK,OAAO,EACZ,iBAAiB,EACjB,eAAe,EAChB,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAMa,WAAY,SAAQ,UAAU;IACzC;;;OAGG;IAEH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE/B;;OAEG;IAEH,UAAU,EAAE,MAAM,CAAM;IAExB;;OAEG;IACH,eAAe,EAAE,eAAe,CAA4B;IAE5D;;OAEG;IACH,MAAM,EAAE,iBAAiB,CAA6B;IAEtD;;OAEG;IACH,cAAc,EAAE,MAAM,CAAM;IAE5B;;OAEG;IACH,OAAO,EAAE,MAAM,CAAM;IAErB;;OAEG;IACH,eAAe,EAAE,OAAO,CAAM;IAE9B;;OAEG;IACH,SAAS,EAAE,IAAI,GAAG,IAAI,CAAQ;IAE9B;;OAEG;IACH,WAAW,EAAE,IAAI,GAAG,IAAI,CAAQ;IAEhC;;OAEG;IACH,iBAAiB,EAAE,IAAI,GAAG,IAAI,CAAQ;IAEtC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAM;gBAEP,OAAO,GAAE,GAAQ;IAoB7B;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,WAAW,CAAC,cAAc,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAO5D;;OAEG;IACH,aAAa,IAAI,IAAI;IAKrB;;OAEG;IACH,MAAM,IAAI,IAAI;CAGf;AAED,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"Fulfillment.d.ts","sourceRoot":"","sources":["../../src/models/Fulfillment.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAc,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AAExE,OAAO,EACL,KAAK,OAAO,EACZ,iBAAiB,EACjB,eAAe,EAChB,MAAM,mBAAmB,CAAC;AAiD3B;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAMa,WAAY,SAAQ,UAAU;IACzC;;;OAGG;IAEH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE/B;;OAEG;IAEH,UAAU,EAAE,MAAM,CAAM;IAExB;;OAEG;IACH,eAAe,EAAE,eAAe,CAA4B;IAE5D;;OAEG;IACH,MAAM,EAAE,iBAAiB,CAA6B;IAEtD;;OAEG;IACH,cAAc,EAAE,MAAM,CAAM;IAE5B;;OAEG;IACH,OAAO,EAAE,MAAM,CAAM;IAErB;;OAEG;IACH,eAAe,EAAE,OAAO,CAAM;IAE9B;;OAEG;IACH,SAAS,EAAE,IAAI,GAAG,IAAI,CAAQ;IAE9B;;OAEG;IACH,WAAW,EAAE,IAAI,GAAG,IAAI,CAAQ;IAEhC;;OAEG;IACH,iBAAiB,EAAE,IAAI,GAAG,IAAI,CAAQ;IAEtC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAM;gBAEP,OAAO,GAAE,GAAQ;IAoB7B;;;;;OAKG;IACY,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ1C;;;;OAIG;IACY,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAQpC;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IAa9B;;;;;;;;;OASG;YACW,kBAAkB;IAchC;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAW7B;;OAEG;IACH,WAAW,CAAC,cAAc,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAQ5D;;;OAGG;IACH,aAAa,IAAI,IAAI;IAMrB;;;OAGG;IACH,MAAM,IAAI,IAAI;CAIf;AAED,eAAe,WAAW,CAAC"}
@@ -37,6 +37,30 @@ export declare class FulfillmentLineItem extends SmrtObject {
37
37
  */
38
38
  notes: string;
39
39
  constructor(options?: any);
40
+ /**
41
+ * Save-time over-fulfillment guard (S5 audit #1390 follow-up, round 2):
42
+ * - `quantityFulfilled` must be a finite, positive number,
43
+ * - the referenced `ContractLineItem` must resolve **within the caller's
44
+ * tenant scope** AND belong to the same contract as the parent
45
+ * Fulfillment, and
46
+ * - the sum of all FulfillmentLineItems against that ContractLineItem
47
+ * (this row included) must not exceed the ordered `ContractLineItem.quantity`.
48
+ *
49
+ * Without this, a caller could ship 50 of 10 ordered — the direct parallel
50
+ * to the PaymentAllocation over-allocation hole #1390 closed.
51
+ *
52
+ * Tenant isolation (round 2): the ContractLineItem is loaded through
53
+ * `ContractLineItemCollection`, so it goes through the tenancy
54
+ * auto-filtering interceptors. A cross-tenant `contractLineItemId` therefore
55
+ * fails closed — it resolves to `null` and trips the existing "does not
56
+ * exist" error rather than leaking a foreign-tenant ordered quantity. The
57
+ * raw `this.db.get('contract_line_items', …)` it replaced bypassed those
58
+ * filters. We additionally load the parent Fulfillment (also tenant-scoped)
59
+ * and reject when the line item belongs to a *different* contract than the
60
+ * Fulfillment is fulfilling, so a valid-but-unrelated line id can't be
61
+ * used to fulfill against the wrong order.
62
+ */
63
+ save(): Promise<this>;
40
64
  }
41
65
  export default FulfillmentLineItem;
42
66
  //# sourceMappingURL=FulfillmentLineItem.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FulfillmentLineItem.d.ts","sourceRoot":"","sources":["../../src/models/FulfillmentLineItem.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAc,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AAGxE;;;;;;;;;;;;;;GAcG;AACH,qBAMa,mBAAoB,SAAQ,UAAU;IACjD;;;OAGG;IAEH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE/B;;OAEG;IAEH,aAAa,EAAE,MAAM,CAAM;IAE3B;;OAEG;IAEH,kBAAkB,EAAE,MAAM,CAAM;IAEhC;;OAEG;IACH,iBAAiB,EAAE,MAAM,CAAO;IAEhC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAM;gBAEP,OAAO,GAAE,GAAQ;CAW9B;AAED,eAAe,mBAAmB,CAAC"}
1
+ {"version":3,"file":"FulfillmentLineItem.d.ts","sourceRoot":"","sources":["../../src/models/FulfillmentLineItem.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAc,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AAUxE;;;;;;;;;;;;;;GAcG;AACH,qBAMa,mBAAoB,SAAQ,UAAU;IACjD;;;OAGG;IAEH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE/B;;OAEG;IAEH,aAAa,EAAE,MAAM,CAAM;IAE3B;;OAEG;IAEH,kBAAkB,EAAE,MAAM,CAAM;IAEhC;;OAEG;IACH,iBAAiB,EAAE,MAAM,CAAO;IAEhC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAM;gBAEP,OAAO,GAAE,GAAQ;IAY7B;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACY,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAwFrC;AAED,eAAe,mBAAmB,CAAC"}
@@ -190,6 +190,31 @@ export declare class Invoice extends SmrtObject {
190
190
  * (the dynamic imports stay inside the package).
191
191
  */
192
192
  private recomputeAmountsForSave;
193
+ /**
194
+ * Whether `amountPaid` covers `totalAmount` within the sub-cent rounding
195
+ * tolerance. This is the SINGLE source of truth for the **PAID** decision —
196
+ * both {@link updatePaymentStatus} (which decides PAID) and
197
+ * {@link assertPaymentStatusConsistent} (which validates PAID on save) call
198
+ * it, so the PAID-deciding comparison and the PAID-validating comparison can
199
+ * never drift apart. A strict `amountPaid >= totalAmount` here would diverge
200
+ * from the epsilon-tolerant guard: a float-summed total paid exactly (e.g.
201
+ * `0.1 × 3` line items paid `0.3`) reads as "not covered" by `>=` but
202
+ * "covered" by the guard, so `updatePaymentStatus` would set PARTIAL and the
203
+ * save-time guard would then reject it — leaving a genuinely-paid invoice
204
+ * unsaveable (S5 audit #1390 follow-up).
205
+ *
206
+ * NOTE: the claim is scoped to PAID. The PARTIAL branch is NOT unified the
207
+ * same way — {@link updatePaymentStatus} treats any `amountPaid > 0` as
208
+ * PARTIAL, whereas {@link isPartiallyPaid} (used by the save-time guard)
209
+ * requires `amountPaid > INVOICE_EPSILON`. That pre-existing asymmetry only
210
+ * matters for a sub-cent dust payment and is intentionally left as-is.
211
+ */
212
+ private isFullyPaid;
213
+ /**
214
+ * Whether `amountPaid` is a non-trivial partial payment of `totalAmount`.
215
+ * Derived from {@link isFullyPaid} so the two stay mutually exclusive.
216
+ */
217
+ private isPartiallyPaid;
193
218
  /**
194
219
  * After amounts are recomputed and amountPaid is re-derived from allocations,
195
220
  * assert the persisted `status` is consistent with the derived
@@ -205,8 +230,19 @@ export declare class Invoice extends SmrtObject {
205
230
  */
206
231
  private assertPaymentStatusConsistent;
207
232
  /**
208
- * Enforce `totalAmount === subtotal + taxAmount` (within rounding tolerance).
209
- * Used when the invoice has no line items to recompute from.
233
+ * Enforce `totalAmount === subtotal + taxAmount` (within rounding tolerance),
234
+ * then SNAP `totalAmount` to the exact arithmetic. Used when the invoice has
235
+ * no line items to recompute from.
236
+ *
237
+ * The snap closes the invoice-vs-ledger epsilon gap (S5 audit #1390
238
+ * follow-up): the invoice guard tolerates `INVOICE_EPSILON` (0.01) but the
239
+ * smrt-ledgers balance check uses a tighter `BALANCE_EPSILON` (0.001). A
240
+ * no-line-item invoice with, say, subtotal 100.00 / total 100.005 passes this
241
+ * guard, yet `recognizeRevenue` would build DR 100.005 / CR 100.00 and
242
+ * `journal.post()` would reject it as unbalanced — voiding the journal and
243
+ * permanently blocking revenue recognition. Snapping `totalAmount` to
244
+ * `subtotal + taxAmount` here (after the tolerance check) means the persisted
245
+ * total and every journal built from it are always ledger-consistent.
210
246
  */
211
247
  private assertTotalArithmetic;
212
248
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"Invoice.d.ts","sourceRoot":"","sources":["../../src/models/Invoice.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAGL,UAAU,EAEX,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,aAAa,EAAE,KAAK,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAmFhF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAwCa,OAAQ,SAAQ,UAAU;IACrC;;;OAGG;IAEH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE/B;;OAEG;IAEH,UAAU,EAAE,MAAM,CAAM;IAExB;;OAEG;IAEH,UAAU,EAAE,MAAM,CAAM;IAMxB;;;OAGG;IACH,aAAa,EAAE,MAAM,CAAM;IAE3B;;OAEG;IACH,SAAS,EAAE,MAAM,CAAM;IAMvB;;OAEG;IACH,SAAS,EAAE,IAAI,CAAc;IAE7B;;OAEG;IACH,OAAO,EAAE,IAAI,CAAc;IAE3B;;OAEG;IACH,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAQ;IAM7B;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAK;IAErB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAK;IAEtB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAK;IAExB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAK;IAEvB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAS;IAMzB;;OAEG;IACH,MAAM,EAAE,aAAa,CAAuB;IAM5C;;OAEG;IAEH,WAAW,EAAE,MAAM,CAAM;IAEzB;;OAEG;IAEH,gBAAgB,EAAE,MAAM,CAAM;IAM9B;;OAEG;IACH,UAAU,EAAE,MAAM,CAAM;IAExB;;;OAGG;IACH,kBAAkB,EAAE,MAAM,CAAM;IAEhC;;OAEG;IACH,gBAAgB,EAAE,MAAM,CAAM;IAE9B;;OAEG;IACH,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAQ;IAM7B;;OAEG;IACH,MAAM,EAAE,IAAI,GAAG,IAAI,CAAQ;IAE3B;;OAEG;IACH,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAQ;IAE7B;;OAEG;IACH,aAAa,EAAE,MAAM,CAAK;IAE1B;;OAEG;IACH,cAAc,EAAE,IAAI,GAAG,IAAI,CAAQ;IAMnC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAM;IAEnB;;OAEG;IACH,aAAa,EAAE,MAAM,CAAM;IAE3B;;OAEG;IACH,KAAK,EAAE,MAAM,CAAM;gBAEP,OAAO,GAAE,GAAQ;IAwC7B;;;;;;;OAOG;IACY,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAY1C;;;;;;;;;;;;;;;;;OAiBG;IACY,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBpC;;;;;;;;OAQG;YACW,kBAAkB;IAgBhC;;;;;OAKG;YACW,uBAAuB;IAyErC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,6BAA6B;IAmCrC;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAU7B;;;;OAIG;IACH,OAAO,CAAC,wBAAwB;IAqBhC;;;;OAIG;IACH,OAAO,CAAC,sBAAsB;IAiB9B;;OAEG;IACH,OAAO,IAAI,OAAO;IAIlB;;OAEG;IACH,MAAM,IAAI,OAAO;IAIjB;;OAEG;IACH,MAAM,IAAI,OAAO;IAIjB;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,SAAS,IAAI,OAAO;IAMpB;;OAEG;IACH,YAAY,IAAI,MAAM;IAQtB;;OAEG;IACH,QAAQ,IAAI,IAAI;IAUhB;;;;;;;OAOG;IACH,UAAU,IAAI,IAAI;IAQlB;;;;;;;OAOG;IACH,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IA4B7C;;OAEG;IACH,MAAM,IAAI,IAAI;IASd;;OAEG;IACH,QAAQ,IAAI,IAAI;IAQhB;;;;;;;;;;;;;;;;;;;;;OAqBG;IACG,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,GAAG,CAAC;IA8FtE;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC;IAiBzC;;;;;;;;;;;;;;;;;OAiBG;IACG,iBAAiB,IAAI,OAAO,CAAC,GAAG,CAAC;CA4BxC;AAED,eAAe,OAAO,CAAC"}
1
+ {"version":3,"file":"Invoice.d.ts","sourceRoot":"","sources":["../../src/models/Invoice.ts"],"names":[],"mappings":"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"}
@@ -72,12 +72,14 @@ export declare class PaymentAllocation extends SmrtObject {
72
72
  notes: string;
73
73
  constructor(options?: any);
74
74
  /**
75
- * Save-time integrity guard (S5 audit #1390):
76
- * - allocation `amount` must be a finite, positive number, and
75
+ * Save-time integrity guard (S5 audit #1390 + follow-up):
76
+ * - allocation `amount` must be a finite, positive number,
77
77
  * - the sum of all allocations against the referenced Payment (this row
78
78
  * included) must not exceed the Payment's amount — over-applying a
79
79
  * payment across invoices would falsify both payment and invoice
80
- * balances.
80
+ * balances, and
81
+ * - the sum of all allocations against the referenced Invoice (this row
82
+ * included) must not exceed the Invoice's `totalAmount`.
81
83
  *
82
84
  * The Payment-amount cap is enforced against the persisted Payment row. An
83
85
  * allocation always carries a `@foreignKey('Payment')` paymentId, so a
@@ -86,6 +88,16 @@ export declare class PaymentAllocation extends SmrtObject {
86
88
  * entirely, letting a caller over-apply (or fabricate) funds simply by
87
89
  * pointing at a non-existent payment. An empty `paymentId` is still rejected
88
90
  * by the underlying FK requirement; the positivity check always applies.
91
+ *
92
+ * The Invoice-total cap (follow-up) closes a complementary hole: the
93
+ * per-Payment cap lets allocations from *different* payments each pass their
94
+ * own check while jointly summing above the invoice total. The next
95
+ * `Invoice.save()` would then recompute `amountPaid` from these allocations,
96
+ * trip `assertNonNegativeAmounts` (amountPaid > totalAmount), and leave the
97
+ * invoice permanently unsaveable while the over-allocations persist. Capping
98
+ * here keeps allocations from ever exceeding what the invoice owes. The cap
99
+ * is skipped only when the invoice row can't be resolved (e.g. ledger-less /
100
+ * not-yet-persisted) so it never blocks an otherwise-valid allocation.
89
101
  */
90
102
  save(): Promise<this>;
91
103
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PaymentAllocation.d.ts","sourceRoot":"","sources":["../../src/models/PaymentAllocation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAc,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AASxE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,qBAoBa,iBAAkB,SAAQ,UAAU;IAC/C;;;OAGG;IAEH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE/B;;OAEG;IAEH,SAAS,EAAE,MAAM,CAAM;IAEvB;;OAEG;IAEH,SAAS,EAAE,MAAM,CAAM;IAEvB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAK;IAEnB;;OAEG;IACH,WAAW,EAAE,IAAI,CAAc;IAE/B;;OAEG;IACH,WAAW,EAAE,MAAM,CAAM;IAEzB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAM;gBAEP,OAAO,GAAE,GAAQ;IAa7B;;;;;;;;;;;;;;;OAeG;IACY,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CA0CrC;AAED,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"PaymentAllocation.d.ts","sourceRoot":"","sources":["../../src/models/PaymentAllocation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAc,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AASxE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,qBAoBa,iBAAkB,SAAQ,UAAU;IAC/C;;;OAGG;IAEH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE/B;;OAEG;IAEH,SAAS,EAAE,MAAM,CAAM;IAEvB;;OAEG;IAEH,SAAS,EAAE,MAAM,CAAM;IAEvB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAK;IAEnB;;OAEG;IACH,WAAW,EAAE,IAAI,CAAc;IAE/B;;OAEG;IACH,WAAW,EAAE,MAAM,CAAM;IAEzB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAM;gBAEP,OAAO,GAAE,GAAQ;IAa7B;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACY,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAmFrC;AAED,eAAe,iBAAiB,CAAC"}
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-06-23T03:03:13.154Z",
3
+ "generatedAt": "2026-06-23T07:09:06.841Z",
4
4
  "packageName": "@happyvertical/smrt-commerce",
5
- "packageVersion": "0.31.0",
5
+ "packageVersion": "0.32.0",
6
6
  "sourceManifestPath": "dist/manifest.json",
7
7
  "agentDocPath": "AGENTS.md",
8
8
  "sourceHashes": {
9
- "manifest": "3f11b5f204fd20ef05d782d5e2d9a19426e02c66e24d9f3dd8318c85992414cf",
10
- "packageJson": "30c69b8574c3e033401f9f8362f3374840a59e60d719c672de9bb964741ab660",
9
+ "manifest": "84f8fdc2826f300da47a9ab3762a862193a80a2dcb3e347c4ab578e5b40a81ef",
10
+ "packageJson": "941964662329b4dd0a86529d6edfa8f2f11e0adf215f3ba4d55e74009aec8d99",
11
11
  "agents": "f544ac9cece5c94aaead6013316709fcd044944b993110fc7218c0db7c998a63"
12
12
  },
13
13
  "exports": [
@@ -76,6 +76,28 @@
76
76
  "tags": [],
77
77
  "risks": []
78
78
  },
79
+ {
80
+ "name": "ContractLineItemCollection",
81
+ "qualifiedName": "@happyvertical/smrt-commerce:ContractLineItemCollection",
82
+ "collection": "contractlineitems",
83
+ "tableName": "contract_line_item_collections",
84
+ "packageName": "@happyvertical/smrt-commerce",
85
+ "extends": "SmrtCollection",
86
+ "fields": [],
87
+ "relationships": [],
88
+ "methods": [
89
+ "findByContract",
90
+ "findByTenant",
91
+ "findGlobal",
92
+ "findWithGlobals"
93
+ ],
94
+ "surfaces": [],
95
+ "relationshipFeatures": [
96
+ "uuidColumns"
97
+ ],
98
+ "tags": [],
99
+ "risks": []
100
+ },
79
101
  {
80
102
  "name": "CustomerCollection",
81
103
  "qualifiedName": "@happyvertical/smrt-commerce:CustomerCollection",
@@ -129,6 +151,30 @@
129
151
  "tags": [],
130
152
  "risks": []
131
153
  },
154
+ {
155
+ "name": "FulfillmentLineItemCollection",
156
+ "qualifiedName": "@happyvertical/smrt-commerce:FulfillmentLineItemCollection",
157
+ "collection": "fulfillmentlineitems",
158
+ "tableName": "fulfillment_line_item_collections",
159
+ "packageName": "@happyvertical/smrt-commerce",
160
+ "extends": "SmrtCollection",
161
+ "fields": [],
162
+ "relationships": [],
163
+ "methods": [
164
+ "findByContractLineItem",
165
+ "findByFulfillment",
166
+ "findByTenant",
167
+ "findGlobal",
168
+ "findWithGlobals",
169
+ "getTotalFulfilledForContractLine"
170
+ ],
171
+ "surfaces": [],
172
+ "relationshipFeatures": [
173
+ "uuidColumns"
174
+ ],
175
+ "tags": [],
176
+ "risks": []
177
+ },
132
178
  {
133
179
  "name": "InvoiceCollection",
134
180
  "qualifiedName": "@happyvertical/smrt-commerce:InvoiceCollection",
@@ -2919,11 +2965,13 @@
2919
2965
  ],
2920
2966
  "methods": [
2921
2967
  "cancel",
2968
+ "initialize",
2922
2969
  "isDelivered",
2923
2970
  "isInTransit",
2924
2971
  "isPending",
2925
2972
  "markDelivered",
2926
- "markShipped"
2973
+ "markShipped",
2974
+ "save"
2927
2975
  ],
2928
2976
  "surfaces": [
2929
2977
  {
@@ -3065,7 +3113,9 @@
3065
3113
  "columnType": "UUID"
3066
3114
  }
3067
3115
  ],
3068
- "methods": [],
3116
+ "methods": [
3117
+ "save"
3118
+ ],
3069
3119
  "surfaces": [
3070
3120
  {
3071
3121
  "kind": "api",
@@ -5488,7 +5538,7 @@
5488
5538
  "junctionCollections": 0,
5489
5539
  "hierarchicalObjects": 0,
5490
5540
  "polymorphicAssociations": 0,
5491
- "uuidColumns": 91
5541
+ "uuidColumns": 93
5492
5542
  },
5493
5543
  "agentDoc": "# @happyvertical/smrt-commerce\n\nE-commerce with Contract STI hierarchy, invoice lifecycle, payment tracking, payout remittance, and optional ledger integration.\n\n## Models\n\n- **Customer** / **Vendor**: linked to Profile via string ID (not FK). Customer has creditLimit, paymentTerms, customerType (DTC / WHOLESALE / RETAIL). Vendor has leadTimeDays, minimumOrder, and `payoutAddresses: Record<string, string>` — a flat map from payout-rail-qualified currency code (`USDC-base`, `BTC`, `USD-stripe`, ...) to destination string (EVM address, BTC address, Stripe Connect account id, IBAN). Use `getPayoutAddress(currency)` for a `Map.get`-style lookup; missing entries return `undefined` (caller decides skip-vs-error).\n- **Contract** (STI base → Estimate, Order, Lease, Agreement, PurchaseOrder, WholesaleOrder, ProductionOrder, Cart, LicenseSale): 9 contract types sharing one table. Carries `channelId` (open-ended string — `dtc-web`, `wholesale-b2b`, `pos-store-N`, etc.) so the same model serves DTC checkout, B2B portals, and POS.\n- **WholesaleOrder**: B2B order. Conventional pairing — customer has `customerType: 'wholesale'`, NET-30/60 terms, delivered via wholesale-portal channel.\n- **ProductionOrder**: manufacturing equivalent of a PurchaseOrder — commission your factory to make finished goods. Consumes raw materials per BOM (`@happyvertical/smrt-manufacturing`) and produces SKU stock (`@happyvertical/smrt-inventory`).\n- **Cart**: transient order-in-progress. Same shape as Order; the application promotes the row from `_meta_type: Cart` to `_meta_type: Order` at checkout instead of copying data between tables.\n- **LicenseSale**: industry-neutral licensing primitive. Carries an *immutable* rights snapshot (`rightsMedium`, `rightsDistributionScope`, `rightsExclusivity`, `rightsDuration`, `rightsTerritory`, `rightsSublicensing`, `rightsDerivatives` — typed `Meta<T>` fields), licensee identity (`licenseeEmail`, optional `licenseeLegalEntity` / `licenseeJurisdiction`), and a signed-PDF reference (`pdfUrl`, `pdfHash`, optional `onChainHashRegistryRef`). Once saved at `ContractStatus.ACCEPTED`, the rights snapshot is frozen — mutating any of the seven rights fields and re-saving throws. The only legal transition out of ACCEPTED is `revoke()` (moves to CANCELLED without touching rights). Useful for stock media, music licensing, code-asset marketplaces, license keys, anywhere rights are sold for a fee.\n- **ContractLineItem**: items on contracts.\n- **Invoice**: status machine `DRAFT → SENT → VIEWED → PARTIAL → PAID` (also OVERDUE, CANCELLED, WRITTEN_OFF). `recognizeRevenue()` creates balanced AR journal entry (DR: Accounts Receivable, CR: Revenue, CR: Tax Payable).\n- **InvoiceLineItem**: line items on invoices.\n- **Payment** / **PaymentAllocation**: tracks payments against invoices. Status controlled by `Invoice.updatePaymentStatus()`, not Payment model. Carries optional backend-adapter fields for PaymentBackend-routed flows: `backendId` (rail adapter id — `base-usdc`, `btc`, `stripe`, distinct from `externalProvider` which names an accounting sync destination), `backendTxRef` (chain tx hash or gateway settlement id), `nativeAmount` / `nativeCurrency` (what actually arrived), `usdAtQuote` / `usdAtConfirmation` (drift accounting for volatile-currency rails). `Payment.usdDrift()` returns the confirmation - quote delta, or `0` when either side is unset.\n- **PaymentIntent**: short-lived pre-payment commitment with multi-option semantics. Locks a USD price for a fixed window (default 15 minutes) and lists one or more `PaymentOption`s describing different rails (`backendId`, `currency`, `payTo`, `nativeAmount`, optional `chain` / `memo` / `x402Capable` / `expiresAt`). First option to receive payment wins; the others are implicitly retired. State machine `awaiting_payment → paid → (issued | retired)` plus `expired` and `cancelled`. Mutate via dedicated `markPaid` / `markIssued` / `expire` / `cancel` / `retire` helpers — direct status assignment bypasses the invariant checks. Idempotency via natural key `(tenant_id, offering_ref, licensee_email, idempotency_key)` and `PaymentIntentCollection.getOrCreateByIdempotencyKey()`.\n- **Payout**: operator-to-supplier remittance. Distinct from Payment because direction, status machine, and chain semantics differ. Status machine `pending → sent → confirmed → failed` (failed is terminal but resettable via `resetFromFailed()` after fixing the underlying problem). References source `paymentId` (plain string) and destination `vendorId` (foreign key). Amount invariant `supplierNet === grossAmount - operatorFee` (1¢ rounding tolerance) enforced on save. `PayoutCollection.createFromPayment()` is the typical entry point; it pulls native amount / currency from the source Payment.\n- **Fulfillment** / **FulfillmentLineItem**: shipment/delivery tracking.\n\n## Ledger Integration\n\n`@happyvertical/smrt-ledgers` is a regular dependency, loaded lazily via dynamic `import()` so the coupling stays runtime-only (no hard static import; the package graph stays a DAG — see #1582). Invoice stores `arJournalId` and `revenueJournalId` as string references. `recognizeRevenue()` creates a balanced AR journal entry; `getArJournal()` returns null when no journal has been recognized yet.\n\n## Cross-Package References\n\n- `customerId` → `@foreignKey('Customer')` (hard reference within package)\n- `profileId` → plain string to smrt-profiles\n- `arJournalId`, `revenueJournalId` → plain string to smrt-ledgers\n- `skuId` (on `PaymentIntent`, `LicenseSale`) → plain string to smrt-products\n- `paymentId` (on `PaymentIntent`, `Payout`, `LicenseSale`) → plain string to `Payment` (same package, but kept as plain string for cross-model consistency)\n- `vendorId` on `Payout` → `@foreignKey(Vendor)` (hard reference within package)\n\n## Gotchas\n\n- **Optional tenancy**: all models `@TenantScoped({ mode: 'optional' })` + nullable tenantId\n- **Currency in decimal fields**: price, taxAmount, totalAmount (not integer cents like affiliates)\n- **Invoice controls payment status**: not the Payment model — use `Invoice.updatePaymentStatus()`\n- **Tax rate is external**: no tax rate field on Invoice — rate must be calculated externally\n- **Profile linking**: separate `ProfileCollection.create()` needed to fetch actual Profile object\n- **PaymentIntent natural key**: `conflictColumns: ['tenant_id', 'offering_ref', 'licensee_email', 'idempotency_key']` — a retried `create` with the same tuple upserts the existing row. Use `getOrCreateByIdempotencyKey()` to branch on `{ intent, created }`. Empty natural-key inputs (e.g. blank `idempotencyKey`) disable dedup by design.\n- **Payout state-machine guards**: `markSent` requires a non-empty `backendTxRef`; `markConfirmed` only valid from `SENT`; `markFailed` only valid from `PENDING` / `SENT` (a confirmed payout can't fail — that path is a refund). `resetFromFailed()` is the dedicated escape hatch from `FAILED` back to `PENDING` after an operator fixes the underlying problem; it clears `backendTxRef` so the next attempt picks up a fresh one.\n- **LicenseSale immutability**: rights snapshot freezes on save-with-status-ACCEPTED. The captured snapshot lives in a module-scoped `WeakMap<LicenseSale, string>` so it doesn't interact with the schema or round-trip through `_meta_data`. Drafts (status != ACCEPTED) remain mutable. To \"change\" an issued license: `revoke()` it, then issue a new `LicenseSale` row.\n- **Vendor.payoutAddresses normalization**: the constructor accepts either a `Record<string, string>` or a pre-serialized JSON string. `initialize()` re-normalizes after the framework's option-override pass; `save()` re-normalizes defensively against direct field assignment. Non-string values inside the input map are silently dropped to preserve the typed invariant downstream.\n"
5494
5544
  }