@endora-commerce/mod-carts 0.100.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 (155) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -0
  3. package/dist/admin/components/CartApprovalPolicyPanel.d.ts +51 -0
  4. package/dist/admin/components/CartApprovalPolicyPanel.d.ts.map +1 -0
  5. package/dist/admin/components/CartApprovalPolicyPanel.js +59 -0
  6. package/dist/admin/components/CartApprovalPolicyPanel.js.map +1 -0
  7. package/dist/admin/index.d.ts +38 -0
  8. package/dist/admin/index.d.ts.map +1 -0
  9. package/dist/admin/index.js +73 -0
  10. package/dist/admin/index.js.map +1 -0
  11. package/dist/admin/pages/CartDetail.d.ts +3 -0
  12. package/dist/admin/pages/CartDetail.d.ts.map +1 -0
  13. package/dist/admin/pages/CartDetail.js +74 -0
  14. package/dist/admin/pages/CartDetail.js.map +1 -0
  15. package/dist/admin/pages/CartsList.d.ts +3 -0
  16. package/dist/admin/pages/CartsList.d.ts.map +1 -0
  17. package/dist/admin/pages/CartsList.js +90 -0
  18. package/dist/admin/pages/CartsList.js.map +1 -0
  19. package/dist/admin/zones/CartApprovalPolicy.d.ts +29 -0
  20. package/dist/admin/zones/CartApprovalPolicy.d.ts.map +1 -0
  21. package/dist/admin/zones/CartApprovalPolicy.js +11 -0
  22. package/dist/admin/zones/CartApprovalPolicy.js.map +1 -0
  23. package/dist/backend/cli/abandonment-sweep.d.ts +64 -0
  24. package/dist/backend/cli/abandonment-sweep.d.ts.map +1 -0
  25. package/dist/backend/cli/abandonment-sweep.js +17 -0
  26. package/dist/backend/cli/abandonment-sweep.js.map +1 -0
  27. package/dist/backend/entities/cart-audit-entry.entity.d.ts +29 -0
  28. package/dist/backend/entities/cart-audit-entry.entity.d.ts.map +1 -0
  29. package/dist/backend/entities/cart-audit-entry.entity.js +109 -0
  30. package/dist/backend/entities/cart-audit-entry.entity.js.map +1 -0
  31. package/dist/backend/entities/cart-item.entity.d.ts +42 -0
  32. package/dist/backend/entities/cart-item.entity.d.ts.map +1 -0
  33. package/dist/backend/entities/cart-item.entity.js +119 -0
  34. package/dist/backend/entities/cart-item.entity.js.map +1 -0
  35. package/dist/backend/entities/cart.entity.d.ts +96 -0
  36. package/dist/backend/entities/cart.entity.d.ts.map +1 -0
  37. package/dist/backend/entities/cart.entity.js +207 -0
  38. package/dist/backend/entities/cart.entity.js.map +1 -0
  39. package/dist/backend/index.d.ts +122 -0
  40. package/dist/backend/index.d.ts.map +1 -0
  41. package/dist/backend/index.js +184 -0
  42. package/dist/backend/index.js.map +1 -0
  43. package/dist/backend/routes.admin.d.ts +31 -0
  44. package/dist/backend/routes.admin.d.ts.map +1 -0
  45. package/dist/backend/routes.admin.js +95 -0
  46. package/dist/backend/routes.admin.js.map +1 -0
  47. package/dist/backend/routes.d.ts +101 -0
  48. package/dist/backend/routes.d.ts.map +1 -0
  49. package/dist/backend/routes.js +620 -0
  50. package/dist/backend/routes.js.map +1 -0
  51. package/dist/backend/routes.organization.d.ts +29 -0
  52. package/dist/backend/routes.organization.d.ts.map +1 -0
  53. package/dist/backend/routes.organization.js +150 -0
  54. package/dist/backend/routes.organization.js.map +1 -0
  55. package/dist/backend/services/cart-abandonment-settings.d.ts +34 -0
  56. package/dist/backend/services/cart-abandonment-settings.d.ts.map +1 -0
  57. package/dist/backend/services/cart-abandonment-settings.js +55 -0
  58. package/dist/backend/services/cart-abandonment-settings.js.map +1 -0
  59. package/dist/backend/services/cart-abandonment-worker.d.ts +110 -0
  60. package/dist/backend/services/cart-abandonment-worker.d.ts.map +1 -0
  61. package/dist/backend/services/cart-abandonment-worker.js +185 -0
  62. package/dist/backend/services/cart-abandonment-worker.js.map +1 -0
  63. package/dist/backend/services/cart-admin-service.d.ts +58 -0
  64. package/dist/backend/services/cart-admin-service.d.ts.map +1 -0
  65. package/dist/backend/services/cart-admin-service.js +397 -0
  66. package/dist/backend/services/cart-admin-service.js.map +1 -0
  67. package/dist/backend/services/cart-approval-service.d.ts +147 -0
  68. package/dist/backend/services/cart-approval-service.d.ts.map +1 -0
  69. package/dist/backend/services/cart-approval-service.js +242 -0
  70. package/dist/backend/services/cart-approval-service.js.map +1 -0
  71. package/dist/backend/services/cart-audit-service.d.ts +75 -0
  72. package/dist/backend/services/cart-audit-service.d.ts.map +1 -0
  73. package/dist/backend/services/cart-audit-service.js +70 -0
  74. package/dist/backend/services/cart-audit-service.js.map +1 -0
  75. package/dist/backend/services/cart-conversion-service.d.ts +104 -0
  76. package/dist/backend/services/cart-conversion-service.d.ts.map +1 -0
  77. package/dist/backend/services/cart-conversion-service.js +175 -0
  78. package/dist/backend/services/cart-conversion-service.js.map +1 -0
  79. package/dist/backend/services/cart-coupon-service.d.ts +129 -0
  80. package/dist/backend/services/cart-coupon-service.d.ts.map +1 -0
  81. package/dist/backend/services/cart-coupon-service.js +244 -0
  82. package/dist/backend/services/cart-coupon-service.js.map +1 -0
  83. package/dist/backend/services/cart-organization-visibility-service.d.ts +43 -0
  84. package/dist/backend/services/cart-organization-visibility-service.d.ts.map +1 -0
  85. package/dist/backend/services/cart-organization-visibility-service.js +98 -0
  86. package/dist/backend/services/cart-organization-visibility-service.js.map +1 -0
  87. package/dist/backend/services/cart-placement-apply-port.d.ts +38 -0
  88. package/dist/backend/services/cart-placement-apply-port.d.ts.map +1 -0
  89. package/dist/backend/services/cart-placement-apply-port.js +71 -0
  90. package/dist/backend/services/cart-placement-apply-port.js.map +1 -0
  91. package/dist/backend/services/cart-pricing-recompute.d.ts +62 -0
  92. package/dist/backend/services/cart-pricing-recompute.d.ts.map +1 -0
  93. package/dist/backend/services/cart-pricing-recompute.js +131 -0
  94. package/dist/backend/services/cart-pricing-recompute.js.map +1 -0
  95. package/dist/backend/services/cart-query-service.d.ts +29 -0
  96. package/dist/backend/services/cart-query-service.d.ts.map +1 -0
  97. package/dist/backend/services/cart-query-service.js +40 -0
  98. package/dist/backend/services/cart-query-service.js.map +1 -0
  99. package/dist/backend/services/cart-read-port.d.ts +40 -0
  100. package/dist/backend/services/cart-read-port.d.ts.map +1 -0
  101. package/dist/backend/services/cart-read-port.js +144 -0
  102. package/dist/backend/services/cart-read-port.js.map +1 -0
  103. package/dist/backend/services/cart-recompute-cache.d.ts +49 -0
  104. package/dist/backend/services/cart-recompute-cache.d.ts.map +1 -0
  105. package/dist/backend/services/cart-recompute-cache.js +56 -0
  106. package/dist/backend/services/cart-recompute-cache.js.map +1 -0
  107. package/dist/backend/services/cart-service.d.ts +165 -0
  108. package/dist/backend/services/cart-service.d.ts.map +1 -0
  109. package/dist/backend/services/cart-service.js +556 -0
  110. package/dist/backend/services/cart-service.js.map +1 -0
  111. package/dist/backend/services/cart-state-machine.d.ts +51 -0
  112. package/dist/backend/services/cart-state-machine.d.ts.map +1 -0
  113. package/dist/backend/services/cart-state-machine.js +74 -0
  114. package/dist/backend/services/cart-state-machine.js.map +1 -0
  115. package/dist/backend/services/cart-upsell-service.d.ts +57 -0
  116. package/dist/backend/services/cart-upsell-service.d.ts.map +1 -0
  117. package/dist/backend/services/cart-upsell-service.js +100 -0
  118. package/dist/backend/services/cart-upsell-service.js.map +1 -0
  119. package/dist/backend/services/customer-group-resolver.d.ts +27 -0
  120. package/dist/backend/services/customer-group-resolver.d.ts.map +1 -0
  121. package/dist/backend/services/customer-group-resolver.js +32 -0
  122. package/dist/backend/services/customer-group-resolver.js.map +1 -0
  123. package/dist/manifest.d.ts +221 -0
  124. package/dist/manifest.d.ts.map +1 -0
  125. package/dist/manifest.js +224 -0
  126. package/dist/manifest.js.map +1 -0
  127. package/dist/migrations/20260611T140352_carts_consolidation.d.ts +43 -0
  128. package/dist/migrations/20260611T140352_carts_consolidation.d.ts.map +1 -0
  129. package/dist/migrations/20260611T140352_carts_consolidation.js +237 -0
  130. package/dist/migrations/20260611T140352_carts_consolidation.js.map +1 -0
  131. package/dist/migrations/20260611T140413_carts_cart_item_packaging.d.ts +12 -0
  132. package/dist/migrations/20260611T140413_carts_cart_item_packaging.d.ts.map +1 -0
  133. package/dist/migrations/20260611T140413_carts_cart_item_packaging.js +20 -0
  134. package/dist/migrations/20260611T140413_carts_cart_item_packaging.js.map +1 -0
  135. package/dist/migrations/20260818T081253_carts_cart_completed_order_fk.d.ts +50 -0
  136. package/dist/migrations/20260818T081253_carts_cart_completed_order_fk.d.ts.map +1 -0
  137. package/dist/migrations/20260818T081253_carts_cart_completed_order_fk.js +62 -0
  138. package/dist/migrations/20260818T081253_carts_cart_completed_order_fk.js.map +1 -0
  139. package/dist/migrations/20260830T112911_carts_organization_attribution_check.d.ts +67 -0
  140. package/dist/migrations/20260830T112911_carts_organization_attribution_check.d.ts.map +1 -0
  141. package/dist/migrations/20260830T112911_carts_organization_attribution_check.js +121 -0
  142. package/dist/migrations/20260830T112911_carts_organization_attribution_check.js.map +1 -0
  143. package/dist/migrations/index.d.ts +30 -0
  144. package/dist/migrations/index.d.ts.map +1 -0
  145. package/dist/migrations/index.js +35 -0
  146. package/dist/migrations/index.js.map +1 -0
  147. package/dist/ports/index.d.ts +106 -0
  148. package/dist/ports/index.d.ts.map +1 -0
  149. package/dist/ports/index.js +2 -0
  150. package/dist/ports/index.js.map +1 -0
  151. package/docs/carts.md +232 -0
  152. package/i18n/en.json +96 -0
  153. package/i18n/pl.json +96 -0
  154. package/package.json +107 -0
  155. package/tailwind.css +14 -0
