@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,556 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { ANONYMOUS_PRODUCT_AUDIENCE, ERROR_CODES, isProductVisibleTo, } from '@endora-commerce/contracts';
3
+ import { HttpError } from '@endora-commerce/platform/http';
4
+ import { Cart } from '../entities/cart.entity.js';
5
+ import { CartItem } from '../entities/cart-item.entity.js';
6
+ import { SalesChannel } from '@endora-commerce/platform/kernel';
7
+ import { outOfRequestChannel } from '@endora-commerce/platform/kernel';
8
+ /**
9
+ * Cart-line hard cap (feature 027 FR-001 / spec edge case "Cart contents
10
+ * cap"). The 201st distinct line is refused with HTTP 422.
11
+ */
12
+ export const CART_MAX_LINES = 200;
13
+ export class CartService {
14
+ emFactory;
15
+ pricingService;
16
+ catalogProducts;
17
+ organizations;
18
+ channelMembership;
19
+ approvalService;
20
+ auditService;
21
+ recomputeCache;
22
+ /**
23
+ * `pricingService` is **required** since issue #124. It used to be optional
24
+ * "for test rigs", and the cart then read the legacy
25
+ * `attributeValues['defaultPrice']` whenever it was missing — so a rig, a
26
+ * wiring slip and `price_lists` switched off all produced the same thing: a
27
+ * cart line priced from a catalogue attribute that no price list supports.
28
+ * Absence is now a `lazyPort` gate that throws `MODULE_DISABLED` at the call,
29
+ * which nothing downstream can mistake for a price.
30
+ *
31
+ * Cart entities don't currently track which sales channel they were
32
+ * created on (multi-channel cart attribution is a separate
33
+ * follow-up), so the resolver runs against the system-default
34
+ * channel — matching the behaviour the foundation cart already had.
35
+ * Lists with a `salesChannel` criterion targeting other channels
36
+ * therefore won't apply on the cart line until cart-side channel
37
+ * tracking lands.
38
+ */
39
+ /**
40
+ * `approvalService` and `auditService` are optional — when wired
41
+ * (production composition), any buyer-driven mutation on an
42
+ * `approved` cart re-arms approval to `pending` and a typed audit
43
+ * row is landed (line_added / line_qty_changed / line_removed).
44
+ * Foundation tests and legacy test rigs that don't wire either still
45
+ * construct the service without them; both hooks no-op when absent.
46
+ */
47
+ constructor(emFactory,
48
+ /** `price_lists`' line-resolution slice (feature 075, Phase C). */
49
+ pricingService,
50
+ /** `catalog`'s product read model — products, variants, packaging units. */
51
+ catalogProducts,
52
+ /** `organizations`' read model — the buying org the price resolves against. */
53
+ organizations,
54
+ /**
55
+ * The sanctioned bridge accessor (Constitution XII), for the assortment
56
+ * gate `addItem` owes (issue #259). Required, like `catalogProducts`: an
57
+ * optional one would make "this rig did not wire it" and "this channel
58
+ * publishes the product" the same answer, which is the fail-open shape the
59
+ * whole issue is about.
60
+ */
61
+ channelMembership, approvalService, auditService, recomputeCache) {
62
+ this.emFactory = emFactory;
63
+ this.pricingService = pricingService;
64
+ this.catalogProducts = catalogProducts;
65
+ this.organizations = organizations;
66
+ this.channelMembership = channelMembership;
67
+ this.approvalService = approvalService;
68
+ this.auditService = auditService;
69
+ this.recomputeCache = recomputeCache;
70
+ }
71
+ /** Invalidate the per-cart price-recompute cache on every cart-side write. */
72
+ async #invalidateRecomputeCache(cartId) {
73
+ if (!this.recomputeCache)
74
+ return;
75
+ try {
76
+ await this.recomputeCache.invalidate(cartId);
77
+ }
78
+ catch {
79
+ // Cache invalidation is best-effort — never block a write.
80
+ }
81
+ }
82
+ async getOrCreateForCustomer(ctx) {
83
+ const em = this.emFactory();
84
+ return this.#getOrCreateForCustomerOn(em, ctx);
85
+ }
86
+ async #getOrCreateForCustomerOn(em, ctx) {
87
+ let cart = await em.findOne(Cart, {
88
+ customerAccountId: ctx.customerAccountId,
89
+ status: 'active',
90
+ });
91
+ if (!cart) {
92
+ cart = em.create(Cart, {
93
+ customerAccountId: ctx.customerAccountId,
94
+ organizationId: ctx.organizationId,
95
+ status: 'active',
96
+ salesChannelId: await this.#defaultSalesChannelId(em),
97
+ });
98
+ await em.persistAndFlush(cart);
99
+ }
100
+ return cart;
101
+ }
102
+ async getOrCreateForAnon(em, token) {
103
+ let cart = await em.findOne(Cart, { anonymousCartToken: token, status: 'active' });
104
+ if (!cart) {
105
+ cart = em.create(Cart, {
106
+ anonymousCartToken: token,
107
+ status: 'active',
108
+ salesChannelId: await this.#defaultSalesChannelId(em),
109
+ });
110
+ await em.persistAndFlush(cart);
111
+ }
112
+ return cart;
113
+ }
114
+ /**
115
+ * Feature 052 — a cart MUST carry a resolved sales channel so promotion and
116
+ * pricing evaluation is channel-scoped (the promotion channel gate fails closed
117
+ * on a null-channel cart, FR-005). Until request-driven channel resolution is
118
+ * unified (spec 03), a new cart defaults to the system-default channel. Returns
119
+ * `null` only if no system-default channel exists (a misconfigured store).
120
+ */
121
+ async #defaultSalesChannelId(em) {
122
+ const channel = await em.findOne(SalesChannel, { systemDefault: true }, { fields: ['id'] });
123
+ return channel?.id ?? null;
124
+ }
125
+ async getItems(cartId) {
126
+ const em = this.emFactory();
127
+ return em.find(CartItem, { cartId });
128
+ }
129
+ async addItem(actor, input) {
130
+ const em = this.emFactory();
131
+ const cart = actor.customer
132
+ ? await this.#getOrCreateForCustomerOn(em, actor.customer)
133
+ : actor.anonymousToken
134
+ ? await this.getOrCreateForAnon(em, actor.anonymousToken)
135
+ : (() => {
136
+ throw new HttpError(401, ERROR_CODES.UNAUTHORIZED, 'Cart requires either a customer session or an anonymous cart token.');
137
+ })();
138
+ if (input.quantity <= 0) {
139
+ throw new HttpError(422, ERROR_CODES.VALIDATION_FAILED, 'Quantity must be > 0.');
140
+ }
141
+ const product = await this.catalogProducts.findById(input.productId);
142
+ // Issue #227 — a product this shopper may not see is a product they may not
143
+ // put in a cart. The read port is the row-level one and applies no policy of
144
+ // its own, deliberately: an order line has to resolve its product long after
145
+ // the operator restricted it. That makes enforcement the caller's, and this
146
+ // caller is holding a `productId` the buyer chose.
147
+ //
148
+ // 404 and not 403, matching the product detail: a caller who may not see the
149
+ // row may not learn it exists, and the two answers are indistinguishable to
150
+ // an honest client because they are the same answer.
151
+ if (!product || !isProductVisibleTo(product, cartAudience(actor))) {
152
+ throw new HttpError(404, ERROR_CODES.PRODUCT_NOT_FOUND, 'Product not found.');
153
+ }
154
+ // Issue #259 — the second filter the predicate above says it is not. The
155
+ // channel is a property of the request, so it is read off the request scope
156
+ // and never re-resolved here; outside a request there is no channel to be
157
+ // out of and `outOfRequestChannel` answers `false`.
158
+ //
159
+ // The same 404 as the two lines above, deliberately: "sold on another
160
+ // channel", "restricted to another organisation" and "does not exist" have
161
+ // to be one answer, or the pair of them is an enumeration oracle over an
162
+ // operator's private assortment — the defect issue #174 found in the
163
+ // type-ahead next door.
164
+ if (await outOfRequestChannel(this.channelMembership, product.id)) {
165
+ throw new HttpError(404, ERROR_CODES.PRODUCT_NOT_FOUND, 'Product not found.');
166
+ }
167
+ // Feature 043 — ordering by a packaging unit (e.g. a pallet): the line is
168
+ // measured in base pieces (`baseQuantity × units`) and snapshots the unit
169
+ // name so the cart/order/RFQ can append it to the product name.
170
+ //
171
+ // `findPackagingUnitInProduct` is the two-argument form of the same read:
172
+ // the "does this unit belong to this product?" test was the second half of
173
+ // the `where` clause and stays inside one query.
174
+ let packagingUnit = null;
175
+ if (input.packagingUnitId) {
176
+ packagingUnit = await this.catalogProducts.findPackagingUnitInProduct(product.id, input.packagingUnitId);
177
+ if (!packagingUnit) {
178
+ throw new HttpError(404, ERROR_CODES.PACKAGING_UNIT_NOT_FOUND, 'Packaging unit not found for this product.');
179
+ }
180
+ }
181
+ const effectiveQuantity = packagingUnit
182
+ ? packagingUnit.baseQuantity * input.quantity
183
+ : input.quantity;
184
+ // If the same product+variant+packaging-unit is already in the cart, bump
185
+ // quantity. A packaging-unit line and a single-piece line of the same
186
+ // product stay distinct (different `packagingUnitId`).
187
+ const existing = await em.findOne(CartItem, {
188
+ cartId: cart.id,
189
+ productId: product.id,
190
+ variantId: input.variantId ?? null,
191
+ packagingUnitId: input.packagingUnitId ?? null,
192
+ });
193
+ if (existing) {
194
+ existing.quantity += effectiveQuantity;
195
+ cart.lastActivityAt = new Date();
196
+ await em.flush();
197
+ }
198
+ else {
199
+ // Feature 027 FR-001: enforce the 200-line cap on the 201st distinct
200
+ // (productId, variantId) pair. Foundation duplicate-summation above
201
+ // does not trigger the cap.
202
+ const currentLineCount = await em.count(CartItem, { cartId: cart.id });
203
+ if (currentLineCount >= CART_MAX_LINES) {
204
+ throw new HttpError(422, ERROR_CODES.CART_LINE_CAP_EXCEEDED, 'cart_line_cap_exceeded');
205
+ }
206
+ const resolved = await this.#resolveLineUnitPrice(em, {
207
+ product,
208
+ organizationId: actor.customer?.organizationId ?? null,
209
+ quantity: effectiveQuantity,
210
+ variantId: input.variantId ?? null,
211
+ });
212
+ // T084 — defence-in-depth: refuse the line when the resolver says
213
+ // this product is quote-only for the (org, channel) tuple.
214
+ if (resolved && resolved.displayMode === 'none') {
215
+ throw new HttpError(400, ERROR_CODES.VALIDATION_FAILED, 'product_quote_only');
216
+ }
217
+ const item = em.create(CartItem, {
218
+ cartId: cart.id,
219
+ productId: product.id,
220
+ ...(input.variantId ? { variantId: input.variantId } : {}),
221
+ ...(packagingUnit
222
+ ? {
223
+ packagingUnitId: packagingUnit.id,
224
+ packagingUnitName: packagingUnit.name,
225
+ packagingUnitBaseQuantity: packagingUnit.baseQuantity,
226
+ }
227
+ : {}),
228
+ quantity: effectiveQuantity,
229
+ // `resolved === null` is `price_lists` answering "no list applies to
230
+ // this line"; the catalogue attribute is the legacy stand-in for that
231
+ // case and is reported as remaining debt (issue #124). What can no
232
+ // longer land here is an *absent* `price_lists`: resolving the port
233
+ // throws `MODULE_DISABLED` before this expression runs.
234
+ unitPrice: resolved
235
+ ? Number(resolved.amount).toFixed(2)
236
+ : (Number(product.attributeValues['defaultPrice'] ??
237
+ product.attributeValues['price'] ??
238
+ 0)).toFixed(2),
239
+ currency: resolved?.currency ?? 'PLN',
240
+ });
241
+ em.persist(item);
242
+ cart.lastActivityAt = new Date();
243
+ await em.flush();
244
+ }
245
+ if (this.approvalService && actor.customer) {
246
+ await this.approvalService.maybeReArm(cart, {
247
+ customerAccountId: actor.customer.customerAccountId,
248
+ });
249
+ }
250
+ if (this.auditService) {
251
+ await this.auditService.record({
252
+ cartId: cart.id,
253
+ actorType: actor.customer ? 'customer' : 'system',
254
+ ...(actor.customer ? { actorId: actor.customer.customerAccountId } : {}),
255
+ action: 'line_added',
256
+ metadata: {
257
+ productId: input.productId,
258
+ ...(input.variantId ? { variantId: input.variantId } : {}),
259
+ quantity: input.quantity,
260
+ },
261
+ });
262
+ }
263
+ await this.#invalidateRecomputeCache(cart.id);
264
+ const items = await em.find(CartItem, { cartId: cart.id });
265
+ return { cart, items };
266
+ }
267
+ /**
268
+ * Look up the line's unit price via the resolver.
269
+ *
270
+ * `null` means "the price-list engine answered, and nothing applies" —
271
+ * `resolveLinePrice`'s own documented answer — so there is no `catch` here
272
+ * (issue #84). There used to be one, and it made the two answers
273
+ * indistinguishable: a `price_lists` switched off, or a resolver bug, both
274
+ * came back as "this line has no price", and the cart quietly re-priced from
275
+ * the catalogue default. An absent `price_lists` now throws through this
276
+ * method to the route (issue #124).
277
+ */
278
+ async #resolveLineUnitPrice(em, input) {
279
+ const channel = await em.findOne(SalesChannel, { systemDefault: true });
280
+ if (!channel)
281
+ return null;
282
+ const organization = input.organizationId
283
+ ? await this.organizations.findById(input.organizationId)
284
+ : null;
285
+ const resolved = await this.pricingService.resolveLinePrice({
286
+ product: input.product,
287
+ variantId: input.variantId,
288
+ context: {
289
+ quantity: input.quantity,
290
+ ...(organization ? { organization } : {}),
291
+ salesChannel: channel,
292
+ },
293
+ });
294
+ if (!resolved)
295
+ return null;
296
+ return {
297
+ amount: resolved.amount,
298
+ currency: resolved.currency,
299
+ priceListId: resolved.priceListId,
300
+ displayMode: resolved.displayMode,
301
+ };
302
+ }
303
+ async updateItem(actor, itemId, quantity) {
304
+ const em = this.emFactory();
305
+ const item = await em.findOne(CartItem, { id: itemId });
306
+ if (!item)
307
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Cart item not found.');
308
+ const cart = await em.findOne(Cart, { id: item.cartId });
309
+ if (!cart)
310
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Cart not found.');
311
+ this.#assertOwnership(cart, actor);
312
+ const oldQuantity = item.quantity;
313
+ if (quantity <= 0) {
314
+ em.remove(item);
315
+ }
316
+ else {
317
+ item.quantity = quantity;
318
+ }
319
+ cart.lastActivityAt = new Date();
320
+ await em.flush();
321
+ if (this.approvalService && actor.customer) {
322
+ await this.approvalService.maybeReArm(cart, {
323
+ customerAccountId: actor.customer.customerAccountId,
324
+ });
325
+ }
326
+ if (this.auditService) {
327
+ await this.auditService.record({
328
+ cartId: cart.id,
329
+ actorType: actor.customer ? 'customer' : 'system',
330
+ ...(actor.customer ? { actorId: actor.customer.customerAccountId } : {}),
331
+ action: quantity <= 0 ? 'line_removed' : 'line_qty_changed',
332
+ metadata: {
333
+ productId: item.productId,
334
+ ...(item.variantId ? { variantId: item.variantId } : {}),
335
+ oldQuantity,
336
+ newQuantity: quantity,
337
+ },
338
+ });
339
+ }
340
+ await this.#invalidateRecomputeCache(cart.id);
341
+ const items = await em.find(CartItem, { cartId: cart.id });
342
+ return { cart, items };
343
+ }
344
+ async removeItem(actor, itemId) {
345
+ const em = this.emFactory();
346
+ const item = await em.findOne(CartItem, { id: itemId });
347
+ if (!item)
348
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Cart item not found.');
349
+ const cart = await em.findOne(Cart, { id: item.cartId });
350
+ if (!cart)
351
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Cart not found.');
352
+ this.#assertOwnership(cart, actor);
353
+ const removedProductId = item.productId;
354
+ const removedVariantId = item.variantId ?? null;
355
+ const removedQuantity = item.quantity;
356
+ em.remove(item);
357
+ cart.lastActivityAt = new Date();
358
+ await em.flush();
359
+ if (this.approvalService && actor.customer) {
360
+ await this.approvalService.maybeReArm(cart, {
361
+ customerAccountId: actor.customer.customerAccountId,
362
+ });
363
+ }
364
+ if (this.auditService) {
365
+ await this.auditService.record({
366
+ cartId: cart.id,
367
+ actorType: actor.customer ? 'customer' : 'system',
368
+ ...(actor.customer ? { actorId: actor.customer.customerAccountId } : {}),
369
+ action: 'line_removed',
370
+ metadata: {
371
+ productId: removedProductId,
372
+ ...(removedVariantId ? { variantId: removedVariantId } : {}),
373
+ quantity: removedQuantity,
374
+ },
375
+ });
376
+ }
377
+ await this.#invalidateRecomputeCache(cart.id);
378
+ const items = await em.find(CartItem, { cartId: cart.id });
379
+ return { cart, items };
380
+ }
381
+ /**
382
+ * Explicit "this buyer just opened the cart page" ping (feature 027 §R4 /
383
+ * FR-032). Bumps `last_activity_at` and, if the cart had transitioned to
384
+ * `abandoned`, brings it back to `active` and clears
385
+ * `abandonment_notified_at` so the next abandonment cycle can notify
386
+ * again. No-op on a missing cart.
387
+ */
388
+ async touch(actor) {
389
+ // command-coverage-ignore: bumps the ephemeral cart's lastActivityAt for the
390
+ // abandonment sweep — transient bookkeeping, not an audited domain mutation.
391
+ const em = this.emFactory();
392
+ let cart = null;
393
+ if (actor.customer) {
394
+ cart = await em.findOne(Cart, {
395
+ customerAccountId: actor.customer.customerAccountId,
396
+ status: { $in: ['active', 'abandoned'] },
397
+ });
398
+ }
399
+ else if (actor.anonymousToken) {
400
+ cart = await em.findOne(Cart, {
401
+ anonymousCartToken: actor.anonymousToken,
402
+ status: { $in: ['active', 'abandoned'] },
403
+ });
404
+ }
405
+ if (!cart)
406
+ return null;
407
+ cart.lastActivityAt = new Date();
408
+ if (cart.status === 'abandoned') {
409
+ cart.status = 'active';
410
+ cart.abandonmentNotifiedAt = null;
411
+ }
412
+ await em.flush();
413
+ return cart;
414
+ }
415
+ /**
416
+ * Merge an anonymous cart's items into the customer's cart at login
417
+ * time (feature 037-cart-merge-on-login).
418
+ *
419
+ * Returns a `CartMergeOutcome` diagnostic the caller (the login route
420
+ * via the `onLogin` hook) uses to surface the result to the storefront
421
+ * and to decide whether to clear the stale `b2b_cart_anon` cookie.
422
+ * See specs/037-cart-merge-on-login/contracts/cart-merge.md § 3.
423
+ *
424
+ * State matrix:
425
+ * - `adopted` — the customer had no active cart; the anon
426
+ * cart's lines were placed onto a freshly-created destination.
427
+ * - `merged` — the customer already had an active cart and
428
+ * ≥ 1 anon line was moved or summed into it.
429
+ * - `noop_empty` — the anon cart was found but carried no items;
430
+ * the source row is still flipped to `completed` + detokenised
431
+ * (cleanup), no audit row is written.
432
+ * - `noop_no_anon` — no cart for the supplied token; no DB change.
433
+ *
434
+ * Coupons on the source cart are NOT carried to the destination
435
+ * (R-05 / FR-010 — the destination's existing `appliedPromotionCode`
436
+ * is preserved verbatim).
437
+ */
438
+ async mergeAnonymousIntoCustomer(anonymousToken, ctx) {
439
+ const em = this.emFactory();
440
+ // Look up the source first so the noop_no_anon branch can skip the
441
+ // destination-cart creation. Per the spec, the diagnostic still
442
+ // includes a destinationCartId so the storefront has a stable id to
443
+ // re-read its cart from on next render.
444
+ const anon = await em.findOne(Cart, { anonymousCartToken: anonymousToken, status: 'active' });
445
+ if (!anon) {
446
+ const destForNoop = await this.#getOrCreateForCustomerOn(em, ctx);
447
+ return {
448
+ outcome: 'noop_no_anon',
449
+ movedLineCount: 0,
450
+ summedLineCount: 0,
451
+ destinationCartId: destForNoop.id,
452
+ };
453
+ }
454
+ // Detect "the customer's destination cart was newly created right now":
455
+ // peek for an existing active cart row BEFORE calling the get-or-create
456
+ // helper. If absent and the source has items, we will return `adopted`.
457
+ const preExistingDest = await em.findOne(Cart, {
458
+ customerAccountId: ctx.customerAccountId,
459
+ status: 'active',
460
+ });
461
+ const customerCart = preExistingDest ?? (await this.#getOrCreateForCustomerOn(em, ctx));
462
+ let movedLineCount = 0;
463
+ let summedLineCount = 0;
464
+ const anonItems = await em.find(CartItem, { cartId: anon.id });
465
+ for (const item of anonItems) {
466
+ const existing = await em.findOne(CartItem, {
467
+ cartId: customerCart.id,
468
+ productId: item.productId,
469
+ variantId: item.variantId ?? null,
470
+ });
471
+ if (existing) {
472
+ existing.quantity += item.quantity;
473
+ summedLineCount += 1;
474
+ }
475
+ else {
476
+ item.cartId = customerCart.id;
477
+ movedLineCount += 1;
478
+ }
479
+ }
480
+ // Merge complete — mark the source anonymous cart as `completed` even
481
+ // when the merge was a noop_empty (it never produced anything down-
482
+ // stream, but leaving it `active` would let the abandonment sweep
483
+ // pick it up later, and leaving the token live would let a second
484
+ // login try to re-merge an already-consumed source). Treating it as
485
+ // `abandoned` would falsely flag it for the abandonment-notification
486
+ // sweep. See feature 027 §R10.
487
+ anon.status = 'completed';
488
+ anon.anonymousCartToken = null;
489
+ const isNoopEmpty = movedLineCount + summedLineCount === 0;
490
+ const outcome = isNoopEmpty
491
+ ? 'noop_empty'
492
+ : preExistingDest
493
+ ? 'merged'
494
+ : 'adopted';
495
+ // Audit only on observable state-changes on the destination cart
496
+ // (R-08). noop_empty and noop_no_anon are not destination-state
497
+ // changes and are observable in the application log instead.
498
+ if ((outcome === 'adopted' || outcome === 'merged') && this.auditService) {
499
+ const sourceAnonTokenHash = createHash('sha256').update(anonymousToken).digest('hex');
500
+ await this.auditService.record({
501
+ cartId: customerCart.id,
502
+ actorType: 'system',
503
+ action: 'cart_merged_from_anon',
504
+ metadata: {
505
+ sourceAnonTokenHash,
506
+ outcome,
507
+ movedLineCount,
508
+ summedLineCount,
509
+ },
510
+ });
511
+ }
512
+ await em.flush();
513
+ return {
514
+ outcome,
515
+ movedLineCount,
516
+ summedLineCount,
517
+ destinationCartId: customerCart.id,
518
+ };
519
+ }
520
+ async clearForCustomer(ctx) {
521
+ // command-coverage-ignore: empties the ephemeral cart — transient pre-order
522
+ // working state; the resulting order captures the audited final state.
523
+ const em = this.emFactory();
524
+ const cart = await em.findOne(Cart, {
525
+ customerAccountId: ctx.customerAccountId,
526
+ status: 'active',
527
+ });
528
+ if (!cart)
529
+ return;
530
+ await em.nativeDelete(CartItem, { cartId: cart.id });
531
+ cart.status = 'completed';
532
+ await em.flush();
533
+ }
534
+ #assertOwnership(cart, actor) {
535
+ if (actor.customer && cart.customerAccountId === actor.customer.customerAccountId)
536
+ return;
537
+ if (actor.anonymousToken && cart.anonymousCartToken === actor.anonymousToken)
538
+ return;
539
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Cart not found.');
540
+ }
541
+ }
542
+ /**
543
+ * The {@link ProductAudience} a cart operation speaks for (issue #227).
544
+ *
545
+ * An anonymous cart is the anonymous audience — the token in the cookie
546
+ * identifies a basket, not a buyer, and it is minted by asking for one. A
547
+ * signed-in shopper carries their Organization, `null` included: the guest-style
548
+ * accounts of feature 026 have none, and they are exactly the caller
549
+ * `logged_in_only` distinguishes from the public.
550
+ */
551
+ function cartAudience(actor) {
552
+ if (!actor.customer)
553
+ return ANONYMOUS_PRODUCT_AUDIENCE;
554
+ return { organizationId: actor.customer.organizationId, authenticated: true };
555
+ }
556
+ //# sourceMappingURL=cart-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cart-service.js","sourceRoot":"","sources":["../../../src/backend/services/cart-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import type { CartStatus, CartApprovalStatus } from '../entities/cart.entity.js';
2
+ /**
3
+ * Pure state-machine helpers for the carts module (feature 027 US4).
4
+ *
5
+ * The cart has two orthogonal axes:
6
+ * - main `status`: active | abandoned | completed | rejected
7
+ * - `approval_status`: not_required | pending | approved
8
+ * | rejected_by_org_admin
9
+ *
10
+ * Production transitions are implemented by `CartService`,
11
+ * `CartApprovalService`, and the abandonment worker — all DB-coupled.
12
+ * This module exposes the same invariants as pure functions so the
13
+ * matrix can be unit-tested (feature 027 T084 / T085) and so callers
14
+ * (route handlers, audit-row builders) can reach decisions without
15
+ * duplicating the inline branching.
16
+ */
17
+ export type ApprovalEvent = 'submit' | 'approve' | 'reject' | 'buyer_mutation' | 'policy_off' | 'policy_on';
18
+ export type ApprovalTransitionFailure = 'already_terminal' | 'already_initiated' | 'not_pending' | 'not_applicable';
19
+ export type ApprovalTransitionResult = {
20
+ ok: true;
21
+ next: CartApprovalStatus;
22
+ } | {
23
+ ok: false;
24
+ reason: ApprovalTransitionFailure;
25
+ };
26
+ /**
27
+ * Returns the post-event approval state for a cart whose current
28
+ * approval state is `from`. Pre-flight checks (policy on, cart not
29
+ * empty, buyer-not-org-admin, etc.) live in the service layer; this
30
+ * function captures only the FSM legality of (state, event) pairs.
31
+ */
32
+ export declare function nextApprovalState(from: CartApprovalStatus, event: ApprovalEvent): ApprovalTransitionResult;
33
+ /**
34
+ * Primary CTA the storefront should render. Pure function over the
35
+ * cart's two-axis state. The route handler caches this on every cart
36
+ * read; see `routes.ts.serializeCart`.
37
+ */
38
+ export type PrimaryCta = 'checkout' | 'submit_for_approval' | 'awaiting_approval' | 'blocked_by_organization';
39
+ export declare function derivePrimaryCta(input: {
40
+ status: CartStatus;
41
+ approvalStatus: CartApprovalStatus;
42
+ requiresApproval?: boolean;
43
+ }): PrimaryCta;
44
+ /**
45
+ * `true` when an `approved` cart should be silently re-armed to
46
+ * `pending` because the buyer just mutated it. Lives here so the
47
+ * various write paths (add / update / remove / coupon apply / coupon
48
+ * clear) all agree on the same rule.
49
+ */
50
+ export declare function shouldReArmOnBuyerMutation(approvalStatus: CartApprovalStatus): boolean;
51
+ //# sourceMappingURL=cart-state-machine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cart-state-machine.d.ts","sourceRoot":"","sources":["../../../src/backend/services/cart-state-machine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAEjF;;;;;;;;;;;;;;GAcG;AAEH,MAAM,MAAM,aAAa,GACrB,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,gBAAgB,GAChB,YAAY,GACZ,WAAW,CAAC;AAEhB,MAAM,MAAM,yBAAyB,GACjC,kBAAkB,GAClB,mBAAmB,GACnB,aAAa,GACb,gBAAgB,CAAC;AAErB,MAAM,MAAM,wBAAwB,GAChC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,kBAAkB,CAAA;CAAE,GACtC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,yBAAyB,CAAA;CAAE,CAAC;AAErD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,kBAAkB,EACxB,KAAK,EAAE,aAAa,GACnB,wBAAwB,CA0C1B;AAED;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAClB,UAAU,GACV,qBAAqB,GACrB,mBAAmB,GACnB,yBAAyB,CAAC;AAE9B,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,MAAM,EAAE,UAAU,CAAC;IACnB,cAAc,EAAE,kBAAkB,CAAC;IACnC,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,GAAG,UAAU,CAeb;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,cAAc,EAAE,kBAAkB,GACjC,OAAO,CAET"}