@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,98 @@
|
|
|
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
|
+
export class CartOrganizationVisibilityService {
|
|
6
|
+
emFactory;
|
|
7
|
+
customerAccounts;
|
|
8
|
+
constructor(emFactory,
|
|
9
|
+
/**
|
|
10
|
+
* `customer_accounts`' read model (feature 075, Phase C). The org-admin
|
|
11
|
+
* cart list renders each cart's owner by e-mail, and it read the account
|
|
12
|
+
* row itself — out of a table deactivation leaves in place.
|
|
13
|
+
*/
|
|
14
|
+
customerAccounts) {
|
|
15
|
+
this.emFactory = emFactory;
|
|
16
|
+
this.customerAccounts = customerAccounts;
|
|
17
|
+
}
|
|
18
|
+
async list(organizationId, query = {}) {
|
|
19
|
+
const em = this.emFactory();
|
|
20
|
+
const where = { organizationId };
|
|
21
|
+
if (query.status && query.status.length > 0)
|
|
22
|
+
where['status'] = { $in: query.status };
|
|
23
|
+
if (query.approvalStatus && query.approvalStatus.length > 0) {
|
|
24
|
+
where['approvalStatus'] = { $in: query.approvalStatus };
|
|
25
|
+
}
|
|
26
|
+
const page = query.page ?? 1;
|
|
27
|
+
const pageSize = Math.min(query.pageSize ?? 50, 100);
|
|
28
|
+
const orderBy = this.sortClause(query.sort ?? 'last_activity_desc');
|
|
29
|
+
const [rows, totalCount] = await em.findAndCount(Cart, where, {
|
|
30
|
+
orderBy,
|
|
31
|
+
limit: pageSize,
|
|
32
|
+
offset: (page - 1) * pageSize,
|
|
33
|
+
});
|
|
34
|
+
const ownerIds = Array.from(new Set(rows.map((r) => r.customerAccountId).filter((id) => Boolean(id))));
|
|
35
|
+
const owners = await this.customerAccounts.findByIds(ownerIds);
|
|
36
|
+
const ownerById = new Map(owners.map((o) => [o.id, o]));
|
|
37
|
+
// Bulk item-count + total resolution.
|
|
38
|
+
const cartIds = rows.map((r) => r.id);
|
|
39
|
+
const itemCounts = new Map();
|
|
40
|
+
if (cartIds.length > 0) {
|
|
41
|
+
const items = await em.find(CartItem, { cartId: { $in: cartIds } });
|
|
42
|
+
for (const it of items) {
|
|
43
|
+
const prev = itemCounts.get(it.cartId) ?? { count: 0, total: 0, currency: it.currency };
|
|
44
|
+
prev.count += 1;
|
|
45
|
+
prev.total += Number(it.unitPrice) * it.quantity;
|
|
46
|
+
itemCounts.set(it.cartId, prev);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
data: rows
|
|
51
|
+
.filter((r) => r.customerAccountId !== null && r.customerAccountId !== undefined)
|
|
52
|
+
.map((cart) => {
|
|
53
|
+
const ic = itemCounts.get(cart.id) ?? { count: 0, total: 0, currency: 'PLN' };
|
|
54
|
+
const owner = ownerById.get(cart.customerAccountId);
|
|
55
|
+
return {
|
|
56
|
+
id: cart.id,
|
|
57
|
+
ownerCustomerAccountId: cart.customerAccountId,
|
|
58
|
+
ownerDisplayName: owner?.email ?? cart.customerAccountId,
|
|
59
|
+
status: cart.status,
|
|
60
|
+
approvalStatus: cart.approvalStatus,
|
|
61
|
+
itemCount: ic.count,
|
|
62
|
+
total: { amount: ic.total, currency: ic.currency },
|
|
63
|
+
lastActivityAt: cart.lastActivityAt.toISOString(),
|
|
64
|
+
submittedForApprovalAt: cart.submittedForApprovalAt?.toISOString() ?? null,
|
|
65
|
+
};
|
|
66
|
+
}),
|
|
67
|
+
meta: { page, pageSize, totalCount },
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Fetch a single cart in the caller's Organization. Refuses with 404
|
|
72
|
+
* when the cart belongs to a different Organization (anti-enumeration
|
|
73
|
+
* symmetry with the ordinary-member ownership check).
|
|
74
|
+
*/
|
|
75
|
+
async getInOrganization(cartId, organizationId) {
|
|
76
|
+
const em = this.emFactory();
|
|
77
|
+
const cart = await em.findOne(Cart, { id: cartId });
|
|
78
|
+
if (!cart || cart.organizationId !== organizationId) {
|
|
79
|
+
throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'cart_not_found');
|
|
80
|
+
}
|
|
81
|
+
return cart;
|
|
82
|
+
}
|
|
83
|
+
sortClause(sort) {
|
|
84
|
+
switch (sort) {
|
|
85
|
+
case 'last_activity_asc':
|
|
86
|
+
return { lastActivityAt: 'asc' };
|
|
87
|
+
case 'total_desc':
|
|
88
|
+
case 'total_asc':
|
|
89
|
+
return { updatedAt: sort === 'total_desc' ? 'desc' : 'asc' };
|
|
90
|
+
case 'created_desc':
|
|
91
|
+
return { createdAt: 'desc' };
|
|
92
|
+
case 'last_activity_desc':
|
|
93
|
+
default:
|
|
94
|
+
return { lastActivityAt: 'desc' };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=cart-organization-visibility-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cart-organization-visibility-service.js","sourceRoot":"","sources":["../../../src/backend/services/cart-organization-visibility-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
2
|
+
import type { CartCustomerContext, CartWithItems } from '@endora-commerce/contracts';
|
|
3
|
+
import type { CartPlacementApplyPort } from '../../ports/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* `cartPlacementApplyPort` — the basket half of order placement, run on the
|
|
6
|
+
* caller's `EntityManager` (feature 080, T048; D-169).
|
|
7
|
+
*
|
|
8
|
+
* `orders` held `Cart` and `CartItem` itself until T048: `placeOrder` read the
|
|
9
|
+
* basket on its own transaction, built the order out of it, and ended by
|
|
10
|
+
* deleting the lines, moving the cart to `completed` and stamping
|
|
11
|
+
* `completed_order_id` — four statements against this module's two tables,
|
|
12
|
+
* written by a module that owns neither. D-168 leaves a packaged `carts` no
|
|
13
|
+
* entity class for `orders` to name, so the reach had to go before this module
|
|
14
|
+
* moves.
|
|
15
|
+
*
|
|
16
|
+
* **The transaction is unchanged, and that is the point of the conversion.**
|
|
17
|
+
* The read and the completion still run on placement's own `EntityManager`;
|
|
18
|
+
* `carts_completed_order_fk` is untouched; a placement that fails still leaves
|
|
19
|
+
* the basket exactly as the buyer left it, which
|
|
20
|
+
* `test/integration/orders/place-order-failure-preserves-cart.test.ts` asserts
|
|
21
|
+
* and which a port opening its own transaction could not deliver. What changed
|
|
22
|
+
* is that the statements are written by the module that owns the tables, and
|
|
23
|
+
* what crosses the boundary is `CartWithItems` — published records — instead of
|
|
24
|
+
* two managed entities the consumer could move any column of (D-77's first
|
|
25
|
+
* narrowing).
|
|
26
|
+
*
|
|
27
|
+
* The port's own doc block, in `../ports/index.ts`, states the constraint and
|
|
28
|
+
* why the read takes an `EntityManager` where `CartReadPort.findActiveForCustomer`
|
|
29
|
+
* does not.
|
|
30
|
+
*/
|
|
31
|
+
export declare class CartPlacementApplyService implements CartPlacementApplyPort {
|
|
32
|
+
readActiveForPlacement(em: EntityManager, ctx: CartCustomerContext): Promise<CartWithItems | null>;
|
|
33
|
+
completeForOrder(em: EntityManager, input: {
|
|
34
|
+
cartId: string;
|
|
35
|
+
orderId: string;
|
|
36
|
+
}): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=cart-placement-apply-port.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cart-placement-apply-port.d.ts","sourceRoot":"","sources":["../../../src/backend/services/cart-placement-apply-port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAGrF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAGnE;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,yBAA0B,YAAW,sBAAsB;IAChE,sBAAsB,CAC1B,EAAE,EAAE,aAAa,EACjB,GAAG,EAAE,mBAAmB,GACvB,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAgB1B,gBAAgB,CACpB,EAAE,EAAE,aAAa,EACjB,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GACzC,OAAO,CAAC,IAAI,CAAC;CAqBjB"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Cart } from '../entities/cart.entity.js';
|
|
2
|
+
import { CartItem } from '../entities/cart-item.entity.js';
|
|
3
|
+
import { toCartItemRecord, toCartRecord } from './cart-read-port.js';
|
|
4
|
+
/**
|
|
5
|
+
* `cartPlacementApplyPort` — the basket half of order placement, run on the
|
|
6
|
+
* caller's `EntityManager` (feature 080, T048; D-169).
|
|
7
|
+
*
|
|
8
|
+
* `orders` held `Cart` and `CartItem` itself until T048: `placeOrder` read the
|
|
9
|
+
* basket on its own transaction, built the order out of it, and ended by
|
|
10
|
+
* deleting the lines, moving the cart to `completed` and stamping
|
|
11
|
+
* `completed_order_id` — four statements against this module's two tables,
|
|
12
|
+
* written by a module that owns neither. D-168 leaves a packaged `carts` no
|
|
13
|
+
* entity class for `orders` to name, so the reach had to go before this module
|
|
14
|
+
* moves.
|
|
15
|
+
*
|
|
16
|
+
* **The transaction is unchanged, and that is the point of the conversion.**
|
|
17
|
+
* The read and the completion still run on placement's own `EntityManager`;
|
|
18
|
+
* `carts_completed_order_fk` is untouched; a placement that fails still leaves
|
|
19
|
+
* the basket exactly as the buyer left it, which
|
|
20
|
+
* `test/integration/orders/place-order-failure-preserves-cart.test.ts` asserts
|
|
21
|
+
* and which a port opening its own transaction could not deliver. What changed
|
|
22
|
+
* is that the statements are written by the module that owns the tables, and
|
|
23
|
+
* what crosses the boundary is `CartWithItems` — published records — instead of
|
|
24
|
+
* two managed entities the consumer could move any column of (D-77's first
|
|
25
|
+
* narrowing).
|
|
26
|
+
*
|
|
27
|
+
* The port's own doc block, in `../ports/index.ts`, states the constraint and
|
|
28
|
+
* why the read takes an `EntityManager` where `CartReadPort.findActiveForCustomer`
|
|
29
|
+
* does not.
|
|
30
|
+
*/
|
|
31
|
+
export class CartPlacementApplyService {
|
|
32
|
+
async readActiveForPlacement(em, ctx) {
|
|
33
|
+
// The same conditional org predicate `CartReadService` applies, and for the
|
|
34
|
+
// same reason: `carts.organization_id` is nullable for a customer account
|
|
35
|
+
// that has none, so a caller with no organisation must not be narrowed out
|
|
36
|
+
// of its own basket. `Cart` is `@CustomerScoped`, so the tenant guard is on
|
|
37
|
+
// the customer either way.
|
|
38
|
+
const cart = await em.findOne(Cart, {
|
|
39
|
+
customerAccountId: ctx.customerAccountId,
|
|
40
|
+
...(ctx.organizationId === null ? {} : { organizationId: ctx.organizationId }),
|
|
41
|
+
status: 'active',
|
|
42
|
+
});
|
|
43
|
+
if (!cart)
|
|
44
|
+
return null;
|
|
45
|
+
const items = await em.find(CartItem, { cartId: cart.id });
|
|
46
|
+
return { cart: toCartRecord(cart), items: items.map(toCartItemRecord) };
|
|
47
|
+
}
|
|
48
|
+
async completeForOrder(em, input) {
|
|
49
|
+
// command-coverage-ignore: the basket half of an order placement, inside
|
|
50
|
+
// the caller's transaction. `orders` audits the placement as one operation
|
|
51
|
+
// through its own Command; a second audit entry for the basket the order
|
|
52
|
+
// came from would record the same decision twice, and the cart is not an
|
|
53
|
+
// auditable domain object in its own right — the same reasoning
|
|
54
|
+
// `cartWritePort.replaceItemsForCustomer` carries.
|
|
55
|
+
const cart = await em.findOne(Cart, { id: input.cartId });
|
|
56
|
+
if (!cart)
|
|
57
|
+
return;
|
|
58
|
+
await em.nativeDelete(CartItem, { cartId: cart.id });
|
|
59
|
+
// Feature 027's renamed status vocabulary: the cart produced an order, so
|
|
60
|
+
// its lifecycle terminates in `completed`.
|
|
61
|
+
cart.status = 'completed';
|
|
62
|
+
// Which order emptied this cart (D-94.1), held by `carts_completed_order_fk`.
|
|
63
|
+
cart.completedOrderId = input.orderId;
|
|
64
|
+
// Always null on an authenticated checkout today, but cleared for the edge
|
|
65
|
+
// case where a customer checks out from an anon-derived cart that still
|
|
66
|
+
// carries the token.
|
|
67
|
+
cart.anonymousCartToken = null;
|
|
68
|
+
await em.flush();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=cart-placement-apply-port.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cart-placement-apply-port.js","sourceRoot":"","sources":["../../../src/backend/services/cart-placement-apply-port.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAE,MAAM,4BAA4B,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAE3D,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAErE;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,OAAO,yBAAyB;IACpC,KAAK,CAAC,sBAAsB,CAC1B,EAAiB,EACjB,GAAwB;QAExB,4EAA4E;QAC5E,0EAA0E;QAC1E,2EAA2E;QAC3E,4EAA4E;QAC5E,2BAA2B;QAC3B,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE;YAClC,iBAAiB,EAAE,GAAG,CAAC,iBAAiB;YACxC,GAAG,CAAC,GAAG,CAAC,cAAc,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,GAAG,CAAC,cAAc,EAAE,CAAC;YAC9E,MAAM,EAAE,QAAQ;SACjB,CAAC,CAAC;QACH,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QACvB,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;QAC3D,OAAO,EAAE,IAAI,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,EAAE,CAAC;IAC1E,CAAC;IAED,KAAK,CAAC,gBAAgB,CACpB,EAAiB,EACjB,KAA0C;QAE1C,yEAAyE;QACzE,2EAA2E;QAC3E,yEAAyE;QACzE,yEAAyE;QACzE,gEAAgE;QAChE,mDAAmD;QACnD,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QAC1D,IAAI,CAAC,IAAI;YAAE,OAAO;QAClB,MAAM,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;QACrD,0EAA0E;QAC1E,2CAA2C;QAC3C,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC;QAC1B,8EAA8E;QAC9E,IAAI,CAAC,gBAAgB,GAAG,KAAK,CAAC,OAAO,CAAC;QACtC,2EAA2E;QAC3E,wEAAwE;QACxE,qBAAqB;QACrB,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;QAC/B,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;IACnB,CAAC;CACF"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
2
|
+
import type { CatalogProductReadPort, LinePricePort, OrganizationDetailsPort } from '@endora-commerce/contracts';
|
|
3
|
+
import type { CartRecomputeCache } from './cart-recompute-cache.js';
|
|
4
|
+
import type { CartItem } from '../entities/cart-item.entity.js';
|
|
5
|
+
/**
|
|
6
|
+
* Per-cart pricing-recompute helper (feature 027 §R5).
|
|
7
|
+
*
|
|
8
|
+
* Re-resolves the unit price of every cart line against the customer's
|
|
9
|
+
* current resolved price list. The cache (when available) lets the
|
|
10
|
+
* mini-cart → full-cart → checkout-entry walk reuse the same resolver
|
|
11
|
+
* output for 30 s.
|
|
12
|
+
*
|
|
13
|
+
* Inputs: the line set is the list of `CartItem` rows for one cart.
|
|
14
|
+
* Outputs: per-line `{cartItemId, amount, currency}` — `amount` is null
|
|
15
|
+
* when the resolver returned null (no eligible price-list bracket for
|
|
16
|
+
* this customer; the cart UI surfaces a `no_price_in_customer_list`
|
|
17
|
+
* unavailability reason for the line).
|
|
18
|
+
*
|
|
19
|
+
* Implementation note: PricingService is per-line today (no batched
|
|
20
|
+
* variant). The cache amortizes work across reads; a future optimization
|
|
21
|
+
* may batch into a single PricingService call when the resolver gains a
|
|
22
|
+
* batch endpoint.
|
|
23
|
+
*/
|
|
24
|
+
export interface RecomputeLineRequest {
|
|
25
|
+
cartItemId: string;
|
|
26
|
+
productId: string;
|
|
27
|
+
variantId?: string | null;
|
|
28
|
+
quantity: number;
|
|
29
|
+
}
|
|
30
|
+
export interface RecomputedLinePrice {
|
|
31
|
+
cartItemId: string;
|
|
32
|
+
amount: number | null;
|
|
33
|
+
currency: string;
|
|
34
|
+
}
|
|
35
|
+
export interface CartPricingRecomputeContext {
|
|
36
|
+
cartId: string;
|
|
37
|
+
organizationId: string | null;
|
|
38
|
+
salesChannelId: string | null;
|
|
39
|
+
}
|
|
40
|
+
export declare class CartPricingRecompute {
|
|
41
|
+
private readonly emFactory;
|
|
42
|
+
/** `price_lists`' line-resolution slice (feature 075, Phase C). */
|
|
43
|
+
private readonly pricingService;
|
|
44
|
+
private readonly cache;
|
|
45
|
+
/** `catalog`'s product read model — the two fields the resolver reads. */
|
|
46
|
+
private readonly catalogProducts;
|
|
47
|
+
/** `organizations`' read model — the buying org and its customer group. */
|
|
48
|
+
private readonly organizations;
|
|
49
|
+
constructor(emFactory: () => EntityManager,
|
|
50
|
+
/** `price_lists`' line-resolution slice (feature 075, Phase C). */
|
|
51
|
+
pricingService: LinePricePort, cache: CartRecomputeCache,
|
|
52
|
+
/** `catalog`'s product read model — the two fields the resolver reads. */
|
|
53
|
+
catalogProducts: CatalogProductReadPort,
|
|
54
|
+
/** `organizations`' read model — the buying org and its customer group. */
|
|
55
|
+
organizations: OrganizationDetailsPort);
|
|
56
|
+
recompute(ctx: CartPricingRecomputeContext, lines: RecomputeLineRequest[]): Promise<RecomputedLinePrice[]>;
|
|
57
|
+
/** Persist the most recently recomputed prices back onto `cart_items`. */
|
|
58
|
+
writeBackTo(em: EntityManager, items: CartItem[], prices: RecomputedLinePrice[]): Promise<void>;
|
|
59
|
+
private allLinesCached;
|
|
60
|
+
private loadChannel;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=cart-pricing-recompute.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cart-pricing-recompute.d.ts","sourceRoot":"","sources":["../../../src/backend/services/cart-pricing-recompute.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EACV,sBAAsB,EACtB,aAAa,EACb,uBAAuB,EACxB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAE,kBAAkB,EAAuB,MAAM,2BAA2B,CAAC;AACzF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAEhE;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,2BAA2B;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,qBAAa,oBAAoB;IAE7B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,mEAAmE;IACnE,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,2EAA2E;IAC3E,OAAO,CAAC,QAAQ,CAAC,aAAa;gBAPb,SAAS,EAAE,MAAM,aAAa;IAC/C,mEAAmE;IAClD,cAAc,EAAE,aAAa,EAC7B,KAAK,EAAE,kBAAkB;IAC1C,0EAA0E;IACzD,eAAe,EAAE,sBAAsB;IACxD,2EAA2E;IAC1D,aAAa,EAAE,uBAAuB;IAGnD,SAAS,CACb,GAAG,EAAE,2BAA2B,EAChC,KAAK,EAAE,oBAAoB,EAAE,GAC5B,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAyFjC,0EAA0E;IACpE,WAAW,CAAC,EAAE,EAAE,aAAa,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,mBAAmB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAerG,OAAO,CAAC,cAAc;YAKR,WAAW;CAM1B"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { SalesChannel } from '@endora-commerce/platform/kernel';
|
|
2
|
+
export class CartPricingRecompute {
|
|
3
|
+
emFactory;
|
|
4
|
+
pricingService;
|
|
5
|
+
cache;
|
|
6
|
+
catalogProducts;
|
|
7
|
+
organizations;
|
|
8
|
+
constructor(emFactory,
|
|
9
|
+
/** `price_lists`' line-resolution slice (feature 075, Phase C). */
|
|
10
|
+
pricingService, cache,
|
|
11
|
+
/** `catalog`'s product read model — the two fields the resolver reads. */
|
|
12
|
+
catalogProducts,
|
|
13
|
+
/** `organizations`' read model — the buying org and its customer group. */
|
|
14
|
+
organizations) {
|
|
15
|
+
this.emFactory = emFactory;
|
|
16
|
+
this.pricingService = pricingService;
|
|
17
|
+
this.cache = cache;
|
|
18
|
+
this.catalogProducts = catalogProducts;
|
|
19
|
+
this.organizations = organizations;
|
|
20
|
+
}
|
|
21
|
+
async recompute(ctx, lines) {
|
|
22
|
+
if (lines.length === 0)
|
|
23
|
+
return [];
|
|
24
|
+
// Cache hit returns the previously resolved prices in their original
|
|
25
|
+
// order (the cache stores keyed by cartItemId so any line removed
|
|
26
|
+
// since the last resolve is naturally dropped on the next read).
|
|
27
|
+
// `unitPrice.amount === null` in the cache means the resolver
|
|
28
|
+
// previously returned no eligible price; we propagate the null so
|
|
29
|
+
// the caller falls back to the snapshot.
|
|
30
|
+
const cached = await this.cache.get(ctx.cartId);
|
|
31
|
+
if (cached && this.allLinesCached(cached, lines)) {
|
|
32
|
+
return lines.map((line) => {
|
|
33
|
+
const hit = cached.lines.find((c) => c.cartItemId === line.cartItemId);
|
|
34
|
+
if (!hit) {
|
|
35
|
+
return { cartItemId: line.cartItemId, amount: null, currency: 'PLN' };
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
cartItemId: line.cartItemId,
|
|
39
|
+
amount: hit.unitPrice.amount,
|
|
40
|
+
currency: hit.unitPrice.currency,
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// Cache miss → resolve from scratch. We load the channel + org once
|
|
45
|
+
// (per-cart) rather than per-line.
|
|
46
|
+
const em = this.emFactory();
|
|
47
|
+
const channel = await this.loadChannel(em, ctx.salesChannelId);
|
|
48
|
+
const organization = ctx.organizationId
|
|
49
|
+
? await this.organizations.findById(ctx.organizationId)
|
|
50
|
+
: null;
|
|
51
|
+
if (!channel) {
|
|
52
|
+
return lines.map((line) => ({ cartItemId: line.cartItemId, amount: null, currency: 'PLN' }));
|
|
53
|
+
}
|
|
54
|
+
const productIds = Array.from(new Set(lines.map((l) => l.productId)));
|
|
55
|
+
const products = await this.catalogProducts.findByIds(productIds);
|
|
56
|
+
const productById = new Map(products.map((p) => [p.id, p]));
|
|
57
|
+
const recomputed = [];
|
|
58
|
+
for (const line of lines) {
|
|
59
|
+
const product = productById.get(line.productId);
|
|
60
|
+
if (!product) {
|
|
61
|
+
recomputed.push({ cartItemId: line.cartItemId, amount: null, currency: 'PLN' });
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
// No `catch` (issue #84): `resolveLinePrice` already answers "no resolver
|
|
65
|
+
// match" with `null`, which is the `amount: null` this loop wants, and
|
|
66
|
+
// that answer then goes into the recompute cache below. A `catch` here
|
|
67
|
+
// cached "no price" for the whole cart whenever `price_lists` was
|
|
68
|
+
// switched off, and the TTL kept doing it after it came back.
|
|
69
|
+
const resolved = await this.pricingService.resolveLinePrice({
|
|
70
|
+
product,
|
|
71
|
+
variantId: line.variantId ?? null,
|
|
72
|
+
context: {
|
|
73
|
+
quantity: line.quantity,
|
|
74
|
+
...(organization ? { organization } : {}),
|
|
75
|
+
salesChannel: channel,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
if (!resolved) {
|
|
79
|
+
recomputed.push({ cartItemId: line.cartItemId, amount: null, currency: 'PLN' });
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
recomputed.push({
|
|
83
|
+
cartItemId: line.cartItemId,
|
|
84
|
+
amount: Number(resolved.amount),
|
|
85
|
+
currency: resolved.currency,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Write the resolved set to the cache so subsequent reads within the
|
|
90
|
+
// TTL skip the resolver entirely. `amount: null` is preserved so the
|
|
91
|
+
// cache hit correctly signals "no resolver match — use snapshot".
|
|
92
|
+
const payload = {
|
|
93
|
+
cartId: ctx.cartId,
|
|
94
|
+
lines: recomputed.map((r) => ({
|
|
95
|
+
cartItemId: r.cartItemId,
|
|
96
|
+
unitPrice: { amount: r.amount, currency: r.currency },
|
|
97
|
+
resolvedAt: new Date().toISOString(),
|
|
98
|
+
})),
|
|
99
|
+
resolvedAt: new Date().toISOString(),
|
|
100
|
+
};
|
|
101
|
+
await this.cache.put(ctx.cartId, payload);
|
|
102
|
+
return recomputed;
|
|
103
|
+
}
|
|
104
|
+
/** Persist the most recently recomputed prices back onto `cart_items`. */
|
|
105
|
+
async writeBackTo(em, items, prices) {
|
|
106
|
+
// command-coverage-ignore: writes derived (recomputed) line prices back to the
|
|
107
|
+
// ephemeral cart — transient working state, not an audited domain mutation.
|
|
108
|
+
const byId = new Map(prices.map((p) => [p.cartItemId, p]));
|
|
109
|
+
const now = new Date();
|
|
110
|
+
for (const item of items) {
|
|
111
|
+
const price = byId.get(item.id);
|
|
112
|
+
if (!price || price.amount === null)
|
|
113
|
+
continue;
|
|
114
|
+
item.recomputedUnitPrice = price.amount.toFixed(2);
|
|
115
|
+
item.recomputedAt = now;
|
|
116
|
+
item.recomputedCurrency = price.currency;
|
|
117
|
+
}
|
|
118
|
+
await em.flush();
|
|
119
|
+
}
|
|
120
|
+
allLinesCached(cached, lines) {
|
|
121
|
+
const cachedIds = new Set(cached.lines.map((l) => l.cartItemId));
|
|
122
|
+
return lines.every((l) => cachedIds.has(l.cartItemId));
|
|
123
|
+
}
|
|
124
|
+
async loadChannel(em, channelId) {
|
|
125
|
+
if (channelId) {
|
|
126
|
+
return em.findOne(SalesChannel, { id: channelId });
|
|
127
|
+
}
|
|
128
|
+
return em.findOne(SalesChannel, { systemDefault: true });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=cart-pricing-recompute.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cart-pricing-recompute.js","sourceRoot":"","sources":["../../../src/backend/services/cart-pricing-recompute.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
2
|
+
/**
|
|
3
|
+
* CartQueryService — read-only cart lookups by customer (feature 040, US5 /
|
|
4
|
+
* FR-030/FR-031). Surfaces the customer's current (active) cart and their
|
|
5
|
+
* abandoned carts for the admin customer-detail panels.
|
|
6
|
+
*/
|
|
7
|
+
export interface CartItemView {
|
|
8
|
+
productId: string;
|
|
9
|
+
variantId: string | null;
|
|
10
|
+
quantity: number;
|
|
11
|
+
unitPrice: string;
|
|
12
|
+
currency: string;
|
|
13
|
+
}
|
|
14
|
+
export interface CartView {
|
|
15
|
+
id: string;
|
|
16
|
+
status: string;
|
|
17
|
+
lastActivityAt: string | null;
|
|
18
|
+
items: CartItemView[];
|
|
19
|
+
}
|
|
20
|
+
export interface CustomerCartsView {
|
|
21
|
+
current: CartView | null;
|
|
22
|
+
abandoned: CartView[];
|
|
23
|
+
}
|
|
24
|
+
export declare class CartQueryService {
|
|
25
|
+
private readonly emFactory;
|
|
26
|
+
constructor(emFactory: () => EntityManager);
|
|
27
|
+
listForCustomer(customerAccountId: string): Promise<CustomerCartsView>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=cart-query-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cart-query-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/cart-query-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAI3D;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,QAAQ,EAAE,CAAC;CACvB;AAED,qBAAa,gBAAgB;IACf,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,MAAM,aAAa;IAErD,eAAe,CAAC,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAqC7E"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Cart } from '../entities/cart.entity.js';
|
|
2
|
+
import { CartItem } from '../entities/cart-item.entity.js';
|
|
3
|
+
export class CartQueryService {
|
|
4
|
+
emFactory;
|
|
5
|
+
constructor(emFactory) {
|
|
6
|
+
this.emFactory = emFactory;
|
|
7
|
+
}
|
|
8
|
+
async listForCustomer(customerAccountId) {
|
|
9
|
+
const em = this.emFactory();
|
|
10
|
+
const carts = await em.find(Cart, { customerAccountId }, { orderBy: { lastActivityAt: 'desc' } });
|
|
11
|
+
if (carts.length === 0)
|
|
12
|
+
return { current: null, abandoned: [] };
|
|
13
|
+
const items = await em.find(CartItem, { cartId: { $in: carts.map((c) => c.id) } });
|
|
14
|
+
const byCart = new Map();
|
|
15
|
+
for (const it of items) {
|
|
16
|
+
const list = byCart.get(it.cartId) ?? [];
|
|
17
|
+
list.push({
|
|
18
|
+
productId: it.productId,
|
|
19
|
+
variantId: it.variantId ?? null,
|
|
20
|
+
quantity: it.quantity,
|
|
21
|
+
unitPrice: it.unitPrice,
|
|
22
|
+
currency: it.currency,
|
|
23
|
+
});
|
|
24
|
+
byCart.set(it.cartId, list);
|
|
25
|
+
}
|
|
26
|
+
const toView = (c) => ({
|
|
27
|
+
id: c.id,
|
|
28
|
+
status: c.status,
|
|
29
|
+
lastActivityAt: c.lastActivityAt ? c.lastActivityAt.toISOString() : null,
|
|
30
|
+
items: byCart.get(c.id) ?? [],
|
|
31
|
+
});
|
|
32
|
+
const current = carts.find((c) => c.status === 'active') ?? null;
|
|
33
|
+
const abandoned = carts.filter((c) => c.status === 'abandoned');
|
|
34
|
+
return {
|
|
35
|
+
current: current ? toView(current) : null,
|
|
36
|
+
abandoned: abandoned.map(toView),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=cart-query-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cart-query-service.js","sourceRoot":"","sources":["../../../src/backend/services/cart-query-service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,4BAA4B,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AA2B3D,MAAM,OAAO,gBAAgB;IACE;IAA7B,YAA6B,SAA8B;QAA9B,cAAS,GAAT,SAAS,CAAqB;IAAG,CAAC;IAE/D,KAAK,CAAC,eAAe,CAAC,iBAAyB;QAC7C,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,IAAI,CACzB,IAAI,EACJ,EAAE,iBAAiB,EAAE,EACrB,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,EAAE,CACxC,CAAC;QACF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;QAEhE,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QACnF,MAAM,MAAM,GAAG,IAAI,GAAG,EAA0B,CAAC;QACjD,KAAK,MAAM,EAAE,IAAI,KAAK,EAAE,CAAC;YACvB,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACzC,IAAI,CAAC,IAAI,CAAC;gBACR,SAAS,EAAE,EAAE,CAAC,SAAS;gBACvB,SAAS,EAAE,EAAE,CAAC,SAAS,IAAI,IAAI;gBAC/B,QAAQ,EAAE,EAAE,CAAC,QAAQ;gBACrB,SAAS,EAAE,EAAE,CAAC,SAAS;gBACvB,QAAQ,EAAE,EAAE,CAAC,QAAQ;aACtB,CAAC,CAAC;YACH,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC9B,CAAC;QAED,MAAM,MAAM,GAAG,CAAC,CAAO,EAAY,EAAE,CAAC,CAAC;YACrC,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,cAAc,EAAE,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI;YACxE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE;SAC9B,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,IAAI,IAAI,CAAC;QACjE,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC;QAChE,OAAO;YACL,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI;YACzC,SAAS,EAAE,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC;SACjC,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
2
|
+
import type { CartCustomerContext, CartItemRecord, CartReadPort, CartRecord, CartSeedLine, CartWithItems, CartWritePort } from '@endora-commerce/contracts';
|
|
3
|
+
import { Cart } from '../entities/cart.entity.js';
|
|
4
|
+
import { CartItem } from '../entities/cart-item.entity.js';
|
|
5
|
+
import type { CartService } from './cart-service.js';
|
|
6
|
+
/**
|
|
7
|
+
* The row-level read model `carts` publishes, and the seed operation that goes
|
|
8
|
+
* with it (feature 075, Phase P).
|
|
9
|
+
*
|
|
10
|
+
* Six inbound sites are the same pair of queries — the customer's active cart,
|
|
11
|
+
* then its items — written out in `orders`' checkout, `orders`' reorder and
|
|
12
|
+
* `quote_requests`' quote-to-cart conversion. Two of those three then
|
|
13
|
+
* `em.create(Cart, …)` when the lookup misses and hand-build `CartItem` rows,
|
|
14
|
+
* which is two modules writing this module's two tables.
|
|
15
|
+
*
|
|
16
|
+
* `replaceItemsForCustomer` is what they call instead. The clear-then-seed
|
|
17
|
+
* rule it implements is the single-active-cart model admin order-create,
|
|
18
|
+
* reorder and RFQ conversion all already assume; it was spelled out twice, in
|
|
19
|
+
* two modules, and `lastActivityAt` was maintained in neither.
|
|
20
|
+
*/
|
|
21
|
+
export declare class CartReadService implements CartReadPort {
|
|
22
|
+
#private;
|
|
23
|
+
private readonly emFactory;
|
|
24
|
+
constructor(emFactory: () => EntityManager);
|
|
25
|
+
findActiveForCustomer(ctx: CartCustomerContext): Promise<CartWithItems | null>;
|
|
26
|
+
findById(cartId: string): Promise<CartWithItems | null>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The write port, adapting `CartService` and adding the seed operation.
|
|
30
|
+
*
|
|
31
|
+
* The service arrives as a getter so the adapter resolves it from the
|
|
32
|
+
* container per call rather than capturing it — it is this module's own gated
|
|
33
|
+
* port, and a singleton may not hold a gate.
|
|
34
|
+
*/
|
|
35
|
+
export declare function createCartWritePort(emFactory: () => EntityManager, getService: () => CartService): CartWritePort;
|
|
36
|
+
export declare function toCartRecord(cart: Cart): CartRecord;
|
|
37
|
+
export declare function toCartItemRecord(item: CartItem): CartItemRecord;
|
|
38
|
+
/** Kept beside the mapper so `CartSeedLine`'s shape is checked where it is used. */
|
|
39
|
+
export type { CartSeedLine };
|
|
40
|
+
//# sourceMappingURL=cart-read-port.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cart-read-port.d.ts","sourceRoot":"","sources":["../../../src/backend/services/cart-read-port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EACV,mBAAmB,EACnB,cAAc,EACd,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,aAAa,EACb,aAAa,EACd,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,4BAA4B,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;;;;;;;;;;;;GAcG;AACH,qBAAa,eAAgB,YAAW,YAAY;;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,MAAM,aAAa;IAErD,qBAAqB,CAAC,GAAG,EAAE,mBAAmB,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAc9E,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;CAW9D;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,aAAa,EAC9B,UAAU,EAAE,MAAM,WAAW,GAC5B,aAAa,CAgDf;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,UAAU,CAuBnD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,GAAG,cAAc,CAkB/D;AAED,oFAAoF;AACpF,YAAY,EAAE,YAAY,EAAE,CAAC"}
|