@endora-commerce/mod-shopping-lists 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 +51 -0
- package/dist/backend/entities/shopping-list-item.entity.d.ts +20 -0
- package/dist/backend/entities/shopping-list-item.entity.d.ts.map +1 -0
- package/dist/backend/entities/shopping-list-item.entity.js +70 -0
- package/dist/backend/entities/shopping-list-item.entity.js.map +1 -0
- package/dist/backend/entities/shopping-list.entity.d.ts +22 -0
- package/dist/backend/entities/shopping-list.entity.d.ts.map +1 -0
- package/dist/backend/entities/shopping-list.entity.js +68 -0
- package/dist/backend/entities/shopping-list.entity.js.map +1 -0
- package/dist/backend/index.d.ts +87 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +74 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/plugin.d.ts +42 -0
- package/dist/backend/plugin.d.ts.map +1 -0
- package/dist/backend/plugin.js +15 -0
- package/dist/backend/plugin.js.map +1 -0
- package/dist/backend/routes.d.ts +19 -0
- package/dist/backend/routes.d.ts.map +1 -0
- package/dist/backend/routes.js +136 -0
- package/dist/backend/routes.js.map +1 -0
- package/dist/backend/services/shopping-list-service.d.ts +134 -0
- package/dist/backend/services/shopping-list-service.d.ts.map +1 -0
- package/dist/backend/services/shopping-list-service.js +331 -0
- package/dist/backend/services/shopping-list-service.js.map +1 -0
- package/dist/manifest.d.ts +191 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +105 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260426T135443_shopping_lists_init.d.ts +18 -0
- package/dist/migrations/20260426T135443_shopping_lists_init.d.ts.map +1 -0
- package/dist/migrations/20260426T135443_shopping_lists_init.js +52 -0
- package/dist/migrations/20260426T135443_shopping_lists_init.js.map +1 -0
- package/dist/migrations/20260611T140418_shopping_lists_default.d.ts +16 -0
- package/dist/migrations/20260611T140418_shopping_lists_default.d.ts.map +1 -0
- package/dist/migrations/20260611T140418_shopping_lists_default.js +31 -0
- package/dist/migrations/20260611T140418_shopping_lists_default.js.map +1 -0
- package/dist/migrations/index.d.ts +28 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +31 -0
- package/dist/migrations/index.js.map +1 -0
- package/docs/shopping_lists.md +70 -0
- package/i18n/en.json +4 -0
- package/i18n/pl.json +4 -0
- package/package.json +66 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { addShoppingListItemRequestSchema, convertShoppingListRequestSchema, createShoppingListRequestSchema, renameShoppingListRequestSchema, updateShoppingListItemRequestSchema, } from '@endora-commerce/contracts';
|
|
2
|
+
import { ShoppingListItem } from './entities/shopping-list-item.entity.js';
|
|
3
|
+
export async function registerShoppingListRoutes(app, deps) {
|
|
4
|
+
const { service, emFactory, requireCustomer, resolveCustomerContext } = deps;
|
|
5
|
+
app.get('/api/v1/shopping-lists', { preHandler: requireCustomer }, async (request) => {
|
|
6
|
+
const ctx = resolveCustomerContext(request);
|
|
7
|
+
const lists = await service.list(ctx);
|
|
8
|
+
const em = emFactory();
|
|
9
|
+
const data = await Promise.all(lists.map(async (l) => {
|
|
10
|
+
const items = await em.find(ShoppingListItem, { shoppingListId: l.id });
|
|
11
|
+
return serializeWithItems(l, items);
|
|
12
|
+
}));
|
|
13
|
+
return { data };
|
|
14
|
+
});
|
|
15
|
+
app.post('/api/v1/shopping-lists', { preHandler: requireCustomer, schema: { body: createShoppingListRequestSchema } }, async (request, reply) => {
|
|
16
|
+
const ctx = resolveCustomerContext(request);
|
|
17
|
+
const body = createShoppingListRequestSchema.parse(request.body);
|
|
18
|
+
const list = await service.create(ctx, body);
|
|
19
|
+
reply.status(201);
|
|
20
|
+
return { data: serializeWithItems(list, []) };
|
|
21
|
+
});
|
|
22
|
+
// Default-list endpoints. Fastify's radix router prioritises the static
|
|
23
|
+
// `default` segment over the parametric `:id`, so ordering vs `/:id` is
|
|
24
|
+
// irrelevant.
|
|
25
|
+
app.get('/api/v1/shopping-lists/default', { preHandler: requireCustomer }, async (request) => {
|
|
26
|
+
const ctx = resolveCustomerContext(request);
|
|
27
|
+
const list = await service.ensureDefault(ctx);
|
|
28
|
+
const em = emFactory();
|
|
29
|
+
const itemCount = await em.count(ShoppingListItem, { shoppingListId: list.id });
|
|
30
|
+
return { data: { id: list.id, name: list.name, itemCount } };
|
|
31
|
+
});
|
|
32
|
+
app.post('/api/v1/shopping-lists/default/items', { preHandler: requireCustomer, schema: { body: addShoppingListItemRequestSchema } }, async (request, reply) => {
|
|
33
|
+
const ctx = resolveCustomerContext(request);
|
|
34
|
+
const body = addShoppingListItemRequestSchema.parse(request.body);
|
|
35
|
+
const { list, itemCount } = await service.addItemToDefault(ctx, {
|
|
36
|
+
productId: body.productId,
|
|
37
|
+
...(body.variantId ? { variantId: body.variantId } : {}),
|
|
38
|
+
quantity: body.quantity,
|
|
39
|
+
...(body.note ? { note: body.note } : {}),
|
|
40
|
+
});
|
|
41
|
+
reply.status(201);
|
|
42
|
+
return { data: { id: list.id, name: list.name, itemCount } };
|
|
43
|
+
});
|
|
44
|
+
app.get('/api/v1/shopping-lists/:id', { preHandler: requireCustomer }, async (request) => {
|
|
45
|
+
const ctx = resolveCustomerContext(request);
|
|
46
|
+
const { list, items } = await service.getByIdWithItems(ctx, request.params.id);
|
|
47
|
+
return { data: serializeWithItems(list, items) };
|
|
48
|
+
});
|
|
49
|
+
app.post('/api/v1/shopping-lists/:id/set-default', { preHandler: requireCustomer }, async (request) => {
|
|
50
|
+
const ctx = resolveCustomerContext(request);
|
|
51
|
+
const list = await service.setDefault(ctx, request.params.id);
|
|
52
|
+
const em = emFactory();
|
|
53
|
+
const items = await em.find(ShoppingListItem, { shoppingListId: list.id });
|
|
54
|
+
return { data: serializeWithItems(list, items) };
|
|
55
|
+
});
|
|
56
|
+
app.patch('/api/v1/shopping-lists/:id', { preHandler: requireCustomer, schema: { body: renameShoppingListRequestSchema } }, async (request) => {
|
|
57
|
+
const ctx = resolveCustomerContext(request);
|
|
58
|
+
const body = renameShoppingListRequestSchema.parse(request.body);
|
|
59
|
+
const list = await service.rename(ctx, request.params.id, body.name);
|
|
60
|
+
const em = emFactory();
|
|
61
|
+
const items = await em.find(ShoppingListItem, { shoppingListId: list.id });
|
|
62
|
+
return { data: serializeWithItems(list, items) };
|
|
63
|
+
});
|
|
64
|
+
app.delete('/api/v1/shopping-lists/:id', { preHandler: requireCustomer }, async (request, reply) => {
|
|
65
|
+
const ctx = resolveCustomerContext(request);
|
|
66
|
+
await service.remove(ctx, request.params.id);
|
|
67
|
+
return reply.status(204).send();
|
|
68
|
+
});
|
|
69
|
+
app.post('/api/v1/shopping-lists/:id/items', { preHandler: requireCustomer, schema: { body: addShoppingListItemRequestSchema } }, async (request, reply) => {
|
|
70
|
+
const ctx = resolveCustomerContext(request);
|
|
71
|
+
const body = addShoppingListItemRequestSchema.parse(request.body);
|
|
72
|
+
await service.addItem(ctx, request.params.id, {
|
|
73
|
+
productId: body.productId,
|
|
74
|
+
...(body.variantId ? { variantId: body.variantId } : {}),
|
|
75
|
+
quantity: body.quantity,
|
|
76
|
+
...(body.note ? { note: body.note } : {}),
|
|
77
|
+
});
|
|
78
|
+
const { list, items } = await service.getByIdWithItems(ctx, request.params.id);
|
|
79
|
+
reply.status(201);
|
|
80
|
+
return { data: serializeWithItems(list, items) };
|
|
81
|
+
});
|
|
82
|
+
app.patch('/api/v1/shopping-lists/:id/items/:itemId', { preHandler: requireCustomer, schema: { body: updateShoppingListItemRequestSchema } }, async (request) => {
|
|
83
|
+
const ctx = resolveCustomerContext(request);
|
|
84
|
+
const body = updateShoppingListItemRequestSchema.parse(request.body);
|
|
85
|
+
await service.updateItem(ctx, request.params.id, request.params.itemId, {
|
|
86
|
+
...(body.quantity !== undefined ? { quantity: body.quantity } : {}),
|
|
87
|
+
...(body.note !== undefined ? { note: body.note } : {}),
|
|
88
|
+
});
|
|
89
|
+
const { list, items } = await service.getByIdWithItems(ctx, request.params.id);
|
|
90
|
+
return { data: serializeWithItems(list, items) };
|
|
91
|
+
});
|
|
92
|
+
app.delete('/api/v1/shopping-lists/:id/items', { preHandler: requireCustomer }, async (request, reply) => {
|
|
93
|
+
const ctx = resolveCustomerContext(request);
|
|
94
|
+
await service.clearItems(ctx, request.params.id);
|
|
95
|
+
return reply.status(204).send();
|
|
96
|
+
});
|
|
97
|
+
app.delete('/api/v1/shopping-lists/:id/items/:itemId', { preHandler: requireCustomer }, async (request, reply) => {
|
|
98
|
+
const ctx = resolveCustomerContext(request);
|
|
99
|
+
await service.removeItem(ctx, request.params.id, request.params.itemId);
|
|
100
|
+
return reply.status(204).send();
|
|
101
|
+
});
|
|
102
|
+
app.post('/api/v1/shopping-lists/:id/convert-to-cart', { preHandler: requireCustomer, schema: { body: convertShoppingListRequestSchema.optional() } }, async (request) => {
|
|
103
|
+
const ctx = resolveCustomerContext(request);
|
|
104
|
+
const body = convertShoppingListRequestSchema.parse(request.body ?? {});
|
|
105
|
+
const result = await service.convertToCart(ctx, request.params.id, body.itemIds);
|
|
106
|
+
return { data: result };
|
|
107
|
+
});
|
|
108
|
+
app.post('/api/v1/shopping-lists/:id/convert-to-rfq', { preHandler: requireCustomer, schema: { body: convertShoppingListRequestSchema.optional() } }, async (request) => {
|
|
109
|
+
const ctx = resolveCustomerContext(request);
|
|
110
|
+
const body = convertShoppingListRequestSchema.parse(request.body ?? {});
|
|
111
|
+
const result = await service.convertToRfq(ctx, request.params.id, body.itemIds);
|
|
112
|
+
return { data: result };
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function serializeWithItems(list, items) {
|
|
116
|
+
return {
|
|
117
|
+
id: list.id,
|
|
118
|
+
organizationId: list.organizationId,
|
|
119
|
+
customerAccountId: list.customerAccountId,
|
|
120
|
+
name: list.name,
|
|
121
|
+
isDefault: list.isDefault,
|
|
122
|
+
items: items.map((it) => ({
|
|
123
|
+
id: it.id,
|
|
124
|
+
shoppingListId: it.shoppingListId,
|
|
125
|
+
productId: it.productId,
|
|
126
|
+
variantId: it.variantId ?? null,
|
|
127
|
+
quantity: it.quantity,
|
|
128
|
+
note: it.note ?? null,
|
|
129
|
+
createdAt: it.createdAt.toISOString(),
|
|
130
|
+
updatedAt: it.updatedAt.toISOString(),
|
|
131
|
+
})),
|
|
132
|
+
createdAt: list.createdAt.toISOString(),
|
|
133
|
+
updatedAt: list.updatedAt.toISOString(),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/backend/routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
2
|
+
import type { CartWritePort, CatalogProductReadPort, RfqCustomerPort } from '@endora-commerce/contracts';
|
|
3
|
+
import type { SalesChannelMembershipPort } from '@endora-commerce/platform/kernel';
|
|
4
|
+
import { ShoppingList } from '../entities/shopping-list.entity.js';
|
|
5
|
+
import { ShoppingListItem } from '../entities/shopping-list-item.entity.js';
|
|
6
|
+
/**
|
|
7
|
+
* ShoppingListService (T201). CRUD + the two terminal converters.
|
|
8
|
+
*
|
|
9
|
+
* Conversions skip archived products and report them in a structured
|
|
10
|
+
* `skipped` array so the UI can surface a friendly message without
|
|
11
|
+
* blocking the rest of the conversion. The converters reuse the published
|
|
12
|
+
* `cartWritePort` / `rfqCustomerPort` surfaces — keeping the actual cart
|
|
13
|
+
* + RFQ semantics (line aggregation, draft version bumps, etc.) in one
|
|
14
|
+
* place.
|
|
15
|
+
*
|
|
16
|
+
* Products arrive over `catalogProductReadPort` (feature 075, Phase C) rather
|
|
17
|
+
* than out of `catalog`'s table. Both reads keep the *wider* lookup — no
|
|
18
|
+
* `activeOnly`, no `liveOnly` — because the archived/not-found distinction is
|
|
19
|
+
* this module's to make: `#partitionByProductStatus` has to tell
|
|
20
|
+
* `product_archived` from `product_not_found`, and a filtered read collapses
|
|
21
|
+
* the two into one.
|
|
22
|
+
*/
|
|
23
|
+
export interface CustomerContext {
|
|
24
|
+
customerAccountId: string;
|
|
25
|
+
organizationId: string;
|
|
26
|
+
}
|
|
27
|
+
export interface ConversionSkip {
|
|
28
|
+
itemId: string;
|
|
29
|
+
productId: string;
|
|
30
|
+
reason: 'product_archived' | 'variant_unavailable' | 'product_not_found';
|
|
31
|
+
}
|
|
32
|
+
export interface ConvertToCartResult {
|
|
33
|
+
shoppingListId: string;
|
|
34
|
+
added: number;
|
|
35
|
+
skipped: ConversionSkip[];
|
|
36
|
+
}
|
|
37
|
+
export interface ConvertToRfqResult {
|
|
38
|
+
shoppingListId: string;
|
|
39
|
+
rfqId: string | null;
|
|
40
|
+
added: number;
|
|
41
|
+
skipped: ConversionSkip[];
|
|
42
|
+
}
|
|
43
|
+
export declare class ShoppingListService {
|
|
44
|
+
#private;
|
|
45
|
+
private readonly emFactory;
|
|
46
|
+
private readonly cartService;
|
|
47
|
+
private readonly rfqService;
|
|
48
|
+
/**
|
|
49
|
+
* The product rows `addItem` and the two converters decide on. Gated: with
|
|
50
|
+
* `catalog` switched off a save-to-list and a conversion both refuse rather
|
|
51
|
+
* than reporting every line as `product_not_found`, which reads as a
|
|
52
|
+
* legitimate answer and is not one.
|
|
53
|
+
*/
|
|
54
|
+
private readonly catalogProducts;
|
|
55
|
+
/**
|
|
56
|
+
* The sanctioned bridge accessor (Constitution XII), for the assortment
|
|
57
|
+
* gate `addItem` owes (issue #259).
|
|
58
|
+
*/
|
|
59
|
+
private readonly channelMembership;
|
|
60
|
+
constructor(emFactory: () => EntityManager, cartService: CartWritePort, rfqService: RfqCustomerPort,
|
|
61
|
+
/**
|
|
62
|
+
* The product rows `addItem` and the two converters decide on. Gated: with
|
|
63
|
+
* `catalog` switched off a save-to-list and a conversion both refuse rather
|
|
64
|
+
* than reporting every line as `product_not_found`, which reads as a
|
|
65
|
+
* legitimate answer and is not one.
|
|
66
|
+
*/
|
|
67
|
+
catalogProducts: CatalogProductReadPort,
|
|
68
|
+
/**
|
|
69
|
+
* The sanctioned bridge accessor (Constitution XII), for the assortment
|
|
70
|
+
* gate `addItem` owes (issue #259).
|
|
71
|
+
*/
|
|
72
|
+
channelMembership: SalesChannelMembershipPort);
|
|
73
|
+
list(ctx: CustomerContext): Promise<ShoppingList[]>;
|
|
74
|
+
getByIdWithItems(ctx: CustomerContext, listId: string): Promise<{
|
|
75
|
+
list: ShoppingList;
|
|
76
|
+
items: ShoppingListItem[];
|
|
77
|
+
}>;
|
|
78
|
+
create(ctx: CustomerContext, input: {
|
|
79
|
+
name: string;
|
|
80
|
+
}): Promise<ShoppingList>;
|
|
81
|
+
/**
|
|
82
|
+
* Resolves the customer's default shopping list, creating/repairing it as
|
|
83
|
+
* needed so every customer always has exactly one default:
|
|
84
|
+
* - returns the flagged default when present;
|
|
85
|
+
* - else promotes the earliest existing list to default;
|
|
86
|
+
* - else creates a "Default" list.
|
|
87
|
+
* Idempotent — safe to call on every storefront default-list read.
|
|
88
|
+
*/
|
|
89
|
+
ensureDefault(ctx: CustomerContext): Promise<ShoppingList>;
|
|
90
|
+
/** Marks `listId` as the customer's default, clearing the flag on the rest. */
|
|
91
|
+
setDefault(ctx: CustomerContext, listId: string): Promise<ShoppingList>;
|
|
92
|
+
/** Adds an item to the customer's default list, creating it if necessary. */
|
|
93
|
+
addItemToDefault(ctx: CustomerContext, input: {
|
|
94
|
+
productId: string;
|
|
95
|
+
variantId?: string;
|
|
96
|
+
quantity: number;
|
|
97
|
+
note?: string;
|
|
98
|
+
}): Promise<{
|
|
99
|
+
list: ShoppingList;
|
|
100
|
+
itemCount: number;
|
|
101
|
+
}>;
|
|
102
|
+
rename(ctx: CustomerContext, listId: string, name: string): Promise<ShoppingList>;
|
|
103
|
+
remove(ctx: CustomerContext, listId: string): Promise<void>;
|
|
104
|
+
addItem(ctx: CustomerContext, listId: string, input: {
|
|
105
|
+
productId: string;
|
|
106
|
+
variantId?: string;
|
|
107
|
+
quantity: number;
|
|
108
|
+
note?: string;
|
|
109
|
+
}): Promise<ShoppingListItem>;
|
|
110
|
+
updateItem(ctx: CustomerContext, listId: string, itemId: string, patch: {
|
|
111
|
+
quantity?: number;
|
|
112
|
+
note?: string | null;
|
|
113
|
+
}): Promise<ShoppingListItem>;
|
|
114
|
+
removeItem(ctx: CustomerContext, listId: string, itemId: string): Promise<void>;
|
|
115
|
+
/**
|
|
116
|
+
* Removes every item from the list, keeping the list itself. Idempotent: an
|
|
117
|
+
* already-empty list is a no-op. Returns the number of rows removed so the
|
|
118
|
+
* caller can report it.
|
|
119
|
+
*/
|
|
120
|
+
clearItems(ctx: CustomerContext, listId: string): Promise<number>;
|
|
121
|
+
/**
|
|
122
|
+
* Adds the given items (or every item on the list when itemIds is omitted)
|
|
123
|
+
* to the customer's authenticated cart. Archived products are skipped and
|
|
124
|
+
* reported in `skipped`.
|
|
125
|
+
*/
|
|
126
|
+
convertToCart(ctx: CustomerContext, listId: string, itemIds: string[] | undefined): Promise<ConvertToCartResult>;
|
|
127
|
+
/**
|
|
128
|
+
* Adds the given items (or every item on the list when itemIds is omitted)
|
|
129
|
+
* to the customer's draft RFQ. Returns the RFQ id so the storefront can
|
|
130
|
+
* deep-link the buyer to it.
|
|
131
|
+
*/
|
|
132
|
+
convertToRfq(ctx: CustomerContext, listId: string, itemIds: string[] | undefined): Promise<ConvertToRfqResult>;
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=shopping-list-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shopping-list-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/shopping-list-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { ERROR_CODES, isProductVisibleTo } from '@endora-commerce/contracts';
|
|
2
|
+
import { HttpError } from '@endora-commerce/platform/http';
|
|
3
|
+
import { outOfRequestChannel, productIdsInRequestChannel, } from '@endora-commerce/platform/kernel';
|
|
4
|
+
import { ShoppingList } from '../entities/shopping-list.entity.js';
|
|
5
|
+
import { ShoppingListItem } from '../entities/shopping-list-item.entity.js';
|
|
6
|
+
export class ShoppingListService {
|
|
7
|
+
emFactory;
|
|
8
|
+
cartService;
|
|
9
|
+
rfqService;
|
|
10
|
+
catalogProducts;
|
|
11
|
+
channelMembership;
|
|
12
|
+
constructor(emFactory, cartService, rfqService,
|
|
13
|
+
/**
|
|
14
|
+
* The product rows `addItem` and the two converters decide on. Gated: with
|
|
15
|
+
* `catalog` switched off a save-to-list and a conversion both refuse rather
|
|
16
|
+
* than reporting every line as `product_not_found`, which reads as a
|
|
17
|
+
* legitimate answer and is not one.
|
|
18
|
+
*/
|
|
19
|
+
catalogProducts,
|
|
20
|
+
/**
|
|
21
|
+
* The sanctioned bridge accessor (Constitution XII), for the assortment
|
|
22
|
+
* gate `addItem` owes (issue #259).
|
|
23
|
+
*/
|
|
24
|
+
channelMembership) {
|
|
25
|
+
this.emFactory = emFactory;
|
|
26
|
+
this.cartService = cartService;
|
|
27
|
+
this.rfqService = rfqService;
|
|
28
|
+
this.catalogProducts = catalogProducts;
|
|
29
|
+
this.channelMembership = channelMembership;
|
|
30
|
+
}
|
|
31
|
+
async list(ctx) {
|
|
32
|
+
const em = this.emFactory();
|
|
33
|
+
return em.find(ShoppingList, { organizationId: ctx.organizationId, customerAccountId: ctx.customerAccountId }, { orderBy: { updatedAt: 'desc' } });
|
|
34
|
+
}
|
|
35
|
+
async getByIdWithItems(ctx, listId) {
|
|
36
|
+
const em = this.emFactory();
|
|
37
|
+
const list = await this.#owned(em, ctx, listId);
|
|
38
|
+
const items = await em.find(ShoppingListItem, { shoppingListId: list.id }, { orderBy: { createdAt: 'asc' } });
|
|
39
|
+
return { list, items };
|
|
40
|
+
}
|
|
41
|
+
async create(ctx, input) {
|
|
42
|
+
// command-coverage-ignore: customer-owned personal saved list — self-service
|
|
43
|
+
// convenience data; conversions delegate to the audited cart/RFQ paths.
|
|
44
|
+
const em = this.emFactory();
|
|
45
|
+
// The customer's first list automatically becomes their default.
|
|
46
|
+
const existingCount = await em.count(ShoppingList, {
|
|
47
|
+
organizationId: ctx.organizationId,
|
|
48
|
+
customerAccountId: ctx.customerAccountId,
|
|
49
|
+
});
|
|
50
|
+
const list = em.create(ShoppingList, {
|
|
51
|
+
organizationId: ctx.organizationId,
|
|
52
|
+
customerAccountId: ctx.customerAccountId,
|
|
53
|
+
name: input.name,
|
|
54
|
+
isDefault: existingCount === 0,
|
|
55
|
+
});
|
|
56
|
+
await em.persistAndFlush(list);
|
|
57
|
+
return list;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Resolves the customer's default shopping list, creating/repairing it as
|
|
61
|
+
* needed so every customer always has exactly one default:
|
|
62
|
+
* - returns the flagged default when present;
|
|
63
|
+
* - else promotes the earliest existing list to default;
|
|
64
|
+
* - else creates a "Default" list.
|
|
65
|
+
* Idempotent — safe to call on every storefront default-list read.
|
|
66
|
+
*/
|
|
67
|
+
async ensureDefault(ctx) {
|
|
68
|
+
// command-coverage-ignore: customer-owned personal saved list — self-service
|
|
69
|
+
// convenience data; conversions delegate to the audited cart/RFQ paths.
|
|
70
|
+
const em = this.emFactory();
|
|
71
|
+
const where = {
|
|
72
|
+
organizationId: ctx.organizationId,
|
|
73
|
+
customerAccountId: ctx.customerAccountId,
|
|
74
|
+
};
|
|
75
|
+
const flagged = await em.findOne(ShoppingList, { ...where, isDefault: true });
|
|
76
|
+
if (flagged)
|
|
77
|
+
return flagged;
|
|
78
|
+
const earliest = await em.findOne(ShoppingList, where, { orderBy: { createdAt: 'asc' } });
|
|
79
|
+
if (earliest) {
|
|
80
|
+
earliest.isDefault = true;
|
|
81
|
+
await em.flush();
|
|
82
|
+
return earliest;
|
|
83
|
+
}
|
|
84
|
+
const created = em.create(ShoppingList, { ...where, name: 'Default', isDefault: true });
|
|
85
|
+
await em.persistAndFlush(created);
|
|
86
|
+
return created;
|
|
87
|
+
}
|
|
88
|
+
/** Marks `listId` as the customer's default, clearing the flag on the rest. */
|
|
89
|
+
async setDefault(ctx, listId) {
|
|
90
|
+
// command-coverage-ignore: customer-owned personal saved list — self-service
|
|
91
|
+
// convenience data; conversions delegate to the audited cart/RFQ paths.
|
|
92
|
+
const em = this.emFactory();
|
|
93
|
+
const list = await this.#owned(em, ctx, listId);
|
|
94
|
+
const others = await em.find(ShoppingList, {
|
|
95
|
+
organizationId: ctx.organizationId,
|
|
96
|
+
customerAccountId: ctx.customerAccountId,
|
|
97
|
+
isDefault: true,
|
|
98
|
+
id: { $ne: listId },
|
|
99
|
+
});
|
|
100
|
+
for (const o of others)
|
|
101
|
+
o.isDefault = false;
|
|
102
|
+
list.isDefault = true;
|
|
103
|
+
await em.flush();
|
|
104
|
+
return list;
|
|
105
|
+
}
|
|
106
|
+
/** Adds an item to the customer's default list, creating it if necessary. */
|
|
107
|
+
async addItemToDefault(ctx, input) {
|
|
108
|
+
const list = await this.ensureDefault(ctx);
|
|
109
|
+
await this.addItem(ctx, list.id, input);
|
|
110
|
+
const em = this.emFactory();
|
|
111
|
+
const itemCount = await em.count(ShoppingListItem, { shoppingListId: list.id });
|
|
112
|
+
return { list, itemCount };
|
|
113
|
+
}
|
|
114
|
+
async rename(ctx, listId, name) {
|
|
115
|
+
// command-coverage-ignore: customer-owned personal saved list — self-service
|
|
116
|
+
// convenience data; conversions delegate to the audited cart/RFQ paths.
|
|
117
|
+
const em = this.emFactory();
|
|
118
|
+
const list = await this.#owned(em, ctx, listId);
|
|
119
|
+
list.name = name;
|
|
120
|
+
await em.flush();
|
|
121
|
+
return list;
|
|
122
|
+
}
|
|
123
|
+
async remove(ctx, listId) {
|
|
124
|
+
// command-coverage-ignore: customer-owned personal saved list — self-service
|
|
125
|
+
// convenience data; conversions delegate to the audited cart/RFQ paths.
|
|
126
|
+
const em = this.emFactory();
|
|
127
|
+
const list = await this.#owned(em, ctx, listId);
|
|
128
|
+
// The default list can be cleared but never deleted — it is the customer's
|
|
129
|
+
// permanent "wishlist" anchor.
|
|
130
|
+
if (list.isDefault) {
|
|
131
|
+
throw new HttpError(409, ERROR_CODES.SHOPPING_LIST_CANNOT_DELETE_DEFAULT, 'The default shopping list cannot be deleted. Clear it or set another list as default first.');
|
|
132
|
+
}
|
|
133
|
+
// Every customer must always keep at least one shopping list. (With the
|
|
134
|
+
// default guard above this only triggers for malformed data where no list
|
|
135
|
+
// is flagged default, but we enforce it defensively.)
|
|
136
|
+
const total = await em.count(ShoppingList, {
|
|
137
|
+
organizationId: ctx.organizationId,
|
|
138
|
+
customerAccountId: ctx.customerAccountId,
|
|
139
|
+
});
|
|
140
|
+
if (total <= 1) {
|
|
141
|
+
throw new HttpError(409, ERROR_CODES.SHOPPING_LIST_CANNOT_DELETE_LAST, 'At least one shopping list must remain.');
|
|
142
|
+
}
|
|
143
|
+
await em.removeAndFlush(list);
|
|
144
|
+
}
|
|
145
|
+
async addItem(ctx, listId, input) {
|
|
146
|
+
// command-coverage-ignore: customer-owned personal saved list — self-service
|
|
147
|
+
// convenience data; conversions delegate to the audited cart/RFQ paths.
|
|
148
|
+
const em = this.emFactory();
|
|
149
|
+
const list = await this.#owned(em, ctx, listId);
|
|
150
|
+
const product = await this.catalogProducts.findById(input.productId);
|
|
151
|
+
// Issue #227 — the saved list renders each line's name and price on every
|
|
152
|
+
// later read, so a product this buyer may not see may not be saved to it.
|
|
153
|
+
// The audience comes off the same `CustomerContext` the list is owned by;
|
|
154
|
+
// `organizationId` is what the allow-list is matched against.
|
|
155
|
+
if (!product ||
|
|
156
|
+
!isProductVisibleTo(product, {
|
|
157
|
+
organizationId: ctx.organizationId,
|
|
158
|
+
authenticated: true,
|
|
159
|
+
})) {
|
|
160
|
+
throw new HttpError(404, ERROR_CODES.PRODUCT_NOT_FOUND, 'Product not found.');
|
|
161
|
+
}
|
|
162
|
+
// Issue #259 — the channel axis, read off the request scope rather than
|
|
163
|
+
// re-resolved. A saved list is read back on later visits and priced then,
|
|
164
|
+
// so what may be saved is what the channel the buyer is shopping on
|
|
165
|
+
// publishes; the refusal is the same 404 the visibility half above throws,
|
|
166
|
+
// so the two cannot be told apart.
|
|
167
|
+
if (await outOfRequestChannel(this.channelMembership, product.id)) {
|
|
168
|
+
throw new HttpError(404, ERROR_CODES.PRODUCT_NOT_FOUND, 'Product not found.');
|
|
169
|
+
}
|
|
170
|
+
const item = em.create(ShoppingListItem, {
|
|
171
|
+
shoppingListId: list.id,
|
|
172
|
+
productId: product.id,
|
|
173
|
+
...(input.variantId ? { variantId: input.variantId } : {}),
|
|
174
|
+
quantity: input.quantity,
|
|
175
|
+
...(input.note ? { note: input.note } : {}),
|
|
176
|
+
});
|
|
177
|
+
list.updatedAt = new Date();
|
|
178
|
+
await em.persistAndFlush(item);
|
|
179
|
+
return item;
|
|
180
|
+
}
|
|
181
|
+
async updateItem(ctx, listId, itemId, patch) {
|
|
182
|
+
// command-coverage-ignore: customer-owned personal saved list — self-service
|
|
183
|
+
// convenience data; conversions delegate to the audited cart/RFQ paths.
|
|
184
|
+
const em = this.emFactory();
|
|
185
|
+
const list = await this.#owned(em, ctx, listId);
|
|
186
|
+
const item = await em.findOne(ShoppingListItem, { id: itemId, shoppingListId: list.id });
|
|
187
|
+
if (!item)
|
|
188
|
+
throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Shopping list item not found.');
|
|
189
|
+
if (patch.quantity !== undefined)
|
|
190
|
+
item.quantity = patch.quantity;
|
|
191
|
+
if (patch.note !== undefined)
|
|
192
|
+
item.note = patch.note;
|
|
193
|
+
list.updatedAt = new Date();
|
|
194
|
+
await em.flush();
|
|
195
|
+
return item;
|
|
196
|
+
}
|
|
197
|
+
async removeItem(ctx, listId, itemId) {
|
|
198
|
+
// command-coverage-ignore: customer-owned personal saved list — self-service
|
|
199
|
+
// convenience data; conversions delegate to the audited cart/RFQ paths.
|
|
200
|
+
const em = this.emFactory();
|
|
201
|
+
const list = await this.#owned(em, ctx, listId);
|
|
202
|
+
const item = await em.findOne(ShoppingListItem, { id: itemId, shoppingListId: list.id });
|
|
203
|
+
if (!item)
|
|
204
|
+
throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Shopping list item not found.');
|
|
205
|
+
list.updatedAt = new Date();
|
|
206
|
+
await em.removeAndFlush(item);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Removes every item from the list, keeping the list itself. Idempotent: an
|
|
210
|
+
* already-empty list is a no-op. Returns the number of rows removed so the
|
|
211
|
+
* caller can report it.
|
|
212
|
+
*/
|
|
213
|
+
async clearItems(ctx, listId) {
|
|
214
|
+
// command-coverage-ignore: customer-owned personal saved list — self-service
|
|
215
|
+
// convenience data; conversions delegate to the audited cart/RFQ paths.
|
|
216
|
+
const em = this.emFactory();
|
|
217
|
+
const list = await this.#owned(em, ctx, listId);
|
|
218
|
+
const removed = await em.nativeDelete(ShoppingListItem, { shoppingListId: list.id });
|
|
219
|
+
if (removed > 0) {
|
|
220
|
+
list.updatedAt = new Date();
|
|
221
|
+
await em.flush();
|
|
222
|
+
}
|
|
223
|
+
return removed;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Adds the given items (or every item on the list when itemIds is omitted)
|
|
227
|
+
* to the customer's authenticated cart. Archived products are skipped and
|
|
228
|
+
* reported in `skipped`.
|
|
229
|
+
*/
|
|
230
|
+
async convertToCart(ctx, listId, itemIds) {
|
|
231
|
+
const em = this.emFactory();
|
|
232
|
+
const list = await this.#owned(em, ctx, listId);
|
|
233
|
+
const items = await this.#selectItems(em, list.id, itemIds);
|
|
234
|
+
const { skipped, kept } = await this.#partitionByProductStatus(items);
|
|
235
|
+
let added = 0;
|
|
236
|
+
for (const item of kept) {
|
|
237
|
+
await this.cartService.addItem({ customer: ctx }, {
|
|
238
|
+
productId: item.productId,
|
|
239
|
+
...(item.variantId ? { variantId: item.variantId } : {}),
|
|
240
|
+
quantity: item.quantity,
|
|
241
|
+
});
|
|
242
|
+
added += 1;
|
|
243
|
+
}
|
|
244
|
+
return { shoppingListId: list.id, added, skipped };
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Adds the given items (or every item on the list when itemIds is omitted)
|
|
248
|
+
* to the customer's draft RFQ. Returns the RFQ id so the storefront can
|
|
249
|
+
* deep-link the buyer to it.
|
|
250
|
+
*/
|
|
251
|
+
async convertToRfq(ctx, listId, itemIds) {
|
|
252
|
+
const em = this.emFactory();
|
|
253
|
+
const list = await this.#owned(em, ctx, listId);
|
|
254
|
+
const items = await this.#selectItems(em, list.id, itemIds);
|
|
255
|
+
const { skipped, kept } = await this.#partitionByProductStatus(items);
|
|
256
|
+
let rfqId = null;
|
|
257
|
+
let added = 0;
|
|
258
|
+
if (kept.length > 0) {
|
|
259
|
+
const rfq = await this.rfqService.createForCustomer({ ...ctx, isOrgAdmin: false }, {
|
|
260
|
+
items: kept.map((item) => ({
|
|
261
|
+
productId: item.productId,
|
|
262
|
+
quantity: item.quantity,
|
|
263
|
+
...(item.variantId ? { variantId: item.variantId } : {}),
|
|
264
|
+
...(item.note ? { lineNote: item.note } : {}),
|
|
265
|
+
})),
|
|
266
|
+
});
|
|
267
|
+
rfqId = rfq.id;
|
|
268
|
+
added = kept.length;
|
|
269
|
+
}
|
|
270
|
+
return { shoppingListId: list.id, rfqId, added, skipped };
|
|
271
|
+
}
|
|
272
|
+
// ------------------------------------------------------------------
|
|
273
|
+
async #owned(em, ctx, listId) {
|
|
274
|
+
const list = await em.findOne(ShoppingList, {
|
|
275
|
+
id: listId,
|
|
276
|
+
organizationId: ctx.organizationId,
|
|
277
|
+
customerAccountId: ctx.customerAccountId,
|
|
278
|
+
});
|
|
279
|
+
if (!list)
|
|
280
|
+
throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Shopping list not found.');
|
|
281
|
+
return list;
|
|
282
|
+
}
|
|
283
|
+
async #selectItems(em, listId, itemIds) {
|
|
284
|
+
if (itemIds && itemIds.length > 0) {
|
|
285
|
+
return em.find(ShoppingListItem, { shoppingListId: listId, id: { $in: itemIds } });
|
|
286
|
+
}
|
|
287
|
+
return em.find(ShoppingListItem, { shoppingListId: listId });
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Split the selected lines into the ones a conversion may carry and the ones
|
|
291
|
+
* it reports as skipped.
|
|
292
|
+
*
|
|
293
|
+
* The channel narrowing (issue #259) belongs **here** rather than at the two
|
|
294
|
+
* conversion targets, and that is the whole reason it is here: both targets
|
|
295
|
+
* already refuse an unacquirable line — `cartService.addItem` with a 404 per
|
|
296
|
+
* call, `rfqService.createForCustomer` with a whole-request 404 — so leaving
|
|
297
|
+
* the question to them would turn a conversion of twenty saved lines into one
|
|
298
|
+
* refusal because of the twenty-first. This seam has a vocabulary for exactly
|
|
299
|
+
* that case and has had it since T201: the line is skipped, reported, and the
|
|
300
|
+
* rest of the conversion goes through.
|
|
301
|
+
*
|
|
302
|
+
* It is asked **before** the status test, so an out-of-channel line reads as
|
|
303
|
+
* `product_not_found` rather than as `product_archived` — the same "it is not
|
|
304
|
+
* here" every other surface gives it, and not a second answer a reader could
|
|
305
|
+
* tell the two apart by.
|
|
306
|
+
*/
|
|
307
|
+
async #partitionByProductStatus(items) {
|
|
308
|
+
if (items.length === 0)
|
|
309
|
+
return { kept: [], skipped: [] };
|
|
310
|
+
const productIds = Array.from(new Set(items.map((i) => i.productId)));
|
|
311
|
+
const products = await this.catalogProducts.findByIds(productIds);
|
|
312
|
+
const byId = new Map(products.map((p) => [p.id, p]));
|
|
313
|
+
const publishedHere = await productIdsInRequestChannel(this.channelMembership, productIds);
|
|
314
|
+
const skipped = [];
|
|
315
|
+
const kept = [];
|
|
316
|
+
for (const item of items) {
|
|
317
|
+
const product = byId.get(item.productId);
|
|
318
|
+
if (!product || (publishedHere !== null && !publishedHere.has(item.productId))) {
|
|
319
|
+
skipped.push({ itemId: item.id, productId: item.productId, reason: 'product_not_found' });
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (product.status === 'inactive' || product.deletedAt) {
|
|
323
|
+
skipped.push({ itemId: item.id, productId: item.productId, reason: 'product_archived' });
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
kept.push(item);
|
|
327
|
+
}
|
|
328
|
+
return { kept, skipped };
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
//# sourceMappingURL=shopping-list-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shopping-list-service.js","sourceRoot":"","sources":["../../../src/backend/services/shopping-list-service.ts"],"names":[],"mappings":"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"}
|