@@ -0,0 +1,62 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+ /**
3
+ * `carts.completed_order_id` — the pointer this table has never had for one of
4
+ * its two terminal transitions (D-94.1, site 2 / D-94.4).
5
+ *
6
+ * `placeOrder` ends by deleting the cart's items and setting
7
+ * `status = 'completed'`, on the same `Cart` object the totals were read from
8
+ * and inside the placement transaction, because a placement that then fails
9
+ * must leave the basket exactly as the buyer left it
10
+ * (`test/integration/orders/place-order-failure-preserves-cart.test.ts`).
11
+ * D-78 point 2 rules such a seam permanent and *declared*, and there was
12
+ * nothing to declare it with: no foreign key existed in either direction.
13
+ *
14
+ * **The pointer goes on the cart, not on the order.** An `orders.cart_id`
15
+ * would satisfy the letter of D-78 point 2 and force nothing — `orders`
16
+ * already declares `carts`, and an implementer could still complete the cart
17
+ * in a separate transaction and satisfy the constraint, because the cart is
18
+ * already committed when the order row is written. The cart-side pointer
19
+ * cannot be written before the order exists, which is precisely the property
20
+ * `payments_order_fk`, `invoices_order_fk` and `shipments_order_fk` rely on.
21
+ * This table already carries exactly this shape for its *other* terminal
22
+ * transition — `carts_converted_to_qr_fk`, added by
23
+ * `20260611T140352_carts_consolidation.ts:100-109`.
24
+ *
25
+ * `null`, and `on delete set null`, for the reason that migration wrote down
26
+ * for its twin: deleting the target does not invalidate the fact that the cart
27
+ * was once completed. The completion pointer is provenance, not a dependency.
28
+ * The column is nullable because it cannot be backfilled — no surviving record
29
+ * says which order emptied which historical cart — and because three of the
30
+ * four paths to `completed` are not placements (`CartService.clearForCustomer`,
31
+ * the anonymous-merge source, and the quote-request conversion), so
32
+ * `status = 'completed'` will never imply it and no check constraint can be
33
+ * added.
34
+ *
35
+ * **No orphan report, and the asymmetry is the point.** The other three
36
+ * constraints of this family need one because their columns have been
37
+ * accepting unverified values for months. This column is new: every existing
38
+ * row starts `null`, so there is nothing to orphan.
39
+ *
40
+ * `carts` declares `orders` in its manifest `dependencies` for this edge
41
+ * (AGENTS.md § Migrations item 4). The reverse port edge — `orders` resolving
42
+ * `cartWritePort` — moves to `acknowledgedDependencies`, which drops the
43
+ * install ordering the constraint says is backwards and keeps the bind and
44
+ * every refusal exactly as they were (D-94.3).
45
+ */
46
+ export class Migration20260818T081253CartsCartCompletedOrderFk extends Migration {
47
+ async up() {
48
+ this.addSql(`alter table "carts" add column "completed_order_id" uuid null;`);
49
+ this.addSql(`
50
+ alter table "carts"
51
+ add constraint "carts_completed_order_fk" foreign key ("completed_order_id")
52
+ references "orders" ("id") on delete set null;
53
+ `);
54
+ this.addSql(`create index "carts_completed_order_idx" on "carts" ("completed_order_id") where "completed_order_id" is not null;`);
55
+ }
56
+ async down() {
57
+ this.addSql(`drop index if exists "carts_completed_order_idx";`);
58
+ this.addSql(`alter table "carts" drop constraint if exists "carts_completed_order_fk";`);
59
+ this.addSql(`alter table "carts" drop column if exists "completed_order_id";`);
60
+ }
61
+ }
62
+ //# sourceMappingURL=20260818T081253_carts_cart_completed_order_fk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20260818T081253_carts_cart_completed_order_fk.js","sourceRoot":"","sources":["../../src/migrations/20260818T081253_carts_cart_completed_order_fk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,MAAM,OAAO,iDAAkD,SAAQ,SAAS;IACrE,KAAK,CAAC,EAAE;QACf,IAAI,CAAC,MAAM,CAAC,gEAAgE,CAAC,CAAC;QAC9E,IAAI,CAAC,MAAM,CAAC;;;;KAIX,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CACT,oHAAoH,CACrH,CAAC;IACJ,CAAC;IAEQ,KAAK,CAAC,IAAI;QACjB,IAAI,CAAC,MAAM,CAAC,mDAAmD,CAAC,CAAC;QACjE,IAAI,CAAC,MAAM,CAAC,2EAA2E,CAAC,CAAC;QACzF,IAAI,CAAC,MAAM,CAAC,iEAAiE,CAAC,CAAC;IACjF,CAAC;CACF"}
@@ -0,0 +1,67 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+ /**
3
+ * D-187 — a `carts` row that names a customer account carries an organisation.
4
+ *
5
+ * `Cart` is the one `@CustomerScoped` class that already has the column
6
+ * (feature 087, Group B), and it has had it since before the granting arm of
7
+ * `customerFilterCond` went live. Nothing structural has held the pair
8
+ * together: `customerOrganizationColumn` reads the ORM's metadata per query, so
9
+ * the moment the property existed a sales representative's list was filtered on
10
+ * `organization_id` — and MikroORM applies no filter to `INSERT`
11
+ * (`r1-spike.md` §4, measured), so a row written with an account and no
12
+ * organisation is simply invisible to the representative who serves that
13
+ * organisation, on a screen that has stopped saying anything is missing.
14
+ *
15
+ * **The constraint is the only refusal an `INSERT` has**, which is the same
16
+ * sentence `20260825T141659_customer_accounts_organization_required.ts` opens
17
+ * with, and the same reason.
18
+ *
19
+ * ## An implication, not an equivalence
20
+ *
21
+ * `customer_account_id is null or organization_id is not null` is FR-010 and
22
+ * FR-011 together and nothing wider: an **owned** row has an organisation, an
23
+ * **ownerless** row need not — an anonymous basket is a representable state and
24
+ * this table is full of them. The equivalence
25
+ * (`(customer_account_id is null) = (organization_id is null)`) would close one
26
+ * further shape, an ownerless row still carrying an organisation, at the price
27
+ * of writing R-6's answer into the schema while R-6 is open. D-187 takes the
28
+ * implication for that reason: do not encode an open question in a constraint.
29
+ *
30
+ * ## No foreign key
31
+ *
32
+ * Deliberately, and matching what this column has always been. D-187 withdraws
33
+ * `r1-spike.md` §8's `on delete set null` recommendation as contradicting
34
+ * FR-011 — it produces a row that names an account and no organisation, which
35
+ * is exactly the state below being made unreachable, and under this constraint
36
+ * it would abort an unrelated organisation delete with a message about a table
37
+ * the operator was not touching. If one is ever taken here it must be
38
+ * `restrict`, and it is not this migration's decision.
39
+ *
40
+ * ## Derive, then count, then refuse — never delete
41
+ *
42
+ * These rows are buyers' baskets. The derivation is the account's own
43
+ * organisation, which is total since D-178 made `customer_accounts.organization_id`
44
+ * `NOT NULL`, so the only way to survive it is a `customer_account_id` pointing
45
+ * at a row that is gone — this table carries no foreign key on that column, so
46
+ * that is a real branch and not a formality. The migration raises with the
47
+ * count and up to twenty ids and deletes nothing (D-184), in the shape the
48
+ * D-178 migration established.
49
+ *
50
+ * `carts` does not declare `customer_accounts` in its manifest `dependencies`
51
+ * and needs no entry for this: `customer_accounts` is created by
52
+ * `organizations/20260424T205317_organizations_init.ts`, far below
53
+ * `BASELINE_THROUGH`, so the table this reads exists whatever this module's
54
+ * position in the dependency order. No foreign key is added, so
55
+ * `fk-dependency-drift` has nothing to say either.
56
+ *
57
+ * The standing guard is
58
+ * `backend/test/integration/tenancy/customer-scoped-organization-completeness.test.ts`,
59
+ * which derives its population from the ORM's metadata rather than from a list
60
+ * — so the next `@CustomerScoped` class to gain this column is covered by
61
+ * gaining it.
62
+ */
63
+ export declare class Migration20260830T112911CartsOrganizationAttributionCheck extends Migration {
64
+ up(): Promise<void>;
65
+ down(): Promise<void>;
66
+ }
67
+ //# sourceMappingURL=20260830T112911_carts_organization_attribution_check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20260830T112911_carts_organization_attribution_check.d.ts","sourceRoot":"","sources":["../../src/migrations/20260830T112911_carts_organization_attribution_check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AACH,qBAAa,yDAA0D,SAAQ,SAAS;IACvE,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC;IAqDnB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAQrC"}
@@ -0,0 +1,121 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+ /**
3
+ * D-187 — a `carts` row that names a customer account carries an organisation.
4
+ *
5
+ * `Cart` is the one `@CustomerScoped` class that already has the column
6
+ * (feature 087, Group B), and it has had it since before the granting arm of
7
+ * `customerFilterCond` went live. Nothing structural has held the pair
8
+ * together: `customerOrganizationColumn` reads the ORM's metadata per query, so
9
+ * the moment the property existed a sales representative's list was filtered on
10
+ * `organization_id` — and MikroORM applies no filter to `INSERT`
11
+ * (`r1-spike.md` §4, measured), so a row written with an account and no
12
+ * organisation is simply invisible to the representative who serves that
13
+ * organisation, on a screen that has stopped saying anything is missing.
14
+ *
15
+ * **The constraint is the only refusal an `INSERT` has**, which is the same
16
+ * sentence `20260825T141659_customer_accounts_organization_required.ts` opens
17
+ * with, and the same reason.
18
+ *
19
+ * ## An implication, not an equivalence
20
+ *
21
+ * `customer_account_id is null or organization_id is not null` is FR-010 and
22
+ * FR-011 together and nothing wider: an **owned** row has an organisation, an
23
+ * **ownerless** row need not — an anonymous basket is a representable state and
24
+ * this table is full of them. The equivalence
25
+ * (`(customer_account_id is null) = (organization_id is null)`) would close one
26
+ * further shape, an ownerless row still carrying an organisation, at the price
27
+ * of writing R-6's answer into the schema while R-6 is open. D-187 takes the
28
+ * implication for that reason: do not encode an open question in a constraint.
29
+ *
30
+ * ## No foreign key
31
+ *
32
+ * Deliberately, and matching what this column has always been. D-187 withdraws
33
+ * `r1-spike.md` §8's `on delete set null` recommendation as contradicting
34
+ * FR-011 — it produces a row that names an account and no organisation, which
35
+ * is exactly the state below being made unreachable, and under this constraint
36
+ * it would abort an unrelated organisation delete with a message about a table
37
+ * the operator was not touching. If one is ever taken here it must be
38
+ * `restrict`, and it is not this migration's decision.
39
+ *
40
+ * ## Derive, then count, then refuse — never delete
41
+ *
42
+ * These rows are buyers' baskets. The derivation is the account's own
43
+ * organisation, which is total since D-178 made `customer_accounts.organization_id`
44
+ * `NOT NULL`, so the only way to survive it is a `customer_account_id` pointing
45
+ * at a row that is gone — this table carries no foreign key on that column, so
46
+ * that is a real branch and not a formality. The migration raises with the
47
+ * count and up to twenty ids and deletes nothing (D-184), in the shape the
48
+ * D-178 migration established.
49
+ *
50
+ * `carts` does not declare `customer_accounts` in its manifest `dependencies`
51
+ * and needs no entry for this: `customer_accounts` is created by
52
+ * `organizations/20260424T205317_organizations_init.ts`, far below
53
+ * `BASELINE_THROUGH`, so the table this reads exists whatever this module's
54
+ * position in the dependency order. No foreign key is added, so
55
+ * `fk-dependency-drift` has nothing to say either.
56
+ *
57
+ * The standing guard is
58
+ * `backend/test/integration/tenancy/customer-scoped-organization-completeness.test.ts`,
59
+ * which derives its population from the ORM's metadata rather than from a list
60
+ * — so the next `@CustomerScoped` class to gain this column is covered by
61
+ * gaining it.
62
+ */
63
+ export class Migration20260830T112911CartsOrganizationAttributionCheck extends Migration {
64
+ async up() {
65
+ // 1. Derive the organisation from the account that owns the cart. Expected
66
+ // to move nothing: `CartService` has stamped it on the owned-create
67
+ // branch since the column existed, and the anonymous branch never
68
+ // becomes owned (the merge completes the anonymous cart rather than
69
+ // re-owning it). "Expected" is not "measured on your database", which is
70
+ // why the statement runs and step 2 counts what it left.
71
+ this.addSql(`
72
+ update "carts" c
73
+ set "organization_id" = ca."organization_id"
74
+ from "customer_accounts" ca
75
+ where ca."id" = c."customer_account_id"
76
+ and c."organization_id" is null;
77
+ `);
78
+ // 2. Count what is left and refuse. A cart can only be here if its
79
+ // `customer_account_id` names an account that no longer exists — the
80
+ // account's own organisation is `NOT NULL` since D-178 — so the row is
81
+ // owned by nobody the platform can name. The merge request that hits
82
+ // this decides what to do with it; deleting a buyer's basket is never
83
+ // it.
84
+ this.addSql(`
85
+ do $$
86
+ declare
87
+ remaining bigint;
88
+ sample text;
89
+ begin
90
+ select count(*) into remaining
91
+ from "carts"
92
+ where "customer_account_id" is not null
93
+ and "organization_id" is null;
94
+ if remaining > 0 then
95
+ select string_agg(id::text, ', ') into sample from (
96
+ select "id" from "carts"
97
+ where "customer_account_id" is not null
98
+ and "organization_id" is null
99
+ order by "id" limit 20
100
+ ) s;
101
+ raise exception
102
+ 'D-187: % carts row(s) still name a customer account with organization_id IS NULL after derivation. First ids: %. Every customer account has an organization (D-178), so these rows name an account that is gone. Do not delete them - decide what they belong to.',
103
+ remaining, sample;
104
+ end if;
105
+ end $$;
106
+ `);
107
+ // 3. The constraint.
108
+ this.addSql(`
109
+ alter table "carts"
110
+ add constraint "carts_organization_attribution_chk"
111
+ check ("customer_account_id" is null or "organization_id" is not null);
112
+ `);
113
+ }
114
+ async down() {
115
+ // Drop the constraint, and nothing else. The organisations step 1 derived
116
+ // were a defect rather than data — a row that named an account already
117
+ // belonged to that account's organisation — so there is nothing to undo.
118
+ this.addSql(`alter table "carts" drop constraint if exists "carts_organization_attribution_chk";`);
119
+ }
120
+ }
121
+ //# sourceMappingURL=20260830T112911_carts_organization_attribution_check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20260830T112911_carts_organization_attribution_check.js","sourceRoot":"","sources":["../../src/migrations/20260830T112911_carts_organization_attribution_check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AACH,MAAM,OAAO,yDAA0D,SAAQ,SAAS;IAC7E,KAAK,CAAC,EAAE;QACf,2EAA2E;QAC3E,uEAAuE;QACvE,qEAAqE;QACrE,uEAAuE;QACvE,4EAA4E;QAC5E,4DAA4D;QAC5D,IAAI,CAAC,MAAM,CAAC;;;;;;KAMX,CAAC,CAAC;QAEH,mEAAmE;QACnE,wEAAwE;QACxE,0EAA0E;QAC1E,wEAAwE;QACxE,yEAAyE;QACzE,SAAS;QACT,IAAI,CAAC,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;KAsBX,CAAC,CAAC;QAEH,qBAAqB;QACrB,IAAI,CAAC,MAAM,CAAC;;;;KAIX,CAAC,CAAC;IACL,CAAC;IAEQ,KAAK,CAAC,IAAI;QACjB,0EAA0E;QAC1E,uEAAuE;QACvE,yEAAyE;QACzE,IAAI,CAAC,MAAM,CACT,qFAAqF,CACtF,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The `./migrations` subpath — every migration class this module owns, as one
3
+ * ordered `migrations` array.
4
+ *
5
+ * The array is what the platform reads when this module is **installed**:
6
+ * `src/packages/package-runtime.ts` takes `exported['migrations']` and refuses
7
+ * the package outright when it is absent (D-168).
8
+ *
9
+ * Listed in ascending timestamp, which is the order of this module's own
10
+ * migrations and of nothing else (feature 081): a manifest `dependencies` array
11
+ * is the only thing ordering this block against another module's.
12
+ *
13
+ * The **named** exports stay beside the array, and the asymmetry with
14
+ * `./backend` — which publishes an array and no named class (D-168) — is
15
+ * deliberate. `db/migrations-registry.generated.ts` imports each class by name
16
+ * from this specifier, and a migration class name is contract in a way an entity
17
+ * class name is not: `mikro_orm_migrations` persists it, so it is a string every
18
+ * already-migrated database holds.
19
+ *
20
+ * A class that is in neither the array nor the barrel is a migration that does
21
+ * not run: `migration:pending` reports nothing pending and the first symptom is
22
+ * a query against a table nobody created.
23
+ */
24
+ import { Migration20260611T140352CartsConsolidation } from './20260611T140352_carts_consolidation.js';
25
+ import { Migration20260611T140413CartsCartItemPackaging } from './20260611T140413_carts_cart_item_packaging.js';
26
+ import { Migration20260818T081253CartsCartCompletedOrderFk } from './20260818T081253_carts_cart_completed_order_fk.js';
27
+ import { Migration20260830T112911CartsOrganizationAttributionCheck } from './20260830T112911_carts_organization_attribution_check.js';
28
+ export declare const migrations: (typeof Migration20260611T140352CartsConsolidation)[];
29
+ export { Migration20260611T140352CartsConsolidation, Migration20260611T140413CartsCartItemPackaging, Migration20260818T081253CartsCartCompletedOrderFk, Migration20260830T112911CartsOrganizationAttributionCheck, };
30
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,0CAA0C,EAAE,MAAM,0CAA0C,CAAC;AACtG,OAAO,EAAE,8CAA8C,EAAE,MAAM,gDAAgD,CAAC;AAChH,OAAO,EAAE,iDAAiD,EAAE,MAAM,oDAAoD,CAAC;AACvH,OAAO,EAAE,yDAAyD,EAAE,MAAM,2DAA2D,CAAC;AAEtI,eAAO,MAAM,UAAU,uDAKtB,CAAC;AAEF,OAAO,EACL,0CAA0C,EAC1C,8CAA8C,EAC9C,iDAAiD,EACjD,yDAAyD,GAC1D,CAAC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The `./migrations` subpath — every migration class this module owns, as one
3
+ * ordered `migrations` array.
4
+ *
5
+ * The array is what the platform reads when this module is **installed**:
6
+ * `src/packages/package-runtime.ts` takes `exported['migrations']` and refuses
7
+ * the package outright when it is absent (D-168).
8
+ *
9
+ * Listed in ascending timestamp, which is the order of this module's own
10
+ * migrations and of nothing else (feature 081): a manifest `dependencies` array
11
+ * is the only thing ordering this block against another module's.
12
+ *
13
+ * The **named** exports stay beside the array, and the asymmetry with
14
+ * `./backend` — which publishes an array and no named class (D-168) — is
15
+ * deliberate. `db/migrations-registry.generated.ts` imports each class by name
16
+ * from this specifier, and a migration class name is contract in a way an entity
17
+ * class name is not: `mikro_orm_migrations` persists it, so it is a string every
18
+ * already-migrated database holds.
19
+ *
20
+ * A class that is in neither the array nor the barrel is a migration that does
21
+ * not run: `migration:pending` reports nothing pending and the first symptom is
22
+ * a query against a table nobody created.
23
+ */
24
+ import { Migration20260611T140352CartsConsolidation } from './20260611T140352_carts_consolidation.js';
25
+ import { Migration20260611T140413CartsCartItemPackaging } from './20260611T140413_carts_cart_item_packaging.js';
26
+ import { Migration20260818T081253CartsCartCompletedOrderFk } from './20260818T081253_carts_cart_completed_order_fk.js';
27
+ import { Migration20260830T112911CartsOrganizationAttributionCheck } from './20260830T112911_carts_organization_attribution_check.js';
28
+ export const migrations = [
29
+ Migration20260611T140352CartsConsolidation,
30
+ Migration20260611T140413CartsCartItemPackaging,
31
+ Migration20260818T081253CartsCartCompletedOrderFk,
32
+ Migration20260830T112911CartsOrganizationAttributionCheck,
33
+ ];
34
+ export { Migration20260611T140352CartsConsolidation, Migration20260611T140413CartsCartItemPackaging, Migration20260818T081253CartsCartCompletedOrderFk, Migration20260830T112911CartsOrganizationAttributionCheck, };
35
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,0CAA0C,EAAE,MAAM,0CAA0C,CAAC;AACtG,OAAO,EAAE,8CAA8C,EAAE,MAAM,gDAAgD,CAAC;AAChH,OAAO,EAAE,iDAAiD,EAAE,MAAM,oDAAoD,CAAC;AACvH,OAAO,EAAE,yDAAyD,EAAE,MAAM,2DAA2D,CAAC;AAEtI,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,0CAA0C;IAC1C,8CAA8C;IAC9C,iDAAiD;IACjD,yDAAyD;CAC1D,CAAC;AAEF,OAAO,EACL,0CAA0C,EAC1C,8CAA8C,EAC9C,iDAAiD,EACjD,yDAAyD,GAC1D,CAAC"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * The port interfaces `carts` publishes whose signature carries a MikroORM
3
+ * `EntityManager`, and **nothing that exists at runtime** (feature 080, T048;
4
+ * D-169, D-171).
5
+ *
6
+ * `tsc` compiles this file to `export {};`. That is the property D-171 makes
7
+ * the boundary decision on — *a subpath is contract surface iff the module it
8
+ * resolves to exports no runtime binding* — and it is why this declaration has
9
+ * its own file rather than sitting on top of `services/cart-read-port.ts`,
10
+ * which exports the read service and the write-port factory beside it. A
11
+ * consumer naming that file names the owner's implementation, whatever
12
+ * `import type` erases; a consumer naming this one names a declaration and can
13
+ * name nothing else.
14
+ *
15
+ * **This is not yet a supported specifier and the ledger still counts it.**
16
+ * `carts` is not a workspace package, so there is no `exports` map for this to
17
+ * be a subpath of, and `check:module-boundary` reads `orders`' relative import
18
+ * exactly as it read the two entity imports it replaces — D-171 says so in as
19
+ * many words: `resolveModulePackage` returns `null` for any specifier starting
20
+ * with `.`, so an unconverted reach has no subpath for the exemption to apply
21
+ * to, and reaching the exempt state takes three separable edits (package the
22
+ * owner, publish the interface, rewrite the specifier). This file is the second
23
+ * of those three, taken early because it is the one that does not need the
24
+ * module to move.
25
+ *
26
+ * Everything else this module publishes is in `@endora-commerce/contracts` and
27
+ * belongs there: `CartReadPort` and `CartWritePort` are contract DTOs end to
28
+ * end, so by D-171's qualifying test — *"does this signature stop the interface
29
+ * living in `packages/contracts`"* — neither belongs here. This one does, and
30
+ * for one reason: its `EntityManager` parameter, which that package may not
31
+ * name because `admin` and `storefront` both compile it (FR-034).
32
+ *
33
+ * No entity class leaves by this door, type-only included (D-168).
34
+ */
35
+ import type { EntityManager } from '@mikro-orm/postgresql';
36
+ import type { CartCustomerContext, CartWithItems } from '@endora-commerce/contracts';
37
+ /**
38
+ * Container name: `cartPlacementApplyPort`. Owner: `carts`.
39
+ *
40
+ * The basket half of order placement, run on the **caller's** `EntityManager`
41
+ * (D-169). `orders` is the one consumer, and it has exactly two questions: what
42
+ * is on the basket I am turning into an order, and — once that order exists —
43
+ * finish the basket off against it.
44
+ *
45
+ * **The seam is not a defect in the design, it *is* the design.**
46
+ * `carts_completed_order_fk` (`carts.completed_order_id` -> `orders.id`, `on
47
+ * delete set null`, D-94.1) is what says so in the schema: the pointer cannot
48
+ * be written before the order exists, and the order does not commit until
49
+ * placement returns. A port that opened its own transaction would commit the
50
+ * completion for a placement that then failed, which is precisely the property
51
+ * `test/integration/orders/place-order-failure-preserves-cart.test.ts` asserts
52
+ * the opposite of — a buyer whose placement fails finds their basket exactly as
53
+ * they left it. The **cart-side** column is what forces that: an
54
+ * `orders.cart_id` would have been satisfiable by completing the cart in a
55
+ * second transaction, because the cart is already committed when the order row
56
+ * is written. A foreign key needs the **table** and never the class (D-169), so
57
+ * that constraint stands while this module publishes no entity class by name.
58
+ *
59
+ * **Why the read takes an `EntityManager` too**, when D-169 says a read handed
60
+ * one is a write seam re-opened to serve a read. Because it is not a read this
61
+ * module offers anybody — it is the first step of *this* protocol, and the rows
62
+ * it returns are the rows {@link CartPlacementApplyPort.completeForOrder} then
63
+ * writes. Reading them on a second `EntityManager` would put a commit boundary
64
+ * between the totals a placement quotes and the basket it clears. The
65
+ * standalone read is a different method on a different port and takes no
66
+ * `EntityManager`: `CartReadPort.findActiveForCustomer` in
67
+ * `@endora-commerce/contracts`, which is what `orders`' non-transactional
68
+ * total preview resolves.
69
+ *
70
+ * **Owner off:** unreachable. `carts` declares `activation.nonDeactivatable`,
71
+ * so it never enters an absent state, and there is no deactivation-consequence
72
+ * to state for an owner the platform refuses to switch off. The port is
73
+ * registered with `providePort` like every other name this module publishes, so
74
+ * that the answer stays the manifest's to give and not this line's.
75
+ */
76
+ export interface CartPlacementApplyPort {
77
+ /**
78
+ * The customer's active basket and its lines, read on `em`.
79
+ *
80
+ * `em` is **required** (D-169). Its one caller is `placeOrder`, which always
81
+ * passes the `EntityManager` its own transaction runs on; an optional
82
+ * parameter is what lets the same method double as a standalone read, and
83
+ * that is a lie about a seam whose whole content is that the read and the
84
+ * write share a transaction.
85
+ *
86
+ * `null` when the customer has no active basket — the state the caller
87
+ * answers `409 CART_EMPTY` for, together with an empty line list.
88
+ */
89
+ readActiveForPlacement(em: EntityManager, ctx: CartCustomerContext): Promise<CartWithItems | null>;
90
+ /**
91
+ * Finish the basket the order was placed from: remove its lines, move it to
92
+ * `completed`, stamp `completed_order_id` and release the anonymous-cart
93
+ * token.
94
+ *
95
+ * Runs on the same `em` as the read above and as the order's own writes, for
96
+ * the reason `carts_completed_order_fk` gives. It is a no-op for a cart that
97
+ * is not there, which cannot happen on the placement path — the caller read
98
+ * it one transaction-step ago — and is the honest answer rather than a throw
99
+ * if it ever does.
100
+ */
101
+ completeForOrder(em: EntityManager, input: {
102
+ cartId: string;
103
+ orderId: string;
104
+ }): Promise<void>;
105
+ }
106
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ports/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAErF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;;;;;;;;;OAWG;IACH,sBAAsB,CACpB,EAAE,EAAE,aAAa,EACjB,GAAG,EAAE,mBAAmB,GACvB,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IACjC;;;;;;;;;;OAUG;IACH,gBAAgB,CACd,EAAE,EAAE,aAAa,EACjB,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GACzC,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ports/index.ts"],"names":[],"mappings":""}