@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
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { defineModuleManifest, defineModuleSettingsManifest, } from '@endora-commerce/contracts';
|
|
2
|
+
/**
|
|
3
|
+
* Carts module — feature 027 consolidation pass.
|
|
4
|
+
*
|
|
5
|
+
* Settings (`carts.abandonment.*`) declared here are seeded by the module-
|
|
6
|
+
* lifecycle ManifestReconciler on backend boot. No raw INSERT in the
|
|
7
|
+
* migration; the reconciler is the single seed path (matches the
|
|
8
|
+
* `inventory.*` and `organizations.*` patterns).
|
|
9
|
+
*/
|
|
10
|
+
export const CARTS_SETTING_CODES = {
|
|
11
|
+
ABANDONMENT_INACTIVITY_MINUTES: 'carts.abandonment.inactivity_minutes',
|
|
12
|
+
ABANDONMENT_NOTIFICATION_RECIPIENT: 'carts.abandonment.notification_recipient',
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* The manifest defaults, exported so a read that has to degrade degrades to
|
|
16
|
+
* *this* binding rather than to a literal invented at the call site (feature
|
|
17
|
+
* 072, D-43).
|
|
18
|
+
*
|
|
19
|
+
* The two used to diverge: the manifest said 10080 while both readers' `catch`
|
|
20
|
+
* answered `0`, and `CartAbandonmentWorker` treats `<= 0` as "sweep nothing".
|
|
21
|
+
* The divergence did not merely lose the configured value — it inverted the
|
|
22
|
+
* feature, and it would still have been off after the channel was fixed. A
|
|
23
|
+
* compiled-in fallback is a second source of truth consulted only when the
|
|
24
|
+
* first is unreachable, i.e. exactly when nobody is watching.
|
|
25
|
+
*/
|
|
26
|
+
export const DEFAULT_ABANDONMENT_INACTIVITY_MINUTES = 10080;
|
|
27
|
+
export const DEFAULT_ABANDONMENT_NOTIFICATION_RECIPIENT = '';
|
|
28
|
+
const settings = defineModuleSettingsManifest({
|
|
29
|
+
moduleCode: 'carts',
|
|
30
|
+
groups: [{ code: 'carts', name: 'Carts' }],
|
|
31
|
+
settings: [
|
|
32
|
+
{
|
|
33
|
+
code: CARTS_SETTING_CODES.ABANDONMENT_INACTIVITY_MINUTES,
|
|
34
|
+
name: 'Cart abandonment threshold (minutes)',
|
|
35
|
+
description: 'Minutes of inactivity before an Active cart is considered Abandoned. Default 10080 (7 days).',
|
|
36
|
+
groupCode: 'carts',
|
|
37
|
+
valueType: 'number',
|
|
38
|
+
defaultValue: DEFAULT_ABANDONMENT_INACTIVITY_MINUTES,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
code: CARTS_SETTING_CODES.ABANDONMENT_NOTIFICATION_RECIPIENT,
|
|
42
|
+
name: 'Abandonment notification recipient',
|
|
43
|
+
description: 'Single e-mail address that receives an abandonment notification on every Active → Abandoned transition. Empty = no notification.',
|
|
44
|
+
groupCode: 'carts',
|
|
45
|
+
valueType: 'string',
|
|
46
|
+
defaultValue: DEFAULT_ABANDONMENT_NOTIFICATION_RECIPIENT,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
export const manifest = defineModuleManifest({
|
|
51
|
+
id: 'carts',
|
|
52
|
+
name: 'Carts',
|
|
53
|
+
description: 'Shopping cart aggregation, pricing, lifecycle, and approval gate.',
|
|
54
|
+
version: '2.0.0',
|
|
55
|
+
// Feature 075, Phase C adds `catalog`. Six files read the `Product`,
|
|
56
|
+
// `ProductPackagingUnit` and `ProductLink` entities directly — the cart line,
|
|
57
|
+
// the cart serializer, the admin detail, the up-sell strip, the pricing
|
|
58
|
+
// recompute and the quote-to-cart conversion — so an operator who switched
|
|
59
|
+
// `catalog` off still had carts naming, pricing and accepting products the
|
|
60
|
+
// platform had stopped serving, out of tables deactivation leaves in place.
|
|
61
|
+
// Binding rather than non-binding: a basket that cannot resolve what is in it
|
|
62
|
+
// must refuse, not quote a figure it cannot justify (issue #124's rule, which
|
|
63
|
+
// `price_lists` already stands on here).
|
|
64
|
+
//
|
|
65
|
+
// D-94.1 adds `orders`, for the foreign key `carts_completed_order_fk`
|
|
66
|
+
// (`carts.completed_order_id` -> `orders.id`, `on delete set null`) — the
|
|
67
|
+
// pointer this table has always had for its *other* terminal transition
|
|
68
|
+
// (`carts_converted_to_qr_fk`) and never for this one. AGENTS.md
|
|
69
|
+
// § Migrations item 4: a cross-module foreign key is declared here or the
|
|
70
|
+
// build fails.
|
|
71
|
+
//
|
|
72
|
+
// D-94.3 moves `promotions` out, to `acknowledgedDependencies` below. The
|
|
73
|
+
// edge is unchanged and still binds — what is withdrawn is the install
|
|
74
|
+
// ordering, which `promotion_usages_order_fk` now says runs the other way:
|
|
75
|
+
// `promotions` declares `orders`, `orders` is declared here, so keeping
|
|
76
|
+
// `promotions` in `dependencies` would close `carts -> promotions -> orders
|
|
77
|
+
// -> carts`.
|
|
78
|
+
dependencies: [
|
|
79
|
+
'catalog',
|
|
80
|
+
'customer_accounts',
|
|
81
|
+
'orders',
|
|
82
|
+
'organizations',
|
|
83
|
+
'price_lists',
|
|
84
|
+
'quote_requests',
|
|
85
|
+
'sales_channels',
|
|
86
|
+
'settings',
|
|
87
|
+
],
|
|
88
|
+
// Feature 073, Amendment A1 — a real port edge whose `dependencies` entry
|
|
89
|
+
// would close a cycle. Read by the flip-time refusals and by
|
|
90
|
+
// `check-port-dependencies`; read by neither the install order nor the
|
|
91
|
+
// migration order.
|
|
92
|
+
acknowledgedDependencies: [
|
|
93
|
+
{
|
|
94
|
+
moduleId: 'promotions',
|
|
95
|
+
port: 'promotionService',
|
|
96
|
+
reason: 'The basket prices through `PromotionApplyPort.applyToCart`, which is a real bind: a ' +
|
|
97
|
+
'cart that cannot resolve its discount must not quote a figure it cannot justify. ' +
|
|
98
|
+
'The edge cannot go in `dependencies` since D-94.1, because ' +
|
|
99
|
+
'`promotion_usages_order_fk` obliges `promotions` to declare `orders`, this module ' +
|
|
100
|
+
'declares `orders` for `carts_completed_order_fk`, and the three together close ' +
|
|
101
|
+
'`carts -> promotions -> orders -> carts`. Acknowledging it withdraws the install ' +
|
|
102
|
+
'ordering the constraint says is backwards and withdraws nothing else — `promotions` ' +
|
|
103
|
+
'stays exactly as (un)deactivatable under this module as it was.',
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
moduleId: 'promotions',
|
|
107
|
+
port: 'promotionCodePort',
|
|
108
|
+
reason: 'The coupon a buyer typed, resolved through the three-step lookup `promotions` owns — ' +
|
|
109
|
+
'the legacy inline code, then the coupon table, then the promotion behind it, each ' +
|
|
110
|
+
'filtered on `isActive`. It shares the cycle of `promotionService` above and is ' +
|
|
111
|
+
'acknowledged for the same reason and with the same effect: the bind is kept, the ' +
|
|
112
|
+
'ordering claim the foreign key contradicts is dropped.',
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
/**
|
|
116
|
+
* The three error codes this module owns — feature 090 Phase 3
|
|
117
|
+
* (`specs/090-module-owned-error-codes/contracts/error-code-declaration.md`
|
|
118
|
+
* §1.1). This is where each sentence is looked up from: `errors.<CODE>` in this
|
|
119
|
+
* module's own `i18n/{en,pl}.json`, which already holds all three in both
|
|
120
|
+
* languages.
|
|
121
|
+
*
|
|
122
|
+
* The list is answer-preserving, not a judgement (§6.2 and §6.5): it is exactly
|
|
123
|
+
* what the prefix chain in `@endora-commerce/mod-i18n` routes here today,
|
|
124
|
+
* copied from the frozen capture at
|
|
125
|
+
* `backend/test/fixtures/error-code-routing/chain-answers.ts` rather than
|
|
126
|
+
* re-derived. Re-routing a code to a better owner is
|
|
127
|
+
* `specs/082-error-code-ownership/rulings.md` §9's remaining work and is
|
|
128
|
+
* deliberately not done here.
|
|
129
|
+
*
|
|
130
|
+
* **Two codes a reader will look for here and not find.** `MIN_NOT_MET` reads
|
|
131
|
+
* like a cart minimum and is `catalog`'s: it is one of three outcomes of bundle
|
|
132
|
+
* option validation (`bundle.service.ts`), and the chain routes it there.
|
|
133
|
+
* `PROMOTION_INVALID` reads like the coupon path below it and falls through the
|
|
134
|
+
* chain to the platform block; nothing in the tree raises it at all.
|
|
135
|
+
*
|
|
136
|
+
* **The tokens are the enum, not the bundle.** `CART_COUPON_REJECTED` is the
|
|
137
|
+
* one code in this module that carries refusal discriminators
|
|
138
|
+
* (`specs/082-error-code-ownership/contracts/error-code-ownership.md` §1.4):
|
|
139
|
+
* `routes.ts` puts `result.reason` in `details.code`, which the envelope reads
|
|
140
|
+
* as the tail of `errors.CART_COUPON_REJECTED.<token>`. That value's type is
|
|
141
|
+
* `CouponApplyDropped.reason` = `CouponDropReason` = `couponDropReasonSchema` in
|
|
142
|
+
* `packages/contracts/src/carts.ts`, and the seven members below are that
|
|
143
|
+
* enum's, in its order. The bundle happens to hold a sentence for each of the
|
|
144
|
+
* seven today, so reading the bundle instead would give the same answer — but
|
|
145
|
+
* only by coincidence: the bundle is a lower bound, and a reason added to the
|
|
146
|
+
* enum before its sentence is written would be missing from it. The enum is the
|
|
147
|
+
* authority because it is what the raise site can actually produce.
|
|
148
|
+
*/
|
|
149
|
+
errorCodes: [
|
|
150
|
+
{
|
|
151
|
+
code: 'CART_COUPON_REJECTED',
|
|
152
|
+
tokens: [
|
|
153
|
+
'invalid_code',
|
|
154
|
+
'expired',
|
|
155
|
+
'below_min_spend',
|
|
156
|
+
'wrong_channel',
|
|
157
|
+
'wrong_customer_group',
|
|
158
|
+
'wrong_organization',
|
|
159
|
+
'coupon_format_invalid',
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
{ code: 'CART_EMPTY' },
|
|
163
|
+
{ code: 'CART_LINE_CAP_EXCEEDED' },
|
|
164
|
+
],
|
|
165
|
+
settings,
|
|
166
|
+
// Feature 074 (Constitution XVII), test C2 — functional base. This reverses
|
|
167
|
+
// the reading 073 took. The quote-only deployment that argued for a switch
|
|
168
|
+
// here is real, but it does not want the *basket* gone: checkout, quick order
|
|
169
|
+
// and the quote flow all assemble one, so switching this off removes the
|
|
170
|
+
// structure every ordering path is built on rather than one path among
|
|
171
|
+
// several. A deployment that quotes and never checks out simply does not use
|
|
172
|
+
// checkout; `quote_requests` is where that choice belongs, and it stays
|
|
173
|
+
// operator-controlled.
|
|
174
|
+
//
|
|
175
|
+
// `carts.enabled` goes with the control it backed: one of the nineteen that
|
|
176
|
+
// never accepted a deactivation. The existing rows are removed by a core data
|
|
177
|
+
// migration (feature 074, FR-010a).
|
|
178
|
+
activation: {
|
|
179
|
+
nonDeactivatable: true,
|
|
180
|
+
reason: 'The basket every ordering path assembles — checkout, quick order and the quote flow all ' +
|
|
181
|
+
'go through it.',
|
|
182
|
+
},
|
|
183
|
+
// Feature 026 checklist — `routes.admin.ts` gates on both codes and this
|
|
184
|
+
// manifest declared neither, so until T136 only a role holding `'*'` could
|
|
185
|
+
// reach the admin cart screens. The permission-inventory scanner that should
|
|
186
|
+
// have caught it read one call shape out of the several the tree writes; it
|
|
187
|
+
// now reads every shape, blanks comments and regex literals, and reports an
|
|
188
|
+
// argument it cannot resolve instead of dropping it.
|
|
189
|
+
//
|
|
190
|
+
// D-173 adds the third, and it is not this module's own code: the admin
|
|
191
|
+
// proxy for the per-Organization `requires_cart_approval` policy is gated on
|
|
192
|
+
// `customers:manage`, whose core `PERMISSION_CATALOGUE` row names
|
|
193
|
+
// `customers` — a module an operator can switch off, while this one declares
|
|
194
|
+
// itself non-deactivatable. Declaring it here makes this module a second
|
|
195
|
+
// *owner*, in the shape issue #213 gave `integrations:manage`, so the code
|
|
196
|
+
// stays grantable on `/admin-roles` while this surface is on. The label —
|
|
197
|
+
// *"Manage customer organizations"* — reads as a sentence about that
|
|
198
|
+
// endpoint, so the code is shared rather than replaced.
|
|
199
|
+
permissions: [
|
|
200
|
+
{ code: 'carts:read', label: 'View customer carts' },
|
|
201
|
+
{ code: 'carts:reject', label: 'Reject a cart pending organization approval' },
|
|
202
|
+
{ code: 'customers:manage', label: 'Manage customer organizations' },
|
|
203
|
+
],
|
|
204
|
+
i18n: { bundlesDir: 'i18n' },
|
|
205
|
+
docs: { dir: 'docs' },
|
|
206
|
+
});
|
|
207
|
+
/** Legacy export retained for backward compatibility. */
|
|
208
|
+
export const cartsManifest = settings;
|
|
209
|
+
/**
|
|
210
|
+
* The operator command this module declares — feature 080, T042b / D-160.9.
|
|
211
|
+
*
|
|
212
|
+
* It was `scripts/abandonment-sweep.ts`, the tree's one call site of
|
|
213
|
+
* `requireModuleEnabled` and the worked example D-157 was written around. The
|
|
214
|
+
* host asks that question now, at the declaration seam, for every command; see
|
|
215
|
+
* `cli/abandonment-sweep.ts` for what the move changed and what it did not.
|
|
216
|
+
*/
|
|
217
|
+
export const cliCommands = [
|
|
218
|
+
{
|
|
219
|
+
name: 'abandonment-sweep',
|
|
220
|
+
summary: 'Run one pass of the cart abandonment sweep against the live database.',
|
|
221
|
+
run: async (context) => (await import('./backend/cli/abandonment-sweep.js')).abandonmentSweep(context),
|
|
222
|
+
},
|
|
223
|
+
];
|
|
224
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
/**
|
|
3
|
+
* Carts consolidation (feature 027).
|
|
4
|
+
*
|
|
5
|
+
* Brings the foundation `carts` table up to the spec defined in feature
|
|
6
|
+
* 027-carts: status lifecycle (active / abandoned / completed / rejected),
|
|
7
|
+
* sales-channel scoping, last-activity bookkeeping for the abandonment
|
|
8
|
+
* sweep, the orthogonal approval sub-state (not_required / pending /
|
|
9
|
+
* approved / rejected_by_org_admin), a coupon-code column wired to the
|
|
10
|
+
* Promotions module, a conversion-lineage column to the Quote Requests
|
|
11
|
+
* module, an abandonment-notification idempotency timestamp, and an
|
|
12
|
+
* optimistic-lock `version` column shared with feature 003 / 026.
|
|
13
|
+
*
|
|
14
|
+
* `cart_items` gains a two-layer pricing snapshot: the existing
|
|
15
|
+
* `unit_price` remains the price the line was added at, while
|
|
16
|
+
* `recomputed_unit_price` / `recomputed_at` / `recomputed_currency` cache
|
|
17
|
+
* the most recent re-resolved price (per feature 011's PricingService).
|
|
18
|
+
*
|
|
19
|
+
* `organizations` gains `requires_cart_approval` — the per-Organization
|
|
20
|
+
* policy flag that gates checkout behind an Organization Administrator's
|
|
21
|
+
* approval. Default false preserves the prior behaviour.
|
|
22
|
+
*
|
|
23
|
+
* Introduces one new table:
|
|
24
|
+
* - cart_audit_entries — typed per-cart audit feed for the cart-detail
|
|
25
|
+
* UI (the platform-wide audit timeline still uses audit_log_entries).
|
|
26
|
+
*
|
|
27
|
+
* Data migration: legacy `status='converted'` rows are remapped to
|
|
28
|
+
* `status='completed'`. The legacy unique partial index
|
|
29
|
+
* `carts_one_active_per_customer` (introduced by feature 001) is dropped
|
|
30
|
+
* and replaced with `carts_one_active_per_customer_per_channel` so the
|
|
31
|
+
* "one active cart per (Customer, Sales Channel)" rule from the spec is
|
|
32
|
+
* enforced at the database layer.
|
|
33
|
+
*
|
|
34
|
+
* Settings (`carts.abandonment.inactivity_minutes`,
|
|
35
|
+
* `carts.abandonment.notification_recipient`) are seeded by the module
|
|
36
|
+
* lifecycle reconciler from the `settings` declaration in
|
|
37
|
+
* `carts/manifest.ts` — not from this migration.
|
|
38
|
+
*/
|
|
39
|
+
export declare class Migration20260611T140352CartsConsolidation extends Migration {
|
|
40
|
+
up(): Promise<void>;
|
|
41
|
+
down(): Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=20260611T140352_carts_consolidation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20260611T140352_carts_consolidation.d.ts","sourceRoot":"","sources":["../../src/migrations/20260611T140352_carts_consolidation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,qBAAa,0CAA2C,SAAQ,SAAS;IACxD,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC;IA2JnB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CA0DrC"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
/**
|
|
3
|
+
* Carts consolidation (feature 027).
|
|
4
|
+
*
|
|
5
|
+
* Brings the foundation `carts` table up to the spec defined in feature
|
|
6
|
+
* 027-carts: status lifecycle (active / abandoned / completed / rejected),
|
|
7
|
+
* sales-channel scoping, last-activity bookkeeping for the abandonment
|
|
8
|
+
* sweep, the orthogonal approval sub-state (not_required / pending /
|
|
9
|
+
* approved / rejected_by_org_admin), a coupon-code column wired to the
|
|
10
|
+
* Promotions module, a conversion-lineage column to the Quote Requests
|
|
11
|
+
* module, an abandonment-notification idempotency timestamp, and an
|
|
12
|
+
* optimistic-lock `version` column shared with feature 003 / 026.
|
|
13
|
+
*
|
|
14
|
+
* `cart_items` gains a two-layer pricing snapshot: the existing
|
|
15
|
+
* `unit_price` remains the price the line was added at, while
|
|
16
|
+
* `recomputed_unit_price` / `recomputed_at` / `recomputed_currency` cache
|
|
17
|
+
* the most recent re-resolved price (per feature 011's PricingService).
|
|
18
|
+
*
|
|
19
|
+
* `organizations` gains `requires_cart_approval` — the per-Organization
|
|
20
|
+
* policy flag that gates checkout behind an Organization Administrator's
|
|
21
|
+
* approval. Default false preserves the prior behaviour.
|
|
22
|
+
*
|
|
23
|
+
* Introduces one new table:
|
|
24
|
+
* - cart_audit_entries — typed per-cart audit feed for the cart-detail
|
|
25
|
+
* UI (the platform-wide audit timeline still uses audit_log_entries).
|
|
26
|
+
*
|
|
27
|
+
* Data migration: legacy `status='converted'` rows are remapped to
|
|
28
|
+
* `status='completed'`. The legacy unique partial index
|
|
29
|
+
* `carts_one_active_per_customer` (introduced by feature 001) is dropped
|
|
30
|
+
* and replaced with `carts_one_active_per_customer_per_channel` so the
|
|
31
|
+
* "one active cart per (Customer, Sales Channel)" rule from the spec is
|
|
32
|
+
* enforced at the database layer.
|
|
33
|
+
*
|
|
34
|
+
* Settings (`carts.abandonment.inactivity_minutes`,
|
|
35
|
+
* `carts.abandonment.notification_recipient`) are seeded by the module
|
|
36
|
+
* lifecycle reconciler from the `settings` declaration in
|
|
37
|
+
* `carts/manifest.ts` — not from this migration.
|
|
38
|
+
*/
|
|
39
|
+
export class Migration20260611T140352CartsConsolidation extends Migration {
|
|
40
|
+
async up() {
|
|
41
|
+
// 1) Data backfill: rename legacy 'converted' → 'completed' before the
|
|
42
|
+
// new CHECK constraint is installed. Foundation code that wrote
|
|
43
|
+
// 'converted' (CartService.clearForCustomer) is updated to write
|
|
44
|
+
// 'completed' in the same PR.
|
|
45
|
+
this.addSql(`update "carts" set "status" = 'completed' where "status" = 'converted';`);
|
|
46
|
+
// 2) New columns on `carts`.
|
|
47
|
+
this.addSql(`
|
|
48
|
+
alter table "carts"
|
|
49
|
+
add column "sales_channel_id" uuid null,
|
|
50
|
+
add column "last_activity_at" timestamptz not null default now(),
|
|
51
|
+
add column "approval_status" varchar(32) not null default 'not_required',
|
|
52
|
+
add column "submitted_for_approval_at" timestamptz null,
|
|
53
|
+
add column "approved_at" timestamptz null,
|
|
54
|
+
add column "approved_by_customer_account_id" uuid null,
|
|
55
|
+
add column "rejected_at" timestamptz null,
|
|
56
|
+
add column "rejected_by_actor" varchar(48) null,
|
|
57
|
+
add column "rejected_reason" text null,
|
|
58
|
+
add column "applied_promotion_code" varchar(64) null,
|
|
59
|
+
add column "converted_to_quote_request_id" uuid null,
|
|
60
|
+
add column "abandonment_notified_at" timestamptz null,
|
|
61
|
+
add column "version" int not null default 0;
|
|
62
|
+
`);
|
|
63
|
+
// 3) CHECK constraints for the two enum-like columns.
|
|
64
|
+
this.addSql(`
|
|
65
|
+
alter table "carts"
|
|
66
|
+
add constraint "carts_status_check"
|
|
67
|
+
check ("status" in ('active', 'abandoned', 'completed', 'rejected'));
|
|
68
|
+
`);
|
|
69
|
+
this.addSql(`
|
|
70
|
+
alter table "carts"
|
|
71
|
+
add constraint "carts_approval_status_check"
|
|
72
|
+
check ("approval_status" in (
|
|
73
|
+
'not_required', 'pending', 'approved', 'rejected_by_org_admin'
|
|
74
|
+
));
|
|
75
|
+
`);
|
|
76
|
+
// 4) Foreign keys on the new columns.
|
|
77
|
+
// sales_channel_id → sales_channels(id). ON DELETE SET NULL keeps the
|
|
78
|
+
// cart record alive if a channel is deleted (rare; admin operation).
|
|
79
|
+
this.addSql(`
|
|
80
|
+
alter table "carts"
|
|
81
|
+
add constraint "carts_sales_channel_fk"
|
|
82
|
+
foreign key ("sales_channel_id")
|
|
83
|
+
references "sales_channels" ("id")
|
|
84
|
+
on delete set null;
|
|
85
|
+
`);
|
|
86
|
+
// approved_by_customer_account_id → customer_accounts(id). ON DELETE
|
|
87
|
+
// SET NULL because the approval record still matters even if the
|
|
88
|
+
// Org-Admin's Customer account is later deleted.
|
|
89
|
+
this.addSql(`
|
|
90
|
+
alter table "carts"
|
|
91
|
+
add constraint "carts_approved_by_customer_fk"
|
|
92
|
+
foreign key ("approved_by_customer_account_id")
|
|
93
|
+
references "customer_accounts" ("id")
|
|
94
|
+
on delete set null;
|
|
95
|
+
`);
|
|
96
|
+
// converted_to_quote_request_id → quote_requests(id). ON DELETE
|
|
97
|
+
// SET NULL because deleting a QR (rare) does not invalidate the
|
|
98
|
+
// fact that the cart was once converted.
|
|
99
|
+
this.addSql(`
|
|
100
|
+
alter table "carts"
|
|
101
|
+
add constraint "carts_converted_to_qr_fk"
|
|
102
|
+
foreign key ("converted_to_quote_request_id")
|
|
103
|
+
references "quote_requests" ("id")
|
|
104
|
+
on delete set null;
|
|
105
|
+
`);
|
|
106
|
+
// 5) Backfill `sales_channel_id` to the system-default sales channel
|
|
107
|
+
// so existing rows participate in the new (customer, channel)
|
|
108
|
+
// uniqueness rule. If no default exists (test fixture), leave
|
|
109
|
+
// NULL — the read path treats NULL as "system-default channel"
|
|
110
|
+
// until the next consolidation tightens it (see research.md §R3).
|
|
111
|
+
this.addSql(`
|
|
112
|
+
update "carts"
|
|
113
|
+
set "sales_channel_id" = (
|
|
114
|
+
select "id" from "sales_channels" where "system_default" = true limit 1
|
|
115
|
+
)
|
|
116
|
+
where "sales_channel_id" is null;
|
|
117
|
+
`);
|
|
118
|
+
// 6) Indexes.
|
|
119
|
+
this.addSql(`
|
|
120
|
+
create index "carts_sales_channel_customer_status_idx"
|
|
121
|
+
on "carts" ("sales_channel_id", "customer_account_id", "status");
|
|
122
|
+
`);
|
|
123
|
+
this.addSql(`
|
|
124
|
+
create index "carts_status_last_activity_idx"
|
|
125
|
+
on "carts" ("status", "last_activity_at");
|
|
126
|
+
`);
|
|
127
|
+
this.addSql(`
|
|
128
|
+
create index "carts_applied_promotion_code_idx"
|
|
129
|
+
on "carts" ("applied_promotion_code")
|
|
130
|
+
where "applied_promotion_code" is not null;
|
|
131
|
+
`);
|
|
132
|
+
this.addSql(`
|
|
133
|
+
create index "carts_org_status_updated_idx"
|
|
134
|
+
on "carts" ("organization_id", "status", "updated_at" desc);
|
|
135
|
+
`);
|
|
136
|
+
// 7) Replace the foundation's "one active cart per Customer" partial
|
|
137
|
+
// unique index with a (Customer, Sales Channel) variant so the spec's
|
|
138
|
+
// one-active-cart-per-(Customer, Sales Channel) rule is enforced at
|
|
139
|
+
// the DB layer. Anonymous carts (customer_account_id is null) are
|
|
140
|
+
// not constrained by this index — anon ownership is enforced by the
|
|
141
|
+
// existing `anonymous_cart_token` unique index.
|
|
142
|
+
this.addSql(`drop index if exists "carts_one_active_per_customer";`);
|
|
143
|
+
this.addSql(`
|
|
144
|
+
create unique index "carts_one_active_per_customer_per_channel"
|
|
145
|
+
on "carts" ("customer_account_id", "sales_channel_id")
|
|
146
|
+
where "status" = 'active' and "customer_account_id" is not null;
|
|
147
|
+
`);
|
|
148
|
+
// 8) New columns on `cart_items` — two-layer pricing snapshot.
|
|
149
|
+
this.addSql(`
|
|
150
|
+
alter table "cart_items"
|
|
151
|
+
add column "recomputed_unit_price" numeric(12,2) null,
|
|
152
|
+
add column "recomputed_at" timestamptz null,
|
|
153
|
+
add column "recomputed_currency" varchar(3) null;
|
|
154
|
+
`);
|
|
155
|
+
// 9) New column on `organizations` — per-org cart-approval policy.
|
|
156
|
+
this.addSql(`
|
|
157
|
+
alter table "organizations"
|
|
158
|
+
add column "requires_cart_approval" boolean not null default false;
|
|
159
|
+
`);
|
|
160
|
+
// 10) Cart audit feed.
|
|
161
|
+
this.addSql(`
|
|
162
|
+
create table "cart_audit_entries" (
|
|
163
|
+
"id" uuid not null,
|
|
164
|
+
"cart_id" uuid not null,
|
|
165
|
+
"occurred_at" timestamptz not null default now(),
|
|
166
|
+
"actor_type" varchar(32) not null,
|
|
167
|
+
"actor_id" uuid null,
|
|
168
|
+
"action" varchar(64) not null,
|
|
169
|
+
"from_state" varchar(32) null,
|
|
170
|
+
"to_state" varchar(32) null,
|
|
171
|
+
"reason" text null,
|
|
172
|
+
"metadata" jsonb not null default '{}'::jsonb,
|
|
173
|
+
constraint "cart_audit_entries_pkey" primary key ("id"),
|
|
174
|
+
constraint "cart_audit_entries_cart_fk"
|
|
175
|
+
foreign key ("cart_id") references "carts" ("id") on delete cascade,
|
|
176
|
+
constraint "cart_audit_entries_actor_type_check"
|
|
177
|
+
check ("actor_type" in ('customer', 'org_admin', 'platform_admin', 'system', 'sweep'))
|
|
178
|
+
);
|
|
179
|
+
`);
|
|
180
|
+
this.addSql(`
|
|
181
|
+
create index "cart_audit_entries_cart_occurred_idx"
|
|
182
|
+
on "cart_audit_entries" ("cart_id", "occurred_at" desc);
|
|
183
|
+
`);
|
|
184
|
+
}
|
|
185
|
+
async down() {
|
|
186
|
+
// Mirror order: drop the cart audit feed first, then the organization
|
|
187
|
+
// column, then cart_items columns, then re-instate the foundation
|
|
188
|
+
// partial unique index, then drop the new carts columns / constraints
|
|
189
|
+
// / indexes.
|
|
190
|
+
this.addSql(`drop index if exists "cart_audit_entries_cart_occurred_idx";`);
|
|
191
|
+
this.addSql(`drop table if exists "cart_audit_entries" cascade;`);
|
|
192
|
+
this.addSql(`alter table "organizations" drop column if exists "requires_cart_approval";`);
|
|
193
|
+
this.addSql(`
|
|
194
|
+
alter table "cart_items"
|
|
195
|
+
drop column if exists "recomputed_unit_price",
|
|
196
|
+
drop column if exists "recomputed_at",
|
|
197
|
+
drop column if exists "recomputed_currency";
|
|
198
|
+
`);
|
|
199
|
+
// Restore foundation's partial unique index.
|
|
200
|
+
this.addSql(`drop index if exists "carts_one_active_per_customer_per_channel";`);
|
|
201
|
+
this.addSql(`
|
|
202
|
+
create unique index "carts_one_active_per_customer"
|
|
203
|
+
on "carts" ("customer_account_id")
|
|
204
|
+
where "status" = 'active' and "customer_account_id" is not null;
|
|
205
|
+
`);
|
|
206
|
+
this.addSql(`drop index if exists "carts_org_status_updated_idx";`);
|
|
207
|
+
this.addSql(`drop index if exists "carts_applied_promotion_code_idx";`);
|
|
208
|
+
this.addSql(`drop index if exists "carts_status_last_activity_idx";`);
|
|
209
|
+
this.addSql(`drop index if exists "carts_sales_channel_customer_status_idx";`);
|
|
210
|
+
this.addSql(`alter table "carts" drop constraint if exists "carts_converted_to_qr_fk";`);
|
|
211
|
+
this.addSql(`alter table "carts" drop constraint if exists "carts_approved_by_customer_fk";`);
|
|
212
|
+
this.addSql(`alter table "carts" drop constraint if exists "carts_sales_channel_fk";`);
|
|
213
|
+
this.addSql(`alter table "carts" drop constraint if exists "carts_approval_status_check";`);
|
|
214
|
+
this.addSql(`alter table "carts" drop constraint if exists "carts_status_check";`);
|
|
215
|
+
this.addSql(`
|
|
216
|
+
alter table "carts"
|
|
217
|
+
drop column if exists "sales_channel_id",
|
|
218
|
+
drop column if exists "last_activity_at",
|
|
219
|
+
drop column if exists "approval_status",
|
|
220
|
+
drop column if exists "submitted_for_approval_at",
|
|
221
|
+
drop column if exists "approved_at",
|
|
222
|
+
drop column if exists "approved_by_customer_account_id",
|
|
223
|
+
drop column if exists "rejected_at",
|
|
224
|
+
drop column if exists "rejected_by_actor",
|
|
225
|
+
drop column if exists "rejected_reason",
|
|
226
|
+
drop column if exists "applied_promotion_code",
|
|
227
|
+
drop column if exists "converted_to_quote_request_id",
|
|
228
|
+
drop column if exists "abandonment_notified_at",
|
|
229
|
+
drop column if exists "version";
|
|
230
|
+
`);
|
|
231
|
+
// Restore legacy 'converted' state for any rows the up() touched.
|
|
232
|
+
// (We cannot perfectly distinguish "converted at the time of migration"
|
|
233
|
+
// from "completed later", so the down() leaves them as 'completed';
|
|
234
|
+
// this is documented as accepted asymmetry.)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=20260611T140352_carts_consolidation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20260611T140352_carts_consolidation.js","sourceRoot":"","sources":["../../src/migrations/20260611T140352_carts_consolidation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
/**
|
|
3
|
+
* Feature 043 — packaging-unit snapshot on cart lines. When a buyer orders a
|
|
4
|
+
* packaging unit (e.g. a pallet), the line records which unit it came from so
|
|
5
|
+
* the cart/order/RFQ can display the unit name appended to the product name.
|
|
6
|
+
* All nullable — plain single-piece lines leave them empty.
|
|
7
|
+
*/
|
|
8
|
+
export declare class Migration20260611T140413CartsCartItemPackaging extends Migration {
|
|
9
|
+
up(): Promise<void>;
|
|
10
|
+
down(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=20260611T140413_carts_cart_item_packaging.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20260611T140413_carts_cart_item_packaging.d.ts","sourceRoot":"","sources":["../../src/migrations/20260611T140413_carts_cart_item_packaging.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;GAKG;AACH,qBAAa,8CAA+C,SAAQ,SAAS;IAC5D,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC;IAMnB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAKrC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
/**
|
|
3
|
+
* Feature 043 — packaging-unit snapshot on cart lines. When a buyer orders a
|
|
4
|
+
* packaging unit (e.g. a pallet), the line records which unit it came from so
|
|
5
|
+
* the cart/order/RFQ can display the unit name appended to the product name.
|
|
6
|
+
* All nullable — plain single-piece lines leave them empty.
|
|
7
|
+
*/
|
|
8
|
+
export class Migration20260611T140413CartsCartItemPackaging extends Migration {
|
|
9
|
+
async up() {
|
|
10
|
+
this.addSql('alter table "cart_items" add column "packaging_unit_id" uuid null;');
|
|
11
|
+
this.addSql('alter table "cart_items" add column "packaging_unit_name" varchar(160) null;');
|
|
12
|
+
this.addSql('alter table "cart_items" add column "packaging_unit_base_quantity" integer null;');
|
|
13
|
+
}
|
|
14
|
+
async down() {
|
|
15
|
+
this.addSql('alter table "cart_items" drop column "packaging_unit_base_quantity";');
|
|
16
|
+
this.addSql('alter table "cart_items" drop column "packaging_unit_name";');
|
|
17
|
+
this.addSql('alter table "cart_items" drop column "packaging_unit_id";');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=20260611T140413_carts_cart_item_packaging.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20260611T140413_carts_cart_item_packaging.js","sourceRoot":"","sources":["../../src/migrations/20260611T140413_carts_cart_item_packaging.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;GAKG;AACH,MAAM,OAAO,8CAA+C,SAAQ,SAAS;IAClE,KAAK,CAAC,EAAE;QACf,IAAI,CAAC,MAAM,CAAC,oEAAoE,CAAC,CAAC;QAClF,IAAI,CAAC,MAAM,CAAC,8EAA8E,CAAC,CAAC;QAC5F,IAAI,CAAC,MAAM,CAAC,kFAAkF,CAAC,CAAC;IAClG,CAAC;IAEQ,KAAK,CAAC,IAAI;QACjB,IAAI,CAAC,MAAM,CAAC,sEAAsE,CAAC,CAAC;QACpF,IAAI,CAAC,MAAM,CAAC,6DAA6D,CAAC,CAAC;QAC3E,IAAI,CAAC,MAAM,CAAC,2DAA2D,CAAC,CAAC;IAC3E,CAAC;CACF"}
|
|
@@ -0,0 +1,50 @@
|
|
|
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 declare class Migration20260818T081253CartsCartCompletedOrderFk extends Migration {
|
|
47
|
+
up(): Promise<void>;
|
|
48
|
+
down(): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=20260818T081253_carts_cart_completed_order_fk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20260818T081253_carts_cart_completed_order_fk.d.ts","sourceRoot":"","sources":["../../src/migrations/20260818T081253_carts_cart_completed_order_fk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,qBAAa,iDAAkD,SAAQ,SAAS;IAC/D,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC;IAYnB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAKrC"}
|