@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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +51 -0
  3. package/dist/backend/entities/shopping-list-item.entity.d.ts +20 -0
  4. package/dist/backend/entities/shopping-list-item.entity.d.ts.map +1 -0
  5. package/dist/backend/entities/shopping-list-item.entity.js +70 -0
  6. package/dist/backend/entities/shopping-list-item.entity.js.map +1 -0
  7. package/dist/backend/entities/shopping-list.entity.d.ts +22 -0
  8. package/dist/backend/entities/shopping-list.entity.d.ts.map +1 -0
  9. package/dist/backend/entities/shopping-list.entity.js +68 -0
  10. package/dist/backend/entities/shopping-list.entity.js.map +1 -0
  11. package/dist/backend/index.d.ts +87 -0
  12. package/dist/backend/index.d.ts.map +1 -0
  13. package/dist/backend/index.js +74 -0
  14. package/dist/backend/index.js.map +1 -0
  15. package/dist/backend/plugin.d.ts +42 -0
  16. package/dist/backend/plugin.d.ts.map +1 -0
  17. package/dist/backend/plugin.js +15 -0
  18. package/dist/backend/plugin.js.map +1 -0
  19. package/dist/backend/routes.d.ts +19 -0
  20. package/dist/backend/routes.d.ts.map +1 -0
  21. package/dist/backend/routes.js +136 -0
  22. package/dist/backend/routes.js.map +1 -0
  23. package/dist/backend/services/shopping-list-service.d.ts +134 -0
  24. package/dist/backend/services/shopping-list-service.d.ts.map +1 -0
  25. package/dist/backend/services/shopping-list-service.js +331 -0
  26. package/dist/backend/services/shopping-list-service.js.map +1 -0
  27. package/dist/manifest.d.ts +191 -0
  28. package/dist/manifest.d.ts.map +1 -0
  29. package/dist/manifest.js +105 -0
  30. package/dist/manifest.js.map +1 -0
  31. package/dist/migrations/20260426T135443_shopping_lists_init.d.ts +18 -0
  32. package/dist/migrations/20260426T135443_shopping_lists_init.d.ts.map +1 -0
  33. package/dist/migrations/20260426T135443_shopping_lists_init.js +52 -0
  34. package/dist/migrations/20260426T135443_shopping_lists_init.js.map +1 -0
  35. package/dist/migrations/20260611T140418_shopping_lists_default.d.ts +16 -0
  36. package/dist/migrations/20260611T140418_shopping_lists_default.d.ts.map +1 -0
  37. package/dist/migrations/20260611T140418_shopping_lists_default.js +31 -0
  38. package/dist/migrations/20260611T140418_shopping_lists_default.js.map +1 -0
  39. package/dist/migrations/index.d.ts +28 -0
  40. package/dist/migrations/index.d.ts.map +1 -0
  41. package/dist/migrations/index.js +31 -0
  42. package/dist/migrations/index.js.map +1 -0
  43. package/docs/shopping_lists.md +70 -0
  44. package/i18n/en.json +4 -0
  45. package/i18n/pl.json +4 -0
  46. 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":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,KAAK,EAAE,aAAa,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAEzG,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC;AAKnF,OAAO,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAE5E;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,WAAW,eAAe;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,kBAAkB,GAAG,qBAAqB,GAAG,mBAAmB,CAAC;CAC1E;AAED,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,cAAc,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,kBAAkB;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,cAAc,EAAE,CAAC;CAC3B;AAED,qBAAa,mBAAmB;;IAE5B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,iBAAiB;gBAdjB,SAAS,EAAE,MAAM,aAAa,EAC9B,WAAW,EAAE,aAAa,EAC1B,UAAU,EAAE,eAAe;IAC5C;;;;;OAKG;IACc,eAAe,EAAE,sBAAsB;IACxD;;;OAGG;IACc,iBAAiB,EAAE,0BAA0B;IAG1D,IAAI,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IASnD,gBAAgB,CACpB,GAAG,EAAE,eAAe,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;QAAE,IAAI,EAAE,YAAY,CAAC;QAAC,KAAK,EAAE,gBAAgB,EAAE,CAAA;KAAE,CAAC;IAWvD,MAAM,CAAC,GAAG,EAAE,eAAe,EAAE,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,YAAY,CAAC;IAmBlF;;;;;;;OAOG;IACG,aAAa,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,YAAY,CAAC;IAuBhE,+EAA+E;IACzE,UAAU,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAiB7E,6EAA6E;IACvE,gBAAgB,CACpB,GAAG,EAAE,eAAe,EACpB,KAAK,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAChF,OAAO,CAAC;QAAE,IAAI,EAAE,YAAY,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAQ/C,MAAM,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAUjF,MAAM,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+B3D,OAAO,CACX,GAAG,EAAE,eAAe,EACpB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAChF,OAAO,CAAC,gBAAgB,CAAC;IAuCtB,UAAU,CACd,GAAG,EAAE,eAAe,EACpB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GACjD,OAAO,CAAC,gBAAgB,CAAC;IActB,UAAU,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWrF;;;;OAIG;IACG,UAAU,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAavE;;;;OAIG;IACG,aAAa,CACjB,GAAG,EAAE,eAAe,EACpB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EAAE,GAAG,SAAS,GAC5B,OAAO,CAAC,mBAAmB,CAAC;IAsB/B;;;;OAIG;IACG,YAAY,CAChB,GAAG,EAAE,eAAe,EACpB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EAAE,GAAG,SAAS,GAC5B,OAAO,CAAC,kBAAkB,CAAC;CA4F/B"}
@@ -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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"}