@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,175 @@
1
+ import { ERROR_CODES } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ import { Cart } from '../entities/cart.entity.js';
4
+ import { CartItem } from '../entities/cart-item.entity.js';
5
+ import { rethrowIfModuleDisabled } from '@endora-commerce/platform/kernel';
6
+ import { productIdsInRequestChannel } from '@endora-commerce/platform/kernel';
7
+ export class CartConversionService {
8
+ emFactory;
9
+ cartService;
10
+ rfqService;
11
+ quoteRequests;
12
+ catalogProducts;
13
+ channelMembership;
14
+ constructor(emFactory, cartService,
15
+ /** `quote_requests`' customer-facing write surface. */
16
+ rfqService,
17
+ /** `quote_requests`' row read model — the quote and its lines. */
18
+ quoteRequests,
19
+ /** `catalog`'s product read model — is the quoted product still there? */
20
+ catalogProducts,
21
+ /**
22
+ * The sanctioned bridge accessor (Constitution XII) — and on this seam it
23
+ * is what keeps the *reason* honest (issue #259). `CartService.addItem`
24
+ * refuses an out-of-channel line by itself, but this method catches every
25
+ * `HttpError` from it and reports one dropped-line reason,
26
+ * `no_price_in_customer_list`. A line the channel does not sell has nothing
27
+ * to do with the buyer's price list, so it is narrowed here instead and
28
+ * falls into the `not_purchasable` branch — where a line whose product has
29
+ * gone already lands.
30
+ */
31
+ channelMembership) {
32
+ this.emFactory = emFactory;
33
+ this.cartService = cartService;
34
+ this.rfqService = rfqService;
35
+ this.quoteRequests = quoteRequests;
36
+ this.catalogProducts = catalogProducts;
37
+ this.channelMembership = channelMembership;
38
+ }
39
+ /**
40
+ * Cart → Quote Request. Refuses on an empty cart. Source cart flips to
41
+ * `completed` with `converted_to_quote_request_id` set.
42
+ */
43
+ async convertToQuoteRequest(cart, ctx, note) {
44
+ // command-coverage-ignore: the durable write is the RFQ (RfqService
45
+ // .createForCustomer, audited as quote_request.create); this only marks the
46
+ // ephemeral cart completed and clears its items — transient working state.
47
+ const em = this.emFactory();
48
+ const items = await em.find(CartItem, { cartId: cart.id });
49
+ if (items.length === 0) {
50
+ throw new HttpError(422, ERROR_CODES.CART_EMPTY, 'cart_empty');
51
+ }
52
+ const rfq = await this.rfqService.createForCustomer(ctx, {
53
+ ...(note !== undefined ? { headerNote: note } : {}),
54
+ items: items.map((it) => ({
55
+ productId: it.productId,
56
+ ...(it.variantId ? { variantId: it.variantId } : {}),
57
+ quantity: it.quantity,
58
+ // Feature 043 — carry the packaging-unit snapshot so the RFQ line
59
+ // shows "<name> (Paleta)" like the cart did.
60
+ ...(it.packagingUnitName ? { packagingUnitName: it.packagingUnitName } : {}),
61
+ ...(it.packagingUnitBaseQuantity != null
62
+ ? { packagingUnitBaseQuantity: it.packagingUnitBaseQuantity }
63
+ : {}),
64
+ // Carry the buyer's currently-snapshotted unit price as the
65
+ // reference price on the QR line — sales sees what the cart
66
+ // would have cost without negotiation.
67
+ desiredUnitPrice: Number(it.unitPrice),
68
+ })),
69
+ });
70
+ // Flip the source cart to `completed` + record the conversion
71
+ // lineage. Reload on this service's own EM fork before mutating —
72
+ // the `cart` arg was loaded by the route handler's fork and a
73
+ // cross-fork flush would silently no-op (same bug we hit in
74
+ // CartCouponService.apply). Mirror the change back onto the
75
+ // caller's in-memory cart so the audit / response paths see the
76
+ // post-conversion state.
77
+ const managedCart = await em.findOne(Cart, { id: cart.id });
78
+ if (managedCart) {
79
+ managedCart.status = 'completed';
80
+ managedCart.convertedToQuoteRequestId = rfq.id;
81
+ managedCart.lastActivityAt = new Date();
82
+ }
83
+ await em.nativeDelete(CartItem, { cartId: cart.id });
84
+ await em.flush();
85
+ cart.status = 'completed';
86
+ cart.convertedToQuoteRequestId = rfq.id;
87
+ cart.lastActivityAt = managedCart?.lastActivityAt ?? new Date();
88
+ return { quoteRequestId: rfq.id, cartId: cart.id };
89
+ }
90
+ /**
91
+ * Quote Request → Cart. Creates a fresh `active` cart (or returns the
92
+ * existing one) for the buyer, appends the QR's lines with re-resolved
93
+ * prices (via `CartService.addItem`, which already routes through the
94
+ * pricing resolver when wired), and surfaces a list of dropped lines.
95
+ *
96
+ * The QR's `desiredUnitPrice` is intentionally dropped — the cart's
97
+ * unit prices come from the customer's current price list.
98
+ */
99
+ async createCartFromQuoteRequest(qrId, ctx) {
100
+ const qr = await this.quoteRequests.findById(qrId);
101
+ if (!qr) {
102
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'quote_request_not_found');
103
+ }
104
+ // Anti-enumeration: only the requesting customer (or the Organization's
105
+ // org-admin members) may convert it. Foundation gates ownership via the
106
+ // RFQ-detail endpoint; we mirror the customer ownership check here.
107
+ if (qr.customerAccountId !== ctx.customerAccountId) {
108
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'quote_request_not_found');
109
+ }
110
+ const qrItems = await this.quoteRequests.listItems(qr.id);
111
+ if (qrItems.length === 0) {
112
+ throw new HttpError(422, ERROR_CODES.VALIDATION_FAILED, 'quote_request_empty');
113
+ }
114
+ const productIds = Array.from(new Set(qrItems.map((it) => it.productId)));
115
+ const products = await this.catalogProducts.findByIds(productIds);
116
+ const publishedHere = await productIdsInRequestChannel(this.channelMembership, productIds);
117
+ const productById = new Map(products
118
+ .filter((p) => publishedHere === null || publishedHere.has(p.id))
119
+ .map((p) => [p.id, p]));
120
+ const droppedLines = [];
121
+ let appendedLineCount = 0;
122
+ let targetCart = null;
123
+ for (const qrItem of qrItems) {
124
+ const product = productById.get(qrItem.productId);
125
+ if (!product) {
126
+ droppedLines.push({
127
+ productId: qrItem.productId,
128
+ productName: qrItem.productName ?? qrItem.productId,
129
+ reason: 'not_purchasable',
130
+ });
131
+ continue;
132
+ }
133
+ try {
134
+ const result = await this.cartService.addItem({ customer: ctx }, {
135
+ productId: qrItem.productId,
136
+ ...(qrItem.variantId ? { variantId: qrItem.variantId } : {}),
137
+ quantity: qrItem.quantity,
138
+ });
139
+ targetCart = result.cart;
140
+ appendedLineCount += 1;
141
+ }
142
+ catch (err) {
143
+ // The tolerance is per line and it is right: one unpriceable product
144
+ // must not abort a twenty-line conversion, and the buyer is told which
145
+ // lines were dropped and why.
146
+ //
147
+ // `rethrowIfModuleDisabled` is its first line because `addItem` reaches
148
+ // `catalog`'s and `price_lists`' gated ports, and `ModuleDisabledError`
149
+ // **is** an `HttpError` — so the `instanceof` test below would let a
150
+ // switched-off pricing engine through as "no price in your list" for
151
+ // every line, which is issue #124's defect wearing a per-line report.
152
+ rethrowIfModuleDisabled(err);
153
+ // CartService throws an HttpError when the product is quote-only
154
+ // (displayMode='none') or no resolver match — both translate to
155
+ // `no_price_in_customer_list` for the storefront banner.
156
+ if (err instanceof HttpError) {
157
+ droppedLines.push({
158
+ productId: qrItem.productId,
159
+ productName: qrItem.productName ?? qrItem.productId,
160
+ reason: 'no_price_in_customer_list',
161
+ });
162
+ continue;
163
+ }
164
+ throw err;
165
+ }
166
+ }
167
+ if (!targetCart) {
168
+ // Every line dropped — still create / fetch the customer's active
169
+ // cart so the caller has a destination cartId.
170
+ targetCart = await this.cartService.getOrCreateForCustomer(ctx);
171
+ }
172
+ return { cartId: targetCart.id, appendedLineCount, droppedLines };
173
+ }
174
+ }
175
+ //# sourceMappingURL=cart-conversion-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cart-conversion-service.js","sourceRoot":"","sources":["../../../src/backend/services/cart-conversion-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,129 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { CouponDropReason, PromotionApplication } from '@endora-commerce/contracts';
3
+ import { Cart } from '../entities/cart.entity.js';
4
+ import { CartItem } from '../entities/cart-item.entity.js';
5
+ import type { CustomerAccountReadPort, OrganizationDetailsPort, PromotionApplyPort, PromotionCodePort } from '@endora-commerce/contracts';
6
+ import type { CartApprovalService } from './cart-approval-service.js';
7
+ /**
8
+ * Cart-level coupon application (feature 027 US2).
9
+ *
10
+ * Plumbs the buyer's coupon code through `PromotionService.applyToCart`:
11
+ * - `apply(cart, code)` writes the code on the cart and asks the
12
+ * promotion engine whether the named promotion would stick. If yes,
13
+ * the discount is reflected via the recomputed snapshot the caller
14
+ * returns to the buyer. If no, the cart's previous code (if any) is
15
+ * preserved and a typed `CouponApplyResult.dropped` is returned
16
+ * identifying the precise reason from the snapshot's eligibility
17
+ * filter.
18
+ * - `clear(cart)` removes the active code. Idempotent.
19
+ * - `reevaluateOnRead(cart, snapshot)` is called from the GET-cart
20
+ * read path. If a previously-applied code is no longer eligible
21
+ * (e.g. the cart fell below `min_cart_subtotal` after a line
22
+ * removal), the code is silently dropped and the caller can surface
23
+ * `couponDroppedThisRead` in the response.
24
+ *
25
+ * The service never mutates promotion rows — only `cart.applied_promotion_code`.
26
+ */
27
+ export interface CouponApplyOk {
28
+ outcome: 'applied';
29
+ cart: Cart;
30
+ appliedCode: string;
31
+ }
32
+ export interface CouponApplyDropped {
33
+ outcome: 'dropped';
34
+ reason: CouponDropReason;
35
+ shortfall?: {
36
+ amount: number;
37
+ currency: string;
38
+ };
39
+ }
40
+ export type CouponApplyResult = CouponApplyOk | CouponApplyDropped;
41
+ export interface CouponReevaluateResult {
42
+ /** When non-null, the read path should populate `couponDroppedThisRead`. */
43
+ dropped: {
44
+ code: string;
45
+ reason: CouponDropReason;
46
+ } | null;
47
+ }
48
+ export declare class CartCouponService {
49
+ private readonly emFactory;
50
+ /** `promotions`' apply surface — the discount the engine computes. */
51
+ private readonly promotionService;
52
+ /**
53
+ * `promotions`' code-resolution port (feature 075, Phase C). The
54
+ * three-step lookup it replaces — legacy `promotions.code`, then
55
+ * `promotion_coupons`, then the promotion behind the coupon, each filtered
56
+ * on `isActive` — is that module's business, and this file reproduced it.
57
+ */
58
+ private readonly promotionCodes;
59
+ /**
60
+ * Issue #177 — the two reads behind the buyer's effective customer group,
61
+ * which every snapshot this service builds used to pass as a literal
62
+ * `null`. A coupon or automatic promotion an operator restricted to a
63
+ * group therefore matched nobody.
64
+ */
65
+ private readonly customerAccounts;
66
+ private readonly organizations;
67
+ private readonly approvalService?;
68
+ /**
69
+ * `approvalService` is optional — when wired, applying or clearing a
70
+ * coupon on an `approved` cart re-arms approval to `pending`. Legacy
71
+ * compositions without the approval surface omit it (re-arm is a
72
+ * no-op when absent).
73
+ */
74
+ constructor(emFactory: () => EntityManager,
75
+ /** `promotions`' apply surface — the discount the engine computes. */
76
+ promotionService: PromotionApplyPort,
77
+ /**
78
+ * `promotions`' code-resolution port (feature 075, Phase C). The
79
+ * three-step lookup it replaces — legacy `promotions.code`, then
80
+ * `promotion_coupons`, then the promotion behind the coupon, each filtered
81
+ * on `isActive` — is that module's business, and this file reproduced it.
82
+ */
83
+ promotionCodes: PromotionCodePort,
84
+ /**
85
+ * Issue #177 — the two reads behind the buyer's effective customer group,
86
+ * which every snapshot this service builds used to pass as a literal
87
+ * `null`. A coupon or automatic promotion an operator restricted to a
88
+ * group therefore matched nobody.
89
+ */
90
+ customerAccounts: CustomerAccountReadPort, organizations: OrganizationDetailsPort, approvalService?: CartApprovalService | undefined);
91
+ /** The buyer's effective group: their own, else their Organization's. */
92
+ private customerGroupIdFor;
93
+ /**
94
+ * Apply (or replace) the active coupon on the cart. Validates against
95
+ * the promotion engine using the current cart contents.
96
+ *
97
+ * `code === null` clears any active coupon (use the `clear` alias for
98
+ * readability — both are equivalent).
99
+ *
100
+ * Note: the cart passed in may have been loaded by a different
101
+ * EntityManager fork. We re-load it on our own fork so the
102
+ * `appliedPromotionCode` mutation flushes to the DB and the
103
+ * caller's in-memory cart is also updated.
104
+ */
105
+ apply(cart: Cart, code: string | null): Promise<CouponApplyResult>;
106
+ /**
107
+ * Feature 045 — compute the promotion application for a cart on read, so
108
+ * the serialized cart can show the real discount amount + per-promotion
109
+ * breakdown (automatic action-based promotions plus any applied coupon).
110
+ * Pure read — never mutates the cart.
111
+ */
112
+ computeApplication(cart: Cart, items: CartItem[]): Promise<PromotionApplication>;
113
+ clear(cart: Cart): Promise<void>;
114
+ /**
115
+ * Called from the GET-cart read path. If a previously-applied code is
116
+ * no longer eligible (e.g. the cart fell below `min_cart_subtotal`),
117
+ * silently drops it on the cart record and returns a typed result the
118
+ * caller can surface as `couponDroppedThisRead` in the response.
119
+ */
120
+ reevaluateOnRead(cart: Cart, items: CartItem[]): Promise<CouponReevaluateResult>;
121
+ /**
122
+ * Persists a coupon drop. The caller's `cart` entity may belong to a
123
+ * different EntityManager fork; we reload on our own fork, mutate, and
124
+ * also mirror the change onto the caller's in-memory cart so the
125
+ * read-side serializer sees the dropped coupon.
126
+ */
127
+ private persistDrop;
128
+ }
129
+ //# sourceMappingURL=cart-coupon-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cart-coupon-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/cart-coupon-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAgB,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AACvG,OAAO,EAAE,IAAI,EAAE,MAAM,4BAA4B,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAC3D,OAAO,KAAK,EACV,uBAAuB,EACvB,uBAAuB,EACvB,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGtE;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,SAAS,CAAC;IACnB,IAAI,EAAE,IAAI,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,SAAS,CAAC;IACnB,MAAM,EAAE,gBAAgB,CAAC;IACzB,SAAS,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAClD;AAED,MAAM,MAAM,iBAAiB,GAAG,aAAa,GAAG,kBAAkB,CAAC;AAEnE,MAAM,WAAW,sBAAsB;IACrC,4EAA4E;IAC5E,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,gBAAgB,CAAA;KAAE,GAAG,IAAI,CAAC;CAC5D;AAED,qBAAa,iBAAiB;IAQ1B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,sEAAsE;IACtE,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC;IAzBnC;;;;;OAKG;gBAEgB,SAAS,EAAE,MAAM,aAAa;IAC/C,sEAAsE;IACrD,gBAAgB,EAAE,kBAAkB;IACrD;;;;;OAKG;IACc,cAAc,EAAE,iBAAiB;IAClD;;;;;OAKG;IACc,gBAAgB,EAAE,uBAAuB,EACzC,aAAa,EAAE,uBAAuB,EACtC,eAAe,CAAC,EAAE,mBAAmB,YAAA;IAGxD,yEAAyE;IACzE,OAAO,CAAC,kBAAkB;IAU1B;;;;;;;;;;;OAWG;IACG,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAoGxE;;;;;OAKG;IACG,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAoBhF,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBtC;;;;;OAKG;IACG,gBAAgB,CACpB,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,QAAQ,EAAE,GAChB,OAAO,CAAC,sBAAsB,CAAC;IAoClC;;;;;OAKG;YACW,WAAW;CAQ1B"}
@@ -0,0 +1,244 @@
1
+ import { Cart } from '../entities/cart.entity.js';
2
+ import { CartItem } from '../entities/cart-item.entity.js';
3
+ import { resolveCartCustomerGroupId } from './customer-group-resolver.js';
4
+ export class CartCouponService {
5
+ emFactory;
6
+ promotionService;
7
+ promotionCodes;
8
+ customerAccounts;
9
+ organizations;
10
+ approvalService;
11
+ /**
12
+ * `approvalService` is optional — when wired, applying or clearing a
13
+ * coupon on an `approved` cart re-arms approval to `pending`. Legacy
14
+ * compositions without the approval surface omit it (re-arm is a
15
+ * no-op when absent).
16
+ */
17
+ constructor(emFactory,
18
+ /** `promotions`' apply surface — the discount the engine computes. */
19
+ promotionService,
20
+ /**
21
+ * `promotions`' code-resolution port (feature 075, Phase C). The
22
+ * three-step lookup it replaces — legacy `promotions.code`, then
23
+ * `promotion_coupons`, then the promotion behind the coupon, each filtered
24
+ * on `isActive` — is that module's business, and this file reproduced it.
25
+ */
26
+ promotionCodes,
27
+ /**
28
+ * Issue #177 — the two reads behind the buyer's effective customer group,
29
+ * which every snapshot this service builds used to pass as a literal
30
+ * `null`. A coupon or automatic promotion an operator restricted to a
31
+ * group therefore matched nobody.
32
+ */
33
+ customerAccounts, organizations, approvalService) {
34
+ this.emFactory = emFactory;
35
+ this.promotionService = promotionService;
36
+ this.promotionCodes = promotionCodes;
37
+ this.customerAccounts = customerAccounts;
38
+ this.organizations = organizations;
39
+ this.approvalService = approvalService;
40
+ }
41
+ /** The buyer's effective group: their own, else their Organization's. */
42
+ customerGroupIdFor(cart) {
43
+ return resolveCartCustomerGroupId({ customerAccounts: this.customerAccounts, organizations: this.organizations }, {
44
+ customerAccountId: cart.customerAccountId ?? null,
45
+ organizationId: cart.organizationId ?? null,
46
+ });
47
+ }
48
+ /**
49
+ * Apply (or replace) the active coupon on the cart. Validates against
50
+ * the promotion engine using the current cart contents.
51
+ *
52
+ * `code === null` clears any active coupon (use the `clear` alias for
53
+ * readability — both are equivalent).
54
+ *
55
+ * Note: the cart passed in may have been loaded by a different
56
+ * EntityManager fork. We re-load it on our own fork so the
57
+ * `appliedPromotionCode` mutation flushes to the DB and the
58
+ * caller's in-memory cart is also updated.
59
+ */
60
+ async apply(cart, code) {
61
+ // command-coverage-ignore: transient cart coupon state — the durable applied
62
+ // promotion is captured on the order at checkout (audited there); the cart is
63
+ // ephemeral pre-order working state.
64
+ if (code === null) {
65
+ await this.clear(cart);
66
+ return { outcome: 'applied', cart, appliedCode: '' };
67
+ }
68
+ const em = this.emFactory();
69
+ const managedCart = await em.findOne(Cart, { id: cart.id });
70
+ if (!managedCart) {
71
+ return { outcome: 'dropped', reason: 'invalid_code' };
72
+ }
73
+ // Resolve the promotion row by code so we can build the reason-specific
74
+ // drop response (the snapshot's eligibility filter inside applyToCart
75
+ // is opaque to callers, so we re-implement the precondition checks
76
+ // here for the reason mapping).
77
+ const promotion = await this.promotionCodes.resolveByCode(code);
78
+ if (!promotion) {
79
+ return { outcome: 'dropped', reason: 'invalid_code' };
80
+ }
81
+ const now = new Date();
82
+ if (promotion.validFrom && now < promotion.validFrom) {
83
+ return { outcome: 'dropped', reason: 'expired' };
84
+ }
85
+ if (promotion.validUntil && now > promotion.validUntil) {
86
+ return { outcome: 'dropped', reason: 'expired' };
87
+ }
88
+ if (promotion.organizationId && promotion.organizationId !== managedCart.organizationId) {
89
+ return { outcome: 'dropped', reason: 'wrong_organization' };
90
+ }
91
+ const items = await em.find(CartItem, { cartId: managedCart.id });
92
+ const subtotal = items.reduce((acc, it) => acc + Number(it.unitPrice) * it.quantity, 0);
93
+ const currency = items[0]?.currency ?? 'PLN';
94
+ if (promotion.minCartSubtotal != null) {
95
+ const min = Number(promotion.minCartSubtotal);
96
+ if (min > subtotal) {
97
+ return {
98
+ outcome: 'dropped',
99
+ reason: 'below_min_spend',
100
+ shortfall: { amount: round2(min - subtotal), currency },
101
+ };
102
+ }
103
+ }
104
+ // Double-check via the promotion engine — this catches any criteria
105
+ // we don't replicate here (e.g. attribute/category rules from
106
+ // feature 012).
107
+ const snapshot = {
108
+ organizationId: cart.organizationId ?? null,
109
+ customerGroupId: await this.customerGroupIdFor(managedCart),
110
+ currency,
111
+ lines: items.map((it) => ({
112
+ productId: it.productId,
113
+ variantId: it.variantId ?? null,
114
+ // Feature 012 / US8 category criteria — hydrated by the engine
115
+ // when needed. Empty seed keeps the contract satisfied.
116
+ categoryIds: [],
117
+ quantity: it.quantity,
118
+ unitPrice: { amount: Number(it.unitPrice), currency: it.currency },
119
+ })),
120
+ deliveryTotal: 0,
121
+ promotionCode: code,
122
+ };
123
+ const application = await this.promotionService.applyToCart(snapshot);
124
+ const stuck = application.appliedPromotions.some((ap) => ap.promotionId === promotion.promotionId);
125
+ if (!stuck) {
126
+ // Eligible by the local pre-checks but rejected by the engine's
127
+ // criteria — bucket as `wrong_customer_group` since that's the
128
+ // most common criterion we don't pre-check here. (The promotion
129
+ // engine doesn't expose a typed reason, so this is the best
130
+ // generic mapping.)
131
+ return { outcome: 'dropped', reason: 'wrong_customer_group' };
132
+ }
133
+ managedCart.appliedPromotionCode = code;
134
+ managedCart.lastActivityAt = new Date();
135
+ await em.flush();
136
+ // Mirror the persisted mutation onto the caller's in-memory cart so
137
+ // serializeCart in the route handler reflects it without re-reading.
138
+ cart.appliedPromotionCode = code;
139
+ cart.lastActivityAt = managedCart.lastActivityAt;
140
+ if (this.approvalService && managedCart.customerAccountId) {
141
+ await this.approvalService.maybeReArm(managedCart, {
142
+ customerAccountId: managedCart.customerAccountId,
143
+ });
144
+ cart.approvalStatus = managedCart.approvalStatus;
145
+ }
146
+ return { outcome: 'applied', cart, appliedCode: code };
147
+ }
148
+ /**
149
+ * Feature 045 — compute the promotion application for a cart on read, so
150
+ * the serialized cart can show the real discount amount + per-promotion
151
+ * breakdown (automatic action-based promotions plus any applied coupon).
152
+ * Pure read — never mutates the cart.
153
+ */
154
+ async computeApplication(cart, items) {
155
+ const currency = items[0]?.currency ?? 'PLN';
156
+ const snapshot = {
157
+ organizationId: cart.organizationId ?? null,
158
+ customerGroupId: await this.customerGroupIdFor(cart),
159
+ currency,
160
+ lines: items.map((it) => ({
161
+ productId: it.productId,
162
+ variantId: it.variantId ?? null,
163
+ categoryIds: [],
164
+ quantity: it.quantity,
165
+ unitPrice: { amount: Number(it.unitPrice), currency: it.currency },
166
+ })),
167
+ deliveryTotal: 0,
168
+ promotionCode: cart.appliedPromotionCode ?? null,
169
+ salesChannelId: cart.salesChannelId ?? null,
170
+ };
171
+ return this.promotionService.applyToCart(snapshot);
172
+ }
173
+ async clear(cart) {
174
+ // command-coverage-ignore: transient cart coupon state (see apply()).
175
+ if (cart.appliedPromotionCode === null)
176
+ return;
177
+ const em = this.emFactory();
178
+ const managedCart = await em.findOne(Cart, { id: cart.id });
179
+ if (!managedCart)
180
+ return;
181
+ managedCart.appliedPromotionCode = null;
182
+ managedCart.lastActivityAt = new Date();
183
+ await em.flush();
184
+ cart.appliedPromotionCode = null;
185
+ cart.lastActivityAt = managedCart.lastActivityAt;
186
+ if (this.approvalService && managedCart.customerAccountId) {
187
+ await this.approvalService.maybeReArm(managedCart, {
188
+ customerAccountId: managedCart.customerAccountId,
189
+ });
190
+ cart.approvalStatus = managedCart.approvalStatus;
191
+ }
192
+ }
193
+ /**
194
+ * Called from the GET-cart read path. If a previously-applied code is
195
+ * no longer eligible (e.g. the cart fell below `min_cart_subtotal`),
196
+ * silently drops it on the cart record and returns a typed result the
197
+ * caller can surface as `couponDroppedThisRead` in the response.
198
+ */
199
+ async reevaluateOnRead(cart, items) {
200
+ if (!cart.appliedPromotionCode)
201
+ return { dropped: null };
202
+ const code = cart.appliedPromotionCode;
203
+ const em = this.emFactory();
204
+ const promotion = await this.promotionCodes.resolveByCode(code);
205
+ if (!promotion) {
206
+ await this.persistDrop(em, cart);
207
+ return { dropped: { code, reason: 'invalid_code' } };
208
+ }
209
+ const now = new Date();
210
+ if ((promotion.validFrom && now < promotion.validFrom) ||
211
+ (promotion.validUntil && now > promotion.validUntil)) {
212
+ await this.persistDrop(em, cart);
213
+ return { dropped: { code, reason: 'expired' } };
214
+ }
215
+ const subtotal = items.reduce((acc, it) => acc + Number(it.unitPrice) * it.quantity, 0);
216
+ if (promotion.minCartSubtotal != null) {
217
+ const min = Number(promotion.minCartSubtotal);
218
+ if (min > subtotal) {
219
+ await this.persistDrop(em, cart);
220
+ return { dropped: { code, reason: 'below_min_spend' } };
221
+ }
222
+ }
223
+ return { dropped: null };
224
+ }
225
+ /**
226
+ * Persists a coupon drop. The caller's `cart` entity may belong to a
227
+ * different EntityManager fork; we reload on our own fork, mutate, and
228
+ * also mirror the change onto the caller's in-memory cart so the
229
+ * read-side serializer sees the dropped coupon.
230
+ */
231
+ async persistDrop(em, cart) {
232
+ // command-coverage-ignore: transient cart coupon state (see apply()).
233
+ const managed = await em.findOne(Cart, { id: cart.id });
234
+ if (!managed)
235
+ return;
236
+ managed.appliedPromotionCode = null;
237
+ await em.flush();
238
+ cart.appliedPromotionCode = null;
239
+ }
240
+ }
241
+ function round2(n) {
242
+ return Math.round(n * 100) / 100;
243
+ }
244
+ //# sourceMappingURL=cart-coupon-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cart-coupon-service.js","sourceRoot":"","sources":["../../../src/backend/services/cart-coupon-service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAE,MAAM,4BAA4B,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAQ3D,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AA0C1E,MAAM,OAAO,iBAAiB;IAQT;IAEA;IAOA;IAOA;IACA;IACA;IAzBnB;;;;;OAKG;IACH,YACmB,SAA8B;IAC/C,sEAAsE;IACrD,gBAAoC;IACrD;;;;;OAKG;IACc,cAAiC;IAClD;;;;;OAKG;IACc,gBAAyC,EACzC,aAAsC,EACtC,eAAqC;QAlBrC,cAAS,GAAT,SAAS,CAAqB;QAE9B,qBAAgB,GAAhB,gBAAgB,CAAoB;QAOpC,mBAAc,GAAd,cAAc,CAAmB;QAOjC,qBAAgB,GAAhB,gBAAgB,CAAyB;QACzC,kBAAa,GAAb,aAAa,CAAyB;QACtC,oBAAe,GAAf,eAAe,CAAsB;IACrD,CAAC;IAEJ,yEAAyE;IACjE,kBAAkB,CAAC,IAAU;QACnC,OAAO,0BAA0B,CAC/B,EAAE,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,EAC9E;YACE,iBAAiB,EAAE,IAAI,CAAC,iBAAiB,IAAI,IAAI;YACjD,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,IAAI;SAC5C,CACF,CAAC;IACJ,CAAC;IAED;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,KAAK,CAAC,IAAU,EAAE,IAAmB;QACzC,6EAA6E;QAC7E,8EAA8E;QAC9E,qCAAqC;QACrC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACvB,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;QACvD,CAAC;QAED,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC5B,MAAM,WAAW,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5D,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;QACxD,CAAC;QAED,wEAAwE;QACxE,sEAAsE;QACtE,mEAAmE;QACnE,gCAAgC;QAChC,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QAChE,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;QACxD,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,SAAS,CAAC,SAAS,IAAI,GAAG,GAAG,SAAS,CAAC,SAAS,EAAE,CAAC;YACrD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;QACnD,CAAC;QACD,IAAI,SAAS,CAAC,UAAU,IAAI,GAAG,GAAG,SAAS,CAAC,UAAU,EAAE,CAAC;YACvD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;QACnD,CAAC;QACD,IAAI,SAAS,CAAC,cAAc,IAAI,SAAS,CAAC,cAAc,KAAK,WAAW,CAAC,cAAc,EAAE,CAAC;YACxF,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAC;QAC9D,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,EAAE,CAAC,CAAC;QAClE,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAC3B,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC,GAAG,GAAG,MAAM,CAAC,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,QAAQ,EACrD,CAAC,CACF,CAAC;QACF,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,IAAI,KAAK,CAAC;QAE7C,IAAI,SAAS,CAAC,eAAe,IAAI,IAAI,EAAE,CAAC;YACtC,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;YAC9C,IAAI,GAAG,GAAG,QAAQ,EAAE,CAAC;gBACnB,OAAO;oBACL,OAAO,EAAE,SAAS;oBAClB,MAAM,EAAE,iBAAiB;oBACzB,SAAS,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,GAAG,QAAQ,CAAC,EAAE,QAAQ,EAAE;iBACxD,CAAC;YACJ,CAAC;QACH,CAAC;QAED,oEAAoE;QACpE,8DAA8D;QAC9D,gBAAgB;QAChB,MAAM,QAAQ,GAAiB;YAC7B,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,IAAI;YAC3C,eAAe,EAAE,MAAM,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC;YAC3D,QAAQ;YACR,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;gBACxB,SAAS,EAAE,EAAE,CAAC,SAAS;gBACvB,SAAS,EAAE,EAAE,CAAC,SAAS,IAAI,IAAI;gBAC/B,+DAA+D;gBAC/D,wDAAwD;gBACxD,WAAW,EAAE,EAAE;gBACf,QAAQ,EAAE,EAAE,CAAC,QAAQ;gBACrB,SAAS,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE;aACnE,CAAC,CAAC;YACH,aAAa,EAAE,CAAC;YAChB,aAAa,EAAE,IAAI;SACpB,CAAC;QAEF,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QACtE,MAAM,KAAK,GAAG,WAAW,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,WAAW,KAAK,SAAS,CAAC,WAAW,CAAC,CAAC;QACnG,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,gEAAgE;YAChE,+DAA+D;YAC/D,gEAAgE;YAChE,4DAA4D;YAC5D,oBAAoB;YACpB,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC;QAChE,CAAC;QAED,WAAW,CAAC,oBAAoB,GAAG,IAAI,CAAC;QACxC,WAAW,CAAC,cAAc,GAAG,IAAI,IAAI,EAAE,CAAC;QACxC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;QACjB,oEAAoE;QACpE,qEAAqE;QACrE,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC;QACjC,IAAI,CAAC,cAAc,GAAG,WAAW,CAAC,cAAc,CAAC;QACjD,IAAI,IAAI,CAAC,eAAe,IAAI,WAAW,CAAC,iBAAiB,EAAE,CAAC;YAC1D,MAAM,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,WAAW,EAAE;gBACjD,iBAAiB,EAAE,WAAW,CAAC,iBAAiB;aACjD,CAAC,CAAC;YACH,IAAI,CAAC,cAAc,GAAG,WAAW,CAAC,cAAc,CAAC;QACnD,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IACzD,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,kBAAkB,CAAC,IAAU,EAAE,KAAiB;QACpD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,IAAI,KAAK,CAAC;QAC7C,MAAM,QAAQ,GAAiB;YAC7B,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,IAAI;YAC3C,eAAe,EAAE,MAAM,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC;YACpD,QAAQ;YACR,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;gBACxB,SAAS,EAAE,EAAE,CAAC,SAAS;gBACvB,SAAS,EAAE,EAAE,CAAC,SAAS,IAAI,IAAI;gBAC/B,WAAW,EAAE,EAAE;gBACf,QAAQ,EAAE,EAAE,CAAC,QAAQ;gBACrB,SAAS,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE;aACnE,CAAC,CAAC;YACH,aAAa,EAAE,CAAC;YAChB,aAAa,EAAE,IAAI,CAAC,oBAAoB,IAAI,IAAI;YAChD,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,IAAI;SAC5C,CAAC;QACF,OAAO,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IACrD,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,IAAU;QACpB,sEAAsE;QACtE,IAAI,IAAI,CAAC,oBAAoB,KAAK,IAAI;YAAE,OAAO;QAC/C,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC5B,MAAM,WAAW,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5D,IAAI,CAAC,WAAW;YAAE,OAAO;QACzB,WAAW,CAAC,oBAAoB,GAAG,IAAI,CAAC;QACxC,WAAW,CAAC,cAAc,GAAG,IAAI,IAAI,EAAE,CAAC;QACxC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;QACjB,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC;QACjC,IAAI,CAAC,cAAc,GAAG,WAAW,CAAC,cAAc,CAAC;QACjD,IAAI,IAAI,CAAC,eAAe,IAAI,WAAW,CAAC,iBAAiB,EAAE,CAAC;YAC1D,MAAM,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,WAAW,EAAE;gBACjD,iBAAiB,EAAE,WAAW,CAAC,iBAAiB;aACjD,CAAC,CAAC;YACH,IAAI,CAAC,cAAc,GAAG,WAAW,CAAC,cAAc,CAAC;QACnD,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,gBAAgB,CACpB,IAAU,EACV,KAAiB;QAEjB,IAAI,CAAC,IAAI,CAAC,oBAAoB;YAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACzD,MAAM,IAAI,GAAG,IAAI,CAAC,oBAAoB,CAAC;QAEvC,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC5B,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QAChE,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,CAAC,WAAW,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;YACjC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,EAAE,EAAE,CAAC;QACvD,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IACE,CAAC,SAAS,CAAC,SAAS,IAAI,GAAG,GAAG,SAAS,CAAC,SAAS,CAAC;YAClD,CAAC,SAAS,CAAC,UAAU,IAAI,GAAG,GAAG,SAAS,CAAC,UAAU,CAAC,EACpD,CAAC;YACD,MAAM,IAAI,CAAC,WAAW,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;YACjC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,CAAC;QAClD,CAAC;QAED,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAC3B,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC,GAAG,GAAG,MAAM,CAAC,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,QAAQ,EACrD,CAAC,CACF,CAAC;QAEF,IAAI,SAAS,CAAC,eAAe,IAAI,IAAI,EAAE,CAAC;YACtC,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;YAC9C,IAAI,GAAG,GAAG,QAAQ,EAAE,CAAC;gBACnB,MAAM,IAAI,CAAC,WAAW,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;gBACjC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,EAAE,EAAE,CAAC;YAC1D,CAAC;QACH,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3B,CAAC;IAED;;;;;OAKG;IACK,KAAK,CAAC,WAAW,CAAC,EAAiB,EAAE,IAAU;QACrD,sEAAsE;QACtE,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;QACxD,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;QACpC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;QACjB,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC;IACnC,CAAC;CACF;AAED,SAAS,MAAM,CAAC,CAAS;IACvB,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;AACnC,CAAC"}
@@ -0,0 +1,43 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { CartStatus, CartApprovalStatus, CustomerAccountReadPort, OrgCartsListResponse } from '@endora-commerce/contracts';
3
+ import { Cart } from '../entities/cart.entity.js';
4
+ /**
5
+ * Organization-Administrator visibility surface (feature 027 US4).
6
+ *
7
+ * Lists every cart belonging to a member of the caller's Organization.
8
+ * The role gate is enforced at the route level; this service trusts
9
+ * its caller's `actor.organizationId` and refuses anti-enumeration
10
+ * lookups against other organizations.
11
+ */
12
+ export interface OrgCartsListQuery {
13
+ status?: CartStatus[];
14
+ approvalStatus?: CartApprovalStatus[];
15
+ page?: number;
16
+ pageSize?: number;
17
+ sort?: 'last_activity_desc' | 'last_activity_asc' | 'total_desc' | 'total_asc' | 'created_desc';
18
+ }
19
+ export declare class CartOrganizationVisibilityService {
20
+ private readonly emFactory;
21
+ /**
22
+ * `customer_accounts`' read model (feature 075, Phase C). The org-admin
23
+ * cart list renders each cart's owner by e-mail, and it read the account
24
+ * row itself — out of a table deactivation leaves in place.
25
+ */
26
+ private readonly customerAccounts;
27
+ constructor(emFactory: () => EntityManager,
28
+ /**
29
+ * `customer_accounts`' read model (feature 075, Phase C). The org-admin
30
+ * cart list renders each cart's owner by e-mail, and it read the account
31
+ * row itself — out of a table deactivation leaves in place.
32
+ */
33
+ customerAccounts: CustomerAccountReadPort);
34
+ list(organizationId: string, query?: OrgCartsListQuery): Promise<OrgCartsListResponse>;
35
+ /**
36
+ * Fetch a single cart in the caller's Organization. Refuses with 404
37
+ * when the cart belongs to a different Organization (anti-enumeration
38
+ * symmetry with the ordinary-member ownership check).
39
+ */
40
+ getInOrganization(cartId: string, organizationId: string): Promise<Cart>;
41
+ private sortClause;
42
+ }
43
+ //# sourceMappingURL=cart-organization-visibility-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cart-organization-visibility-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/cart-organization-visibility-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,KAAK,EACV,UAAU,EACV,kBAAkB,EAClB,uBAAuB,EACvB,oBAAoB,EACrB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAE,IAAI,EAAE,MAAM,4BAA4B,CAAC;AAGlD;;;;;;;GAOG;AAEH,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,UAAU,EAAE,CAAC;IACtB,cAAc,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,oBAAoB,GAAG,mBAAmB,GAAG,YAAY,GAAG,WAAW,GAAG,cAAc,CAAC;CACjG;AAED,qBAAa,iCAAiC;IAE1C,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,gBAAgB;gBANhB,SAAS,EAAE,MAAM,aAAa;IAC/C;;;;OAIG;IACc,gBAAgB,EAAE,uBAAuB;IAGtD,IAAI,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,GAAE,iBAAsB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IA0DhG;;;;OAIG;IACG,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS9E,OAAO,CAAC,UAAU;CAgBnB"}