@endora-commerce/mod-carts 0.100.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/admin/components/CartApprovalPolicyPanel.d.ts +51 -0
- package/dist/admin/components/CartApprovalPolicyPanel.d.ts.map +1 -0
- package/dist/admin/components/CartApprovalPolicyPanel.js +59 -0
- package/dist/admin/components/CartApprovalPolicyPanel.js.map +1 -0
- package/dist/admin/index.d.ts +38 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +73 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/CartDetail.d.ts +3 -0
- package/dist/admin/pages/CartDetail.d.ts.map +1 -0
- package/dist/admin/pages/CartDetail.js +74 -0
- package/dist/admin/pages/CartDetail.js.map +1 -0
- package/dist/admin/pages/CartsList.d.ts +3 -0
- package/dist/admin/pages/CartsList.d.ts.map +1 -0
- package/dist/admin/pages/CartsList.js +90 -0
- package/dist/admin/pages/CartsList.js.map +1 -0
- package/dist/admin/zones/CartApprovalPolicy.d.ts +29 -0
- package/dist/admin/zones/CartApprovalPolicy.d.ts.map +1 -0
- package/dist/admin/zones/CartApprovalPolicy.js +11 -0
- package/dist/admin/zones/CartApprovalPolicy.js.map +1 -0
- package/dist/backend/cli/abandonment-sweep.d.ts +64 -0
- package/dist/backend/cli/abandonment-sweep.d.ts.map +1 -0
- package/dist/backend/cli/abandonment-sweep.js +17 -0
- package/dist/backend/cli/abandonment-sweep.js.map +1 -0
- package/dist/backend/entities/cart-audit-entry.entity.d.ts +29 -0
- package/dist/backend/entities/cart-audit-entry.entity.d.ts.map +1 -0
- package/dist/backend/entities/cart-audit-entry.entity.js +109 -0
- package/dist/backend/entities/cart-audit-entry.entity.js.map +1 -0
- package/dist/backend/entities/cart-item.entity.d.ts +42 -0
- package/dist/backend/entities/cart-item.entity.d.ts.map +1 -0
- package/dist/backend/entities/cart-item.entity.js +119 -0
- package/dist/backend/entities/cart-item.entity.js.map +1 -0
- package/dist/backend/entities/cart.entity.d.ts +96 -0
- package/dist/backend/entities/cart.entity.d.ts.map +1 -0
- package/dist/backend/entities/cart.entity.js +207 -0
- package/dist/backend/entities/cart.entity.js.map +1 -0
- package/dist/backend/index.d.ts +122 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +184 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +31 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +95 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.d.ts +101 -0
- package/dist/backend/routes.d.ts.map +1 -0
- package/dist/backend/routes.js +620 -0
- package/dist/backend/routes.js.map +1 -0
- package/dist/backend/routes.organization.d.ts +29 -0
- package/dist/backend/routes.organization.d.ts.map +1 -0
- package/dist/backend/routes.organization.js +150 -0
- package/dist/backend/routes.organization.js.map +1 -0
- package/dist/backend/services/cart-abandonment-settings.d.ts +34 -0
- package/dist/backend/services/cart-abandonment-settings.d.ts.map +1 -0
- package/dist/backend/services/cart-abandonment-settings.js +55 -0
- package/dist/backend/services/cart-abandonment-settings.js.map +1 -0
- package/dist/backend/services/cart-abandonment-worker.d.ts +110 -0
- package/dist/backend/services/cart-abandonment-worker.d.ts.map +1 -0
- package/dist/backend/services/cart-abandonment-worker.js +185 -0
- package/dist/backend/services/cart-abandonment-worker.js.map +1 -0
- package/dist/backend/services/cart-admin-service.d.ts +58 -0
- package/dist/backend/services/cart-admin-service.d.ts.map +1 -0
- package/dist/backend/services/cart-admin-service.js +397 -0
- package/dist/backend/services/cart-admin-service.js.map +1 -0
- package/dist/backend/services/cart-approval-service.d.ts +147 -0
- package/dist/backend/services/cart-approval-service.d.ts.map +1 -0
- package/dist/backend/services/cart-approval-service.js +242 -0
- package/dist/backend/services/cart-approval-service.js.map +1 -0
- package/dist/backend/services/cart-audit-service.d.ts +75 -0
- package/dist/backend/services/cart-audit-service.d.ts.map +1 -0
- package/dist/backend/services/cart-audit-service.js +70 -0
- package/dist/backend/services/cart-audit-service.js.map +1 -0
- package/dist/backend/services/cart-conversion-service.d.ts +104 -0
- package/dist/backend/services/cart-conversion-service.d.ts.map +1 -0
- package/dist/backend/services/cart-conversion-service.js +175 -0
- package/dist/backend/services/cart-conversion-service.js.map +1 -0
- package/dist/backend/services/cart-coupon-service.d.ts +129 -0
- package/dist/backend/services/cart-coupon-service.d.ts.map +1 -0
- package/dist/backend/services/cart-coupon-service.js +244 -0
- package/dist/backend/services/cart-coupon-service.js.map +1 -0
- package/dist/backend/services/cart-organization-visibility-service.d.ts +43 -0
- package/dist/backend/services/cart-organization-visibility-service.d.ts.map +1 -0
- package/dist/backend/services/cart-organization-visibility-service.js +98 -0
- package/dist/backend/services/cart-organization-visibility-service.js.map +1 -0
- package/dist/backend/services/cart-placement-apply-port.d.ts +38 -0
- package/dist/backend/services/cart-placement-apply-port.d.ts.map +1 -0
- package/dist/backend/services/cart-placement-apply-port.js +71 -0
- package/dist/backend/services/cart-placement-apply-port.js.map +1 -0
- package/dist/backend/services/cart-pricing-recompute.d.ts +62 -0
- package/dist/backend/services/cart-pricing-recompute.d.ts.map +1 -0
- package/dist/backend/services/cart-pricing-recompute.js +131 -0
- package/dist/backend/services/cart-pricing-recompute.js.map +1 -0
- package/dist/backend/services/cart-query-service.d.ts +29 -0
- package/dist/backend/services/cart-query-service.d.ts.map +1 -0
- package/dist/backend/services/cart-query-service.js +40 -0
- package/dist/backend/services/cart-query-service.js.map +1 -0
- package/dist/backend/services/cart-read-port.d.ts +40 -0
- package/dist/backend/services/cart-read-port.d.ts.map +1 -0
- package/dist/backend/services/cart-read-port.js +144 -0
- package/dist/backend/services/cart-read-port.js.map +1 -0
- package/dist/backend/services/cart-recompute-cache.d.ts +49 -0
- package/dist/backend/services/cart-recompute-cache.d.ts.map +1 -0
- package/dist/backend/services/cart-recompute-cache.js +56 -0
- package/dist/backend/services/cart-recompute-cache.js.map +1 -0
- package/dist/backend/services/cart-service.d.ts +165 -0
- package/dist/backend/services/cart-service.d.ts.map +1 -0
- package/dist/backend/services/cart-service.js +556 -0
- package/dist/backend/services/cart-service.js.map +1 -0
- package/dist/backend/services/cart-state-machine.d.ts +51 -0
- package/dist/backend/services/cart-state-machine.d.ts.map +1 -0
- package/dist/backend/services/cart-state-machine.js +74 -0
- package/dist/backend/services/cart-state-machine.js.map +1 -0
- package/dist/backend/services/cart-upsell-service.d.ts +57 -0
- package/dist/backend/services/cart-upsell-service.d.ts.map +1 -0
- package/dist/backend/services/cart-upsell-service.js +100 -0
- package/dist/backend/services/cart-upsell-service.js.map +1 -0
- package/dist/backend/services/customer-group-resolver.d.ts +27 -0
- package/dist/backend/services/customer-group-resolver.d.ts.map +1 -0
- package/dist/backend/services/customer-group-resolver.js +32 -0
- package/dist/backend/services/customer-group-resolver.js.map +1 -0
- package/dist/manifest.d.ts +221 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +224 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260611T140352_carts_consolidation.d.ts +43 -0
- package/dist/migrations/20260611T140352_carts_consolidation.d.ts.map +1 -0
- package/dist/migrations/20260611T140352_carts_consolidation.js +237 -0
- package/dist/migrations/20260611T140352_carts_consolidation.js.map +1 -0
- package/dist/migrations/20260611T140413_carts_cart_item_packaging.d.ts +12 -0
- package/dist/migrations/20260611T140413_carts_cart_item_packaging.d.ts.map +1 -0
- package/dist/migrations/20260611T140413_carts_cart_item_packaging.js +20 -0
- package/dist/migrations/20260611T140413_carts_cart_item_packaging.js.map +1 -0
- package/dist/migrations/20260818T081253_carts_cart_completed_order_fk.d.ts +50 -0
- package/dist/migrations/20260818T081253_carts_cart_completed_order_fk.d.ts.map +1 -0
- package/dist/migrations/20260818T081253_carts_cart_completed_order_fk.js +62 -0
- package/dist/migrations/20260818T081253_carts_cart_completed_order_fk.js.map +1 -0
- package/dist/migrations/20260830T112911_carts_organization_attribution_check.d.ts +67 -0
- package/dist/migrations/20260830T112911_carts_organization_attribution_check.d.ts.map +1 -0
- package/dist/migrations/20260830T112911_carts_organization_attribution_check.js +121 -0
- package/dist/migrations/20260830T112911_carts_organization_attribution_check.js.map +1 -0
- package/dist/migrations/index.d.ts +30 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +35 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/ports/index.d.ts +106 -0
- package/dist/ports/index.d.ts.map +1 -0
- package/dist/ports/index.js +2 -0
- package/dist/ports/index.js.map +1 -0
- package/docs/carts.md +232 -0
- package/i18n/en.json +96 -0
- package/i18n/pl.json +96 -0
- package/package.json +107 -0
- package/tailwind.css +14 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Cart } from '../entities/cart.entity.js';
|
|
2
|
+
import { CartItem } from '../entities/cart-item.entity.js';
|
|
3
|
+
/**
|
|
4
|
+
* The row-level read model `carts` publishes, and the seed operation that goes
|
|
5
|
+
* with it (feature 075, Phase P).
|
|
6
|
+
*
|
|
7
|
+
* Six inbound sites are the same pair of queries — the customer's active cart,
|
|
8
|
+
* then its items — written out in `orders`' checkout, `orders`' reorder and
|
|
9
|
+
* `quote_requests`' quote-to-cart conversion. Two of those three then
|
|
10
|
+
* `em.create(Cart, …)` when the lookup misses and hand-build `CartItem` rows,
|
|
11
|
+
* which is two modules writing this module's two tables.
|
|
12
|
+
*
|
|
13
|
+
* `replaceItemsForCustomer` is what they call instead. The clear-then-seed
|
|
14
|
+
* rule it implements is the single-active-cart model admin order-create,
|
|
15
|
+
* reorder and RFQ conversion all already assume; it was spelled out twice, in
|
|
16
|
+
* two modules, and `lastActivityAt` was maintained in neither.
|
|
17
|
+
*/
|
|
18
|
+
export class CartReadService {
|
|
19
|
+
emFactory;
|
|
20
|
+
constructor(emFactory) {
|
|
21
|
+
this.emFactory = emFactory;
|
|
22
|
+
}
|
|
23
|
+
async findActiveForCustomer(ctx) {
|
|
24
|
+
const em = this.emFactory();
|
|
25
|
+
const cart = await em.findOne(Cart, {
|
|
26
|
+
customerAccountId: ctx.customerAccountId,
|
|
27
|
+
// The organisation is always present for a signed-in customer since
|
|
28
|
+
// D-178, so the empty arm is unreachable rather than a supported mode.
|
|
29
|
+
// It stays because the context type still admits a null.
|
|
30
|
+
...(ctx.organizationId === null ? {} : { organizationId: ctx.organizationId }),
|
|
31
|
+
status: 'active',
|
|
32
|
+
});
|
|
33
|
+
if (!cart)
|
|
34
|
+
return null;
|
|
35
|
+
return { cart: toCartRecord(cart), items: await this.#items(em, cart.id) };
|
|
36
|
+
}
|
|
37
|
+
async findById(cartId) {
|
|
38
|
+
const em = this.emFactory();
|
|
39
|
+
const cart = await em.findOne(Cart, { id: cartId });
|
|
40
|
+
if (!cart)
|
|
41
|
+
return null;
|
|
42
|
+
return { cart: toCartRecord(cart), items: await this.#items(em, cart.id) };
|
|
43
|
+
}
|
|
44
|
+
async #items(em, cartId) {
|
|
45
|
+
const items = await em.find(CartItem, { cartId }, { orderBy: { createdAt: 'asc', id: 'asc' } });
|
|
46
|
+
return items.map(toCartItemRecord);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The write port, adapting `CartService` and adding the seed operation.
|
|
51
|
+
*
|
|
52
|
+
* The service arrives as a getter so the adapter resolves it from the
|
|
53
|
+
* container per call rather than capturing it — it is this module's own gated
|
|
54
|
+
* port, and a singleton may not hold a gate.
|
|
55
|
+
*/
|
|
56
|
+
export function createCartWritePort(emFactory, getService) {
|
|
57
|
+
const read = new CartReadService(emFactory);
|
|
58
|
+
return {
|
|
59
|
+
async getOrCreateForCustomer(ctx) {
|
|
60
|
+
return toCartRecord(await getService().getOrCreateForCustomer(ctx));
|
|
61
|
+
},
|
|
62
|
+
async addItem(actor, input) {
|
|
63
|
+
const result = await getService().addItem(actor, input);
|
|
64
|
+
return { cart: toCartRecord(result.cart), items: result.items.map(toCartItemRecord) };
|
|
65
|
+
},
|
|
66
|
+
clearForCustomer: (ctx) => getService().clearForCustomer(ctx),
|
|
67
|
+
async replaceItemsForCustomer(ctx, lines) {
|
|
68
|
+
// command-coverage-ignore: seeds the customer's own active cart from an
|
|
69
|
+
// already-audited source (a reorder, an accepted quote). The cart is not
|
|
70
|
+
// an auditable domain object — the order or quote that produced these
|
|
71
|
+
// lines is, and its own Command has already recorded the decision.
|
|
72
|
+
const em = emFactory();
|
|
73
|
+
const cart = await getService().getOrCreateForCustomer(ctx);
|
|
74
|
+
const existing = await em.find(CartItem, { cartId: cart.id });
|
|
75
|
+
if (existing.length > 0)
|
|
76
|
+
em.remove(existing);
|
|
77
|
+
for (const line of lines) {
|
|
78
|
+
em.create(CartItem, {
|
|
79
|
+
cartId: cart.id,
|
|
80
|
+
productId: line.productId,
|
|
81
|
+
...(line.variantId ? { variantId: line.variantId } : {}),
|
|
82
|
+
quantity: line.quantity,
|
|
83
|
+
unitPrice: line.unitPrice,
|
|
84
|
+
currency: line.currency,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
cart.lastActivityAt = new Date();
|
|
88
|
+
await em.flush();
|
|
89
|
+
const seeded = await read.findById(cart.id);
|
|
90
|
+
// The cart was created or loaded one statement ago in this same
|
|
91
|
+
// `EntityManager`, so a miss here is not a race — it is impossible, and
|
|
92
|
+
// an empty answer would be worse than a throw because the caller is
|
|
93
|
+
// about to place an order from it.
|
|
94
|
+
if (!seeded) {
|
|
95
|
+
throw new Error(`[carts] cart ${cart.id} vanished between seeding and reading it back.`);
|
|
96
|
+
}
|
|
97
|
+
return seeded;
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export function toCartRecord(cart) {
|
|
102
|
+
return {
|
|
103
|
+
id: cart.id,
|
|
104
|
+
customerAccountId: cart.customerAccountId ?? null,
|
|
105
|
+
organizationId: cart.organizationId ?? null,
|
|
106
|
+
anonymousCartToken: cart.anonymousCartToken ?? null,
|
|
107
|
+
salesChannelId: cart.salesChannelId ?? null,
|
|
108
|
+
status: cart.status,
|
|
109
|
+
approvalStatus: cart.approvalStatus,
|
|
110
|
+
submittedForApprovalAt: cart.submittedForApprovalAt ?? null,
|
|
111
|
+
approvedAt: cart.approvedAt ?? null,
|
|
112
|
+
approvedByCustomerAccountId: cart.approvedByCustomerAccountId ?? null,
|
|
113
|
+
rejectedAt: cart.rejectedAt ?? null,
|
|
114
|
+
rejectedByActor: cart.rejectedByActor ?? null,
|
|
115
|
+
rejectedReason: cart.rejectedReason ?? null,
|
|
116
|
+
appliedPromotionCode: cart.appliedPromotionCode ?? null,
|
|
117
|
+
convertedToQuoteRequestId: cart.convertedToQuoteRequestId ?? null,
|
|
118
|
+
abandonmentNotifiedAt: cart.abandonmentNotifiedAt ?? null,
|
|
119
|
+
lastActivityAt: cart.lastActivityAt,
|
|
120
|
+
version: cart.version,
|
|
121
|
+
createdAt: cart.createdAt,
|
|
122
|
+
updatedAt: cart.updatedAt,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
export function toCartItemRecord(item) {
|
|
126
|
+
return {
|
|
127
|
+
id: item.id,
|
|
128
|
+
cartId: item.cartId,
|
|
129
|
+
productId: item.productId,
|
|
130
|
+
variantId: item.variantId ?? null,
|
|
131
|
+
quantity: item.quantity,
|
|
132
|
+
unitPrice: item.unitPrice,
|
|
133
|
+
currency: item.currency,
|
|
134
|
+
recomputedUnitPrice: item.recomputedUnitPrice ?? null,
|
|
135
|
+
recomputedAt: item.recomputedAt ?? null,
|
|
136
|
+
recomputedCurrency: item.recomputedCurrency ?? null,
|
|
137
|
+
packagingUnitId: item.packagingUnitId ?? null,
|
|
138
|
+
packagingUnitName: item.packagingUnitName ?? null,
|
|
139
|
+
packagingUnitBaseQuantity: item.packagingUnitBaseQuantity ?? null,
|
|
140
|
+
createdAt: item.createdAt,
|
|
141
|
+
updatedAt: item.updatedAt,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=cart-read-port.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cart-read-port.js","sourceRoot":"","sources":["../../../src/backend/services/cart-read-port.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Redis } from 'ioredis';
|
|
2
|
+
/**
|
|
3
|
+
* Redis-backed per-cart pricing-recompute cache (feature 027 §R5).
|
|
4
|
+
*
|
|
5
|
+
* Stores the most recently re-resolved line prices keyed by `cartId` so
|
|
6
|
+
* the mini-cart → full-cart → checkout-entry walk does not re-run the
|
|
7
|
+
* `PricingService.resolveLinePrice` resolver three times within seconds.
|
|
8
|
+
*
|
|
9
|
+
* Cache TTL is short (30 s by default) so a price-list edit propagates to
|
|
10
|
+
* the buyer's next page-open with predictable latency. Every cart-side
|
|
11
|
+
* write must call `invalidate(cartId)` so the next read sees fresh
|
|
12
|
+
* resolver output.
|
|
13
|
+
*
|
|
14
|
+
* The cache is **optional** — when Redis is unavailable the cache may be
|
|
15
|
+
* disabled (set `enabled: false` in the options) and the recompute helper
|
|
16
|
+
* still works (just resolves on every read).
|
|
17
|
+
*/
|
|
18
|
+
export declare const CART_RECOMPUTE_CACHE_KEY_PREFIX = "b2b:cart:recompute:";
|
|
19
|
+
export declare const CART_RECOMPUTE_CACHE_DEFAULT_TTL_SECONDS = 30;
|
|
20
|
+
export interface CartRecomputeCacheOptions {
|
|
21
|
+
ttlSeconds?: number;
|
|
22
|
+
enabled?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface CachedCartLinePrice {
|
|
25
|
+
cartItemId: string;
|
|
26
|
+
/** `amount: null` = resolver failed; the caller should fall back to the snapshot. */
|
|
27
|
+
unitPrice: {
|
|
28
|
+
amount: number | null;
|
|
29
|
+
currency: string;
|
|
30
|
+
};
|
|
31
|
+
resolvedAt: string;
|
|
32
|
+
}
|
|
33
|
+
export interface CachedCartRecompute {
|
|
34
|
+
cartId: string;
|
|
35
|
+
lines: CachedCartLinePrice[];
|
|
36
|
+
/** Wall-clock when the resolver was last asked. ISO-8601. */
|
|
37
|
+
resolvedAt: string;
|
|
38
|
+
}
|
|
39
|
+
export declare class CartRecomputeCache {
|
|
40
|
+
private readonly redis;
|
|
41
|
+
private readonly ttlSeconds;
|
|
42
|
+
private readonly enabled;
|
|
43
|
+
constructor(redis: Redis, options?: CartRecomputeCacheOptions);
|
|
44
|
+
static keyFor(cartId: string): string;
|
|
45
|
+
get(cartId: string): Promise<CachedCartRecompute | null>;
|
|
46
|
+
put(cartId: string, payload: CachedCartRecompute): Promise<void>;
|
|
47
|
+
invalidate(cartId: string): Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=cart-recompute-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cart-recompute-cache.d.ts","sourceRoot":"","sources":["../../../src/backend/services/cart-recompute-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,+BAA+B,wBAAwB,CAAC;AACrE,eAAO,MAAM,wCAAwC,KAAK,CAAC;AAE3D,MAAM,WAAW,yBAAyB;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,qFAAqF;IACrF,SAAS,EAAE;QAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACvD,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,mBAAmB,EAAE,CAAC;IAC7B,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,kBAAkB;IAIjB,OAAO,CAAC,QAAQ,CAAC,KAAK;IAHlC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;gBAEL,KAAK,EAAE,KAAK,EAAE,OAAO,GAAE,yBAA8B;IAKlF,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAI/B,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;IAYxD,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAUhE,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAIhD"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis-backed per-cart pricing-recompute cache (feature 027 §R5).
|
|
3
|
+
*
|
|
4
|
+
* Stores the most recently re-resolved line prices keyed by `cartId` so
|
|
5
|
+
* the mini-cart → full-cart → checkout-entry walk does not re-run the
|
|
6
|
+
* `PricingService.resolveLinePrice` resolver three times within seconds.
|
|
7
|
+
*
|
|
8
|
+
* Cache TTL is short (30 s by default) so a price-list edit propagates to
|
|
9
|
+
* the buyer's next page-open with predictable latency. Every cart-side
|
|
10
|
+
* write must call `invalidate(cartId)` so the next read sees fresh
|
|
11
|
+
* resolver output.
|
|
12
|
+
*
|
|
13
|
+
* The cache is **optional** — when Redis is unavailable the cache may be
|
|
14
|
+
* disabled (set `enabled: false` in the options) and the recompute helper
|
|
15
|
+
* still works (just resolves on every read).
|
|
16
|
+
*/
|
|
17
|
+
export const CART_RECOMPUTE_CACHE_KEY_PREFIX = 'b2b:cart:recompute:';
|
|
18
|
+
export const CART_RECOMPUTE_CACHE_DEFAULT_TTL_SECONDS = 30;
|
|
19
|
+
export class CartRecomputeCache {
|
|
20
|
+
redis;
|
|
21
|
+
ttlSeconds;
|
|
22
|
+
enabled;
|
|
23
|
+
constructor(redis, options = {}) {
|
|
24
|
+
this.redis = redis;
|
|
25
|
+
this.ttlSeconds = options.ttlSeconds ?? CART_RECOMPUTE_CACHE_DEFAULT_TTL_SECONDS;
|
|
26
|
+
this.enabled = (options.enabled ?? true) && this.ttlSeconds > 0;
|
|
27
|
+
}
|
|
28
|
+
static keyFor(cartId) {
|
|
29
|
+
return `${CART_RECOMPUTE_CACHE_KEY_PREFIX}${cartId}`;
|
|
30
|
+
}
|
|
31
|
+
async get(cartId) {
|
|
32
|
+
if (!this.enabled)
|
|
33
|
+
return null;
|
|
34
|
+
const raw = await this.redis.get(CartRecomputeCache.keyFor(cartId));
|
|
35
|
+
if (raw === null)
|
|
36
|
+
return null;
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(raw);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
await this.redis.del(CartRecomputeCache.keyFor(cartId));
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async put(cartId, payload) {
|
|
46
|
+
if (!this.enabled)
|
|
47
|
+
return;
|
|
48
|
+
await this.redis.set(CartRecomputeCache.keyFor(cartId), JSON.stringify(payload), 'EX', this.ttlSeconds);
|
|
49
|
+
}
|
|
50
|
+
async invalidate(cartId) {
|
|
51
|
+
if (!this.enabled)
|
|
52
|
+
return;
|
|
53
|
+
await this.redis.del(CartRecomputeCache.keyFor(cartId));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=cart-recompute-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cart-recompute-cache.js","sourceRoot":"","sources":["../../../src/backend/services/cart-recompute-cache.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,CAAC,MAAM,+BAA+B,GAAG,qBAAqB,CAAC;AACrE,MAAM,CAAC,MAAM,wCAAwC,GAAG,EAAE,CAAC;AAqB3D,MAAM,OAAO,kBAAkB;IAIA;IAHZ,UAAU,CAAS;IACnB,OAAO,CAAU;IAElC,YAA6B,KAAY,EAAE,UAAqC,EAAE;QAArD,UAAK,GAAL,KAAK,CAAO;QACvC,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,wCAAwC,CAAC;QACjF,IAAI,CAAC,OAAO,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;IAClE,CAAC;IAED,MAAM,CAAC,MAAM,CAAC,MAAc;QAC1B,OAAO,GAAG,+BAA+B,GAAG,MAAM,EAAE,CAAC;IACvD,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,MAAc;QACtB,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAC/B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,kBAAkB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QACpE,IAAI,GAAG,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAC9B,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAwB,CAAC;QAChD,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,kBAAkB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;YACxD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,MAAc,EAAE,OAA4B;QACpD,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAClB,kBAAkB,CAAC,MAAM,CAAC,MAAM,CAAC,EACjC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EACvB,IAAI,EACJ,IAAI,CAAC,UAAU,CAChB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,MAAc;QAC7B,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,kBAAkB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAC1D,CAAC;CACF"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
2
|
+
import { type CartMergeOutcome } from '@endora-commerce/contracts';
|
|
3
|
+
import { Cart } from '../entities/cart.entity.js';
|
|
4
|
+
import { CartItem } from '../entities/cart-item.entity.js';
|
|
5
|
+
import type { CatalogProductReadPort, LinePricePort, OrganizationDetailsPort } from '@endora-commerce/contracts';
|
|
6
|
+
import type { SalesChannelMembershipPort } from '@endora-commerce/platform/kernel';
|
|
7
|
+
import type { CartApprovalService } from './cart-approval-service.js';
|
|
8
|
+
import type { CartAuditService } from './cart-audit-service.js';
|
|
9
|
+
import type { CartRecomputeCache } from './cart-recompute-cache.js';
|
|
10
|
+
/**
|
|
11
|
+
* CartService (T125).
|
|
12
|
+
*
|
|
13
|
+
* Two cart shapes:
|
|
14
|
+
* - Anonymous: identified by a caller-held cookie token. No customer or
|
|
15
|
+
* organization attached.
|
|
16
|
+
* - Authenticated: the customer's single active cart.
|
|
17
|
+
*
|
|
18
|
+
* At login, the existing anonymous cart (if any) is merged into the
|
|
19
|
+
* authenticated cart — duplicate lines have their quantities summed.
|
|
20
|
+
*/
|
|
21
|
+
export interface AnonymousContext {
|
|
22
|
+
anonymousCartToken: string;
|
|
23
|
+
}
|
|
24
|
+
export interface CustomerContext {
|
|
25
|
+
customerAccountId: string;
|
|
26
|
+
/**
|
|
27
|
+
* Feature 026 US2 — null for guest-style Customer accounts that have no
|
|
28
|
+
* Organization. Carts already persist `organization_id` nullable, so the
|
|
29
|
+
* service handles either case (no-org carts fall back to platform-default
|
|
30
|
+
* prices via PricingService).
|
|
31
|
+
*/
|
|
32
|
+
organizationId: string | null;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Cart-line hard cap (feature 027 FR-001 / spec edge case "Cart contents
|
|
36
|
+
* cap"). The 201st distinct line is refused with HTTP 422.
|
|
37
|
+
*/
|
|
38
|
+
export declare const CART_MAX_LINES = 200;
|
|
39
|
+
export declare class CartService {
|
|
40
|
+
#private;
|
|
41
|
+
private readonly emFactory;
|
|
42
|
+
/** `price_lists`' line-resolution slice (feature 075, Phase C). */
|
|
43
|
+
private readonly pricingService;
|
|
44
|
+
/** `catalog`'s product read model — products, variants, packaging units. */
|
|
45
|
+
private readonly catalogProducts;
|
|
46
|
+
/** `organizations`' read model — the buying org the price resolves against. */
|
|
47
|
+
private readonly organizations;
|
|
48
|
+
/**
|
|
49
|
+
* The sanctioned bridge accessor (Constitution XII), for the assortment
|
|
50
|
+
* gate `addItem` owes (issue #259). Required, like `catalogProducts`: an
|
|
51
|
+
* optional one would make "this rig did not wire it" and "this channel
|
|
52
|
+
* publishes the product" the same answer, which is the fail-open shape the
|
|
53
|
+
* whole issue is about.
|
|
54
|
+
*/
|
|
55
|
+
private readonly channelMembership;
|
|
56
|
+
private readonly approvalService?;
|
|
57
|
+
private readonly auditService?;
|
|
58
|
+
private readonly recomputeCache?;
|
|
59
|
+
/**
|
|
60
|
+
* `pricingService` is **required** since issue #124. It used to be optional
|
|
61
|
+
* "for test rigs", and the cart then read the legacy
|
|
62
|
+
* `attributeValues['defaultPrice']` whenever it was missing — so a rig, a
|
|
63
|
+
* wiring slip and `price_lists` switched off all produced the same thing: a
|
|
64
|
+
* cart line priced from a catalogue attribute that no price list supports.
|
|
65
|
+
* Absence is now a `lazyPort` gate that throws `MODULE_DISABLED` at the call,
|
|
66
|
+
* which nothing downstream can mistake for a price.
|
|
67
|
+
*
|
|
68
|
+
* Cart entities don't currently track which sales channel they were
|
|
69
|
+
* created on (multi-channel cart attribution is a separate
|
|
70
|
+
* follow-up), so the resolver runs against the system-default
|
|
71
|
+
* channel — matching the behaviour the foundation cart already had.
|
|
72
|
+
* Lists with a `salesChannel` criterion targeting other channels
|
|
73
|
+
* therefore won't apply on the cart line until cart-side channel
|
|
74
|
+
* tracking lands.
|
|
75
|
+
*/
|
|
76
|
+
/**
|
|
77
|
+
* `approvalService` and `auditService` are optional — when wired
|
|
78
|
+
* (production composition), any buyer-driven mutation on an
|
|
79
|
+
* `approved` cart re-arms approval to `pending` and a typed audit
|
|
80
|
+
* row is landed (line_added / line_qty_changed / line_removed).
|
|
81
|
+
* Foundation tests and legacy test rigs that don't wire either still
|
|
82
|
+
* construct the service without them; both hooks no-op when absent.
|
|
83
|
+
*/
|
|
84
|
+
constructor(emFactory: () => EntityManager,
|
|
85
|
+
/** `price_lists`' line-resolution slice (feature 075, Phase C). */
|
|
86
|
+
pricingService: LinePricePort,
|
|
87
|
+
/** `catalog`'s product read model — products, variants, packaging units. */
|
|
88
|
+
catalogProducts: CatalogProductReadPort,
|
|
89
|
+
/** `organizations`' read model — the buying org the price resolves against. */
|
|
90
|
+
organizations: OrganizationDetailsPort,
|
|
91
|
+
/**
|
|
92
|
+
* The sanctioned bridge accessor (Constitution XII), for the assortment
|
|
93
|
+
* gate `addItem` owes (issue #259). Required, like `catalogProducts`: an
|
|
94
|
+
* optional one would make "this rig did not wire it" and "this channel
|
|
95
|
+
* publishes the product" the same answer, which is the fail-open shape the
|
|
96
|
+
* whole issue is about.
|
|
97
|
+
*/
|
|
98
|
+
channelMembership: SalesChannelMembershipPort, approvalService?: CartApprovalService | undefined, auditService?: CartAuditService | undefined, recomputeCache?: CartRecomputeCache | undefined);
|
|
99
|
+
getOrCreateForCustomer(ctx: CustomerContext): Promise<Cart>;
|
|
100
|
+
getOrCreateForAnon(em: EntityManager, token: string): Promise<Cart>;
|
|
101
|
+
getItems(cartId: string): Promise<CartItem[]>;
|
|
102
|
+
addItem(actor: {
|
|
103
|
+
customer?: CustomerContext;
|
|
104
|
+
anonymousToken?: string;
|
|
105
|
+
}, input: {
|
|
106
|
+
productId: string;
|
|
107
|
+
variantId?: string;
|
|
108
|
+
quantity: number;
|
|
109
|
+
packagingUnitId?: string;
|
|
110
|
+
}): Promise<{
|
|
111
|
+
cart: Cart;
|
|
112
|
+
items: CartItem[];
|
|
113
|
+
}>;
|
|
114
|
+
updateItem(actor: {
|
|
115
|
+
customer?: CustomerContext;
|
|
116
|
+
anonymousToken?: string;
|
|
117
|
+
}, itemId: string, quantity: number): Promise<{
|
|
118
|
+
cart: Cart;
|
|
119
|
+
items: CartItem[];
|
|
120
|
+
}>;
|
|
121
|
+
removeItem(actor: {
|
|
122
|
+
customer?: CustomerContext;
|
|
123
|
+
anonymousToken?: string;
|
|
124
|
+
}, itemId: string): Promise<{
|
|
125
|
+
cart: Cart;
|
|
126
|
+
items: CartItem[];
|
|
127
|
+
}>;
|
|
128
|
+
/**
|
|
129
|
+
* Explicit "this buyer just opened the cart page" ping (feature 027 §R4 /
|
|
130
|
+
* FR-032). Bumps `last_activity_at` and, if the cart had transitioned to
|
|
131
|
+
* `abandoned`, brings it back to `active` and clears
|
|
132
|
+
* `abandonment_notified_at` so the next abandonment cycle can notify
|
|
133
|
+
* again. No-op on a missing cart.
|
|
134
|
+
*/
|
|
135
|
+
touch(actor: {
|
|
136
|
+
customer?: CustomerContext;
|
|
137
|
+
anonymousToken?: string;
|
|
138
|
+
}): Promise<Cart | null>;
|
|
139
|
+
/**
|
|
140
|
+
* Merge an anonymous cart's items into the customer's cart at login
|
|
141
|
+
* time (feature 037-cart-merge-on-login).
|
|
142
|
+
*
|
|
143
|
+
* Returns a `CartMergeOutcome` diagnostic the caller (the login route
|
|
144
|
+
* via the `onLogin` hook) uses to surface the result to the storefront
|
|
145
|
+
* and to decide whether to clear the stale `b2b_cart_anon` cookie.
|
|
146
|
+
* See specs/037-cart-merge-on-login/contracts/cart-merge.md § 3.
|
|
147
|
+
*
|
|
148
|
+
* State matrix:
|
|
149
|
+
* - `adopted` — the customer had no active cart; the anon
|
|
150
|
+
* cart's lines were placed onto a freshly-created destination.
|
|
151
|
+
* - `merged` — the customer already had an active cart and
|
|
152
|
+
* ≥ 1 anon line was moved or summed into it.
|
|
153
|
+
* - `noop_empty` — the anon cart was found but carried no items;
|
|
154
|
+
* the source row is still flipped to `completed` + detokenised
|
|
155
|
+
* (cleanup), no audit row is written.
|
|
156
|
+
* - `noop_no_anon` — no cart for the supplied token; no DB change.
|
|
157
|
+
*
|
|
158
|
+
* Coupons on the source cart are NOT carried to the destination
|
|
159
|
+
* (R-05 / FR-010 — the destination's existing `appliedPromotionCode`
|
|
160
|
+
* is preserved verbatim).
|
|
161
|
+
*/
|
|
162
|
+
mergeAnonymousIntoCustomer(anonymousToken: string, ctx: CustomerContext): Promise<CartMergeOutcome>;
|
|
163
|
+
clearForCustomer(ctx: CustomerContext): Promise<void>;
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=cart-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cart-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/cart-service.ts"],"names":[],"mappings":"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"}
|