@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.
- package/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/admin/components/CartApprovalPolicyPanel.d.ts +51 -0
- package/dist/admin/components/CartApprovalPolicyPanel.d.ts.map +1 -0
- package/dist/admin/components/CartApprovalPolicyPanel.js +59 -0
- package/dist/admin/components/CartApprovalPolicyPanel.js.map +1 -0
- package/dist/admin/index.d.ts +38 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +73 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/CartDetail.d.ts +3 -0
- package/dist/admin/pages/CartDetail.d.ts.map +1 -0
- package/dist/admin/pages/CartDetail.js +74 -0
- package/dist/admin/pages/CartDetail.js.map +1 -0
- package/dist/admin/pages/CartsList.d.ts +3 -0
- package/dist/admin/pages/CartsList.d.ts.map +1 -0
- package/dist/admin/pages/CartsList.js +90 -0
- package/dist/admin/pages/CartsList.js.map +1 -0
- package/dist/admin/zones/CartApprovalPolicy.d.ts +29 -0
- package/dist/admin/zones/CartApprovalPolicy.d.ts.map +1 -0
- package/dist/admin/zones/CartApprovalPolicy.js +11 -0
- package/dist/admin/zones/CartApprovalPolicy.js.map +1 -0
- package/dist/backend/cli/abandonment-sweep.d.ts +64 -0
- package/dist/backend/cli/abandonment-sweep.d.ts.map +1 -0
- package/dist/backend/cli/abandonment-sweep.js +17 -0
- package/dist/backend/cli/abandonment-sweep.js.map +1 -0
- package/dist/backend/entities/cart-audit-entry.entity.d.ts +29 -0
- package/dist/backend/entities/cart-audit-entry.entity.d.ts.map +1 -0
- package/dist/backend/entities/cart-audit-entry.entity.js +109 -0
- package/dist/backend/entities/cart-audit-entry.entity.js.map +1 -0
- package/dist/backend/entities/cart-item.entity.d.ts +42 -0
- package/dist/backend/entities/cart-item.entity.d.ts.map +1 -0
- package/dist/backend/entities/cart-item.entity.js +119 -0
- package/dist/backend/entities/cart-item.entity.js.map +1 -0
- package/dist/backend/entities/cart.entity.d.ts +96 -0
- package/dist/backend/entities/cart.entity.d.ts.map +1 -0
- package/dist/backend/entities/cart.entity.js +207 -0
- package/dist/backend/entities/cart.entity.js.map +1 -0
- package/dist/backend/index.d.ts +122 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +184 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +31 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +95 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.d.ts +101 -0
- package/dist/backend/routes.d.ts.map +1 -0
- package/dist/backend/routes.js +620 -0
- package/dist/backend/routes.js.map +1 -0
- package/dist/backend/routes.organization.d.ts +29 -0
- package/dist/backend/routes.organization.d.ts.map +1 -0
- package/dist/backend/routes.organization.js +150 -0
- package/dist/backend/routes.organization.js.map +1 -0
- package/dist/backend/services/cart-abandonment-settings.d.ts +34 -0
- package/dist/backend/services/cart-abandonment-settings.d.ts.map +1 -0
- package/dist/backend/services/cart-abandonment-settings.js +55 -0
- package/dist/backend/services/cart-abandonment-settings.js.map +1 -0
- package/dist/backend/services/cart-abandonment-worker.d.ts +110 -0
- package/dist/backend/services/cart-abandonment-worker.d.ts.map +1 -0
- package/dist/backend/services/cart-abandonment-worker.js +185 -0
- package/dist/backend/services/cart-abandonment-worker.js.map +1 -0
- package/dist/backend/services/cart-admin-service.d.ts +58 -0
- package/dist/backend/services/cart-admin-service.d.ts.map +1 -0
- package/dist/backend/services/cart-admin-service.js +397 -0
- package/dist/backend/services/cart-admin-service.js.map +1 -0
- package/dist/backend/services/cart-approval-service.d.ts +147 -0
- package/dist/backend/services/cart-approval-service.d.ts.map +1 -0
- package/dist/backend/services/cart-approval-service.js +242 -0
- package/dist/backend/services/cart-approval-service.js.map +1 -0
- package/dist/backend/services/cart-audit-service.d.ts +75 -0
- package/dist/backend/services/cart-audit-service.d.ts.map +1 -0
- package/dist/backend/services/cart-audit-service.js +70 -0
- package/dist/backend/services/cart-audit-service.js.map +1 -0
- package/dist/backend/services/cart-conversion-service.d.ts +104 -0
- package/dist/backend/services/cart-conversion-service.d.ts.map +1 -0
- package/dist/backend/services/cart-conversion-service.js +175 -0
- package/dist/backend/services/cart-conversion-service.js.map +1 -0
- package/dist/backend/services/cart-coupon-service.d.ts +129 -0
- package/dist/backend/services/cart-coupon-service.d.ts.map +1 -0
- package/dist/backend/services/cart-coupon-service.js +244 -0
- package/dist/backend/services/cart-coupon-service.js.map +1 -0
- package/dist/backend/services/cart-organization-visibility-service.d.ts +43 -0
- package/dist/backend/services/cart-organization-visibility-service.d.ts.map +1 -0
- package/dist/backend/services/cart-organization-visibility-service.js +98 -0
- package/dist/backend/services/cart-organization-visibility-service.js.map +1 -0
- package/dist/backend/services/cart-placement-apply-port.d.ts +38 -0
- package/dist/backend/services/cart-placement-apply-port.d.ts.map +1 -0
- package/dist/backend/services/cart-placement-apply-port.js +71 -0
- package/dist/backend/services/cart-placement-apply-port.js.map +1 -0
- package/dist/backend/services/cart-pricing-recompute.d.ts +62 -0
- package/dist/backend/services/cart-pricing-recompute.d.ts.map +1 -0
- package/dist/backend/services/cart-pricing-recompute.js +131 -0
- package/dist/backend/services/cart-pricing-recompute.js.map +1 -0
- package/dist/backend/services/cart-query-service.d.ts +29 -0
- package/dist/backend/services/cart-query-service.d.ts.map +1 -0
- package/dist/backend/services/cart-query-service.js +40 -0
- package/dist/backend/services/cart-query-service.js.map +1 -0
- package/dist/backend/services/cart-read-port.d.ts +40 -0
- package/dist/backend/services/cart-read-port.d.ts.map +1 -0
- package/dist/backend/services/cart-read-port.js +144 -0
- package/dist/backend/services/cart-read-port.js.map +1 -0
- package/dist/backend/services/cart-recompute-cache.d.ts +49 -0
- package/dist/backend/services/cart-recompute-cache.d.ts.map +1 -0
- package/dist/backend/services/cart-recompute-cache.js +56 -0
- package/dist/backend/services/cart-recompute-cache.js.map +1 -0
- package/dist/backend/services/cart-service.d.ts +165 -0
- package/dist/backend/services/cart-service.d.ts.map +1 -0
- package/dist/backend/services/cart-service.js +556 -0
- package/dist/backend/services/cart-service.js.map +1 -0
- package/dist/backend/services/cart-state-machine.d.ts +51 -0
- package/dist/backend/services/cart-state-machine.d.ts.map +1 -0
- package/dist/backend/services/cart-state-machine.js +74 -0
- package/dist/backend/services/cart-state-machine.js.map +1 -0
- package/dist/backend/services/cart-upsell-service.d.ts +57 -0
- package/dist/backend/services/cart-upsell-service.d.ts.map +1 -0
- package/dist/backend/services/cart-upsell-service.js +100 -0
- package/dist/backend/services/cart-upsell-service.js.map +1 -0
- package/dist/backend/services/customer-group-resolver.d.ts +27 -0
- package/dist/backend/services/customer-group-resolver.d.ts.map +1 -0
- package/dist/backend/services/customer-group-resolver.js +32 -0
- package/dist/backend/services/customer-group-resolver.js.map +1 -0
- package/dist/manifest.d.ts +221 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +224 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260611T140352_carts_consolidation.d.ts +43 -0
- package/dist/migrations/20260611T140352_carts_consolidation.d.ts.map +1 -0
- package/dist/migrations/20260611T140352_carts_consolidation.js +237 -0
- package/dist/migrations/20260611T140352_carts_consolidation.js.map +1 -0
- package/dist/migrations/20260611T140413_carts_cart_item_packaging.d.ts +12 -0
- package/dist/migrations/20260611T140413_carts_cart_item_packaging.d.ts.map +1 -0
- package/dist/migrations/20260611T140413_carts_cart_item_packaging.js +20 -0
- package/dist/migrations/20260611T140413_carts_cart_item_packaging.js.map +1 -0
- package/dist/migrations/20260818T081253_carts_cart_completed_order_fk.d.ts +50 -0
- package/dist/migrations/20260818T081253_carts_cart_completed_order_fk.d.ts.map +1 -0
- package/dist/migrations/20260818T081253_carts_cart_completed_order_fk.js +62 -0
- package/dist/migrations/20260818T081253_carts_cart_completed_order_fk.js.map +1 -0
- package/dist/migrations/20260830T112911_carts_organization_attribution_check.d.ts +67 -0
- package/dist/migrations/20260830T112911_carts_organization_attribution_check.d.ts.map +1 -0
- package/dist/migrations/20260830T112911_carts_organization_attribution_check.js +121 -0
- package/dist/migrations/20260830T112911_carts_organization_attribution_check.js.map +1 -0
- package/dist/migrations/index.d.ts +30 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +35 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/ports/index.d.ts +106 -0
- package/dist/ports/index.d.ts.map +1 -0
- package/dist/ports/index.js +2 -0
- package/dist/ports/index.js.map +1 -0
- package/docs/carts.md +232 -0
- package/i18n/en.json +96 -0
- package/i18n/pl.json +96 -0
- package/package.json +107 -0
- 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 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ports/index.ts"],"names":[],"mappings":""}
|