@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,191 @@
|
|
|
1
|
+
export declare const shoppingListsSettingsManifest: {
|
|
2
|
+
moduleCode: string;
|
|
3
|
+
groups: {
|
|
4
|
+
code: string;
|
|
5
|
+
name: string;
|
|
6
|
+
salesChannelCodes?: string[] | undefined;
|
|
7
|
+
isSystemProtected?: boolean | undefined;
|
|
8
|
+
}[];
|
|
9
|
+
settings: {
|
|
10
|
+
code: string;
|
|
11
|
+
name: string;
|
|
12
|
+
valueType: "string" | "number" | "boolean" | "json" | "string_list" | "secret" | "credential_ref";
|
|
13
|
+
defaultValue: unknown;
|
|
14
|
+
description?: string | undefined;
|
|
15
|
+
groupCode?: string | undefined;
|
|
16
|
+
previousDefaultValues?: unknown[] | undefined;
|
|
17
|
+
salesChannelCodes?: string[] | undefined;
|
|
18
|
+
enumOptions?: string[] | undefined;
|
|
19
|
+
configurationType?: string | undefined;
|
|
20
|
+
hidden?: boolean | undefined;
|
|
21
|
+
}[];
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Shopping Lists module — manifest backfill (Module Lifecycle, feature 018).
|
|
25
|
+
*
|
|
26
|
+
* Predates the lifecycle system; this manifest is the static record
|
|
27
|
+
* required so the module participates in the registry. No install /
|
|
28
|
+
* uninstall hook today — the module's schema is owned by earlier
|
|
29
|
+
* platform-wide migrations.
|
|
30
|
+
*/
|
|
31
|
+
export declare const manifest: {
|
|
32
|
+
id: string;
|
|
33
|
+
name: string;
|
|
34
|
+
version: string;
|
|
35
|
+
dependencies: string[];
|
|
36
|
+
description?: string | undefined;
|
|
37
|
+
acknowledgedDependencies?: {
|
|
38
|
+
moduleId: string;
|
|
39
|
+
port: string;
|
|
40
|
+
reason: string;
|
|
41
|
+
}[] | undefined;
|
|
42
|
+
nonBindingDependencies?: {
|
|
43
|
+
moduleId: string;
|
|
44
|
+
name: string;
|
|
45
|
+
kind: "contributes-to" | "degrades-without" | "refuses-without";
|
|
46
|
+
reason: string;
|
|
47
|
+
whenAbsent?: string | undefined;
|
|
48
|
+
}[] | undefined;
|
|
49
|
+
activation?: {
|
|
50
|
+
settingCode: string;
|
|
51
|
+
default: boolean;
|
|
52
|
+
} | {
|
|
53
|
+
nonDeactivatable: true;
|
|
54
|
+
reason: string;
|
|
55
|
+
} | undefined;
|
|
56
|
+
settings?: {
|
|
57
|
+
moduleCode: string;
|
|
58
|
+
groups: {
|
|
59
|
+
code: string;
|
|
60
|
+
name: string;
|
|
61
|
+
salesChannelCodes?: string[] | undefined;
|
|
62
|
+
isSystemProtected?: boolean | undefined;
|
|
63
|
+
}[];
|
|
64
|
+
settings: {
|
|
65
|
+
code: string;
|
|
66
|
+
name: string;
|
|
67
|
+
valueType: "string" | "number" | "boolean" | "json" | "string_list" | "secret" | "credential_ref";
|
|
68
|
+
defaultValue: unknown;
|
|
69
|
+
description?: string | undefined;
|
|
70
|
+
groupCode?: string | undefined;
|
|
71
|
+
previousDefaultValues?: unknown[] | undefined;
|
|
72
|
+
salesChannelCodes?: string[] | undefined;
|
|
73
|
+
enumOptions?: string[] | undefined;
|
|
74
|
+
configurationType?: string | undefined;
|
|
75
|
+
hidden?: boolean | undefined;
|
|
76
|
+
}[];
|
|
77
|
+
} | undefined;
|
|
78
|
+
i18n?: {
|
|
79
|
+
bundlesDir: string;
|
|
80
|
+
} | undefined;
|
|
81
|
+
docs?: false | {
|
|
82
|
+
dir: string;
|
|
83
|
+
} | undefined;
|
|
84
|
+
demo?: false | {
|
|
85
|
+
summary: string;
|
|
86
|
+
seed: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoSeedResult>;
|
|
87
|
+
reset: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoResetResult>;
|
|
88
|
+
after?: readonly string[] | undefined;
|
|
89
|
+
package?: string | undefined;
|
|
90
|
+
} | undefined;
|
|
91
|
+
actions?: {
|
|
92
|
+
id: string;
|
|
93
|
+
labelKey: string;
|
|
94
|
+
icon: "Plus" | "Sparkles" | "Settings" | "Search" | "Boxes" | "Layers" | "Menu" | "PlusCircle" | "PlusSquare" | "FilePlus" | "FolderPlus" | "Upload" | "FileUp" | "CloudUpload" | "Download" | "FileDown" | "FileText" | "BookOpen" | "Rss" | "Package" | "Tag" | "ShoppingCart" | "Receipt" | "CreditCard" | "Users" | "UserPlus" | "Inbox" | "ListChecks" | "ClipboardList" | "Image" | "Video" | "LayoutDashboard" | "PanelLeft" | "KeyRound" | "ShieldCheck" | "Edit" | "Archive" | "Box" | "Truck" | "CircleDollarSign" | "Activity" | "LineChart" | "Smartphone" | "Webhook" | "Scale" | "PlugZap" | "PercentDiamond" | "Newspaper" | "Languages" | "Eraser" | "Warehouse" | "TrendingDown" | "Bell" | "PackageOpen" | "Building2" | "Store" | "ClipboardCheck";
|
|
95
|
+
targetRoute: string;
|
|
96
|
+
keywords: string[];
|
|
97
|
+
weight: number;
|
|
98
|
+
descriptionKey?: string | undefined;
|
|
99
|
+
requiredPermission?: string | undefined;
|
|
100
|
+
}[] | undefined;
|
|
101
|
+
permissions?: {
|
|
102
|
+
code: string;
|
|
103
|
+
label: string;
|
|
104
|
+
module?: string | undefined;
|
|
105
|
+
description?: string | undefined;
|
|
106
|
+
requires?: string[] | undefined;
|
|
107
|
+
}[] | undefined;
|
|
108
|
+
transactionalEmails?: {
|
|
109
|
+
code: string;
|
|
110
|
+
name: string;
|
|
111
|
+
variables: {
|
|
112
|
+
key: string;
|
|
113
|
+
label: string;
|
|
114
|
+
sampleValue?: string | undefined;
|
|
115
|
+
description?: string | undefined;
|
|
116
|
+
}[];
|
|
117
|
+
description?: string | undefined;
|
|
118
|
+
group?: string | undefined;
|
|
119
|
+
}[] | undefined;
|
|
120
|
+
capabilities?: string[] | undefined;
|
|
121
|
+
exclusiveCapabilities?: {
|
|
122
|
+
key: string;
|
|
123
|
+
errorCode: string;
|
|
124
|
+
}[] | undefined;
|
|
125
|
+
errorCodes?: {
|
|
126
|
+
code: string;
|
|
127
|
+
tokens?: string[] | undefined;
|
|
128
|
+
}[] | undefined;
|
|
129
|
+
blocks?: {
|
|
130
|
+
name: string;
|
|
131
|
+
labelKey: string;
|
|
132
|
+
category: string;
|
|
133
|
+
contexts: ("email" | "invoice" | "cms" | "newsletter")[];
|
|
134
|
+
fields: Record<string, {
|
|
135
|
+
type: "number" | "object" | "array" | "text" | "textarea" | "select" | "radio" | "external" | "uuid" | "richtext";
|
|
136
|
+
label?: string | undefined;
|
|
137
|
+
required?: boolean | undefined;
|
|
138
|
+
options?: {
|
|
139
|
+
label: string;
|
|
140
|
+
value: string | number;
|
|
141
|
+
}[] | undefined;
|
|
142
|
+
refKind?: string | undefined;
|
|
143
|
+
}>;
|
|
144
|
+
descriptionKey?: string | undefined;
|
|
145
|
+
defaultProps?: Record<string, unknown> | undefined;
|
|
146
|
+
responsiveFields?: string[] | undefined;
|
|
147
|
+
previewIcon?: string | undefined;
|
|
148
|
+
weight?: number | undefined;
|
|
149
|
+
}[] | undefined;
|
|
150
|
+
blockCategories?: {
|
|
151
|
+
key: string;
|
|
152
|
+
titleKey: string;
|
|
153
|
+
contexts: ("email" | "invoice" | "cms" | "newsletter")[];
|
|
154
|
+
weight?: number | undefined;
|
|
155
|
+
visible?: boolean | undefined;
|
|
156
|
+
}[] | undefined;
|
|
157
|
+
env?: {
|
|
158
|
+
name: string;
|
|
159
|
+
describes: {
|
|
160
|
+
en: string;
|
|
161
|
+
pl: string;
|
|
162
|
+
};
|
|
163
|
+
requirement: {
|
|
164
|
+
kind: "required";
|
|
165
|
+
} | {
|
|
166
|
+
kind: "requiredWhen";
|
|
167
|
+
input: string;
|
|
168
|
+
equals: string;
|
|
169
|
+
} | {
|
|
170
|
+
kind: "optional";
|
|
171
|
+
without: {
|
|
172
|
+
en: string;
|
|
173
|
+
pl: string;
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
secret: boolean;
|
|
177
|
+
generable: boolean;
|
|
178
|
+
owner: {
|
|
179
|
+
kind: "platform";
|
|
180
|
+
} | {
|
|
181
|
+
kind: "application";
|
|
182
|
+
application: "admin" | "backend" | "storefront";
|
|
183
|
+
} | {
|
|
184
|
+
kind: "module";
|
|
185
|
+
moduleId: string;
|
|
186
|
+
};
|
|
187
|
+
consumers: ("admin" | "backend" | "storefront")[];
|
|
188
|
+
addressOf: "admin" | "backend" | "storefront" | null;
|
|
189
|
+
}[] | undefined;
|
|
190
|
+
};
|
|
191
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;CAcxC,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiFnB,CAAC"}
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { defineModuleManifest, defineModuleSettingsManifest } from '@endora-commerce/contracts';
|
|
2
|
+
export const shoppingListsSettingsManifest = defineModuleSettingsManifest({
|
|
3
|
+
moduleCode: 'shopping_lists',
|
|
4
|
+
groups: [{ code: 'shopping_lists', name: 'Shopping lists' }],
|
|
5
|
+
settings: [
|
|
6
|
+
{
|
|
7
|
+
code: 'shopping_lists.enabled',
|
|
8
|
+
name: 'Shopping lists enabled',
|
|
9
|
+
description: 'Switches customer shopping lists on or off: creating and sharing lists, saving a cart line to one, and converting a list into a quote request. Nothing is dropped — every list, its items and its sharing state stay in the database.',
|
|
10
|
+
groupCode: 'shopping_lists',
|
|
11
|
+
valueType: 'boolean',
|
|
12
|
+
defaultValue: true,
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
});
|
|
16
|
+
/**
|
|
17
|
+
* Shopping Lists module — manifest backfill (Module Lifecycle, feature 018).
|
|
18
|
+
*
|
|
19
|
+
* Predates the lifecycle system; this manifest is the static record
|
|
20
|
+
* required so the module participates in the registry. No install /
|
|
21
|
+
* uninstall hook today — the module's schema is owned by earlier
|
|
22
|
+
* platform-wide migrations.
|
|
23
|
+
*/
|
|
24
|
+
export const manifest = defineModuleManifest({
|
|
25
|
+
id: 'shopping_lists',
|
|
26
|
+
name: 'Shopping Lists',
|
|
27
|
+
description: 'Customer-owned saved shopping lists.',
|
|
28
|
+
version: '1.0.0',
|
|
29
|
+
// Feature 072 (T133) — the edges were always there; the conversion is what
|
|
30
|
+
// made them declarations.
|
|
31
|
+
//
|
|
32
|
+
// `carts` joins them in feature 075 Phase C. It was absent on the grounds
|
|
33
|
+
// that "the service handed back through `shoppingListServiceSink` points
|
|
34
|
+
// outward, and declaring the consumer would invert the direction" — true of
|
|
35
|
+
// the sink, and not of the other edge: `convertToCart` resolves `carts`'
|
|
36
|
+
// `cartWritePort` and calls it, which points inward. The check accepted it
|
|
37
|
+
// only through the transitive path `orders` -> `carts`, so the declaration
|
|
38
|
+
// was a property of a neighbour's manifest. No cycle: nothing in the tree
|
|
39
|
+
// depends on `shopping_lists`.
|
|
40
|
+
dependencies: [
|
|
41
|
+
'auth',
|
|
42
|
+
'carts',
|
|
43
|
+
'catalog',
|
|
44
|
+
'orders',
|
|
45
|
+
'organizations',
|
|
46
|
+
'quote_requests',
|
|
47
|
+
'settings',
|
|
48
|
+
],
|
|
49
|
+
settings: shoppingListsSettingsManifest,
|
|
50
|
+
// Feature 073 (Constitution XVII) — the operator's activation control. It
|
|
51
|
+
// covered the quick-order surfaces too while this module hosted them; T139
|
|
52
|
+
// separated the two, so this now switches shopping lists and nothing else.
|
|
53
|
+
activation: { settingCode: 'shopping_lists.enabled', default: true },
|
|
54
|
+
/**
|
|
55
|
+
* This module's first i18n bundle — D-129's remaining sweep, MR 3.
|
|
56
|
+
*
|
|
57
|
+
* It ships two keys and nothing else, and it exists because a **declaring**
|
|
58
|
+
* module that contributes no bundle file is `check:error-translations` exit
|
|
59
|
+
* 2 for the whole tree rather than a finding
|
|
60
|
+
* (`specs/090-module-owned-error-codes/contracts/error-translation-population.md`
|
|
61
|
+
* §2.1, §2.3; `d129-sweep.md` §3.4). The bundle lives at the **package
|
|
62
|
+
* root**, not under `dist`: `manifest-locations.ts` resolves a packaged
|
|
63
|
+
* module's `manifestPath` to its `package.json`, so `dirname` is the package
|
|
64
|
+
* directory. The files are a **flat** `{"a.b.c": "text"}` map, because a
|
|
65
|
+
* nested object fails `TranslationBundleEntriesSchema` and the boot
|
|
66
|
+
* reconciler logs and skips it — silently.
|
|
67
|
+
*/
|
|
68
|
+
i18n: { bundlesDir: 'i18n' },
|
|
69
|
+
docs: { dir: 'docs' },
|
|
70
|
+
/**
|
|
71
|
+
* The `SHOPPING_LIST_*` codes — D-129's remaining sweep, Tier A
|
|
72
|
+
* (`specs/090-module-owned-error-codes/d129-sweep.md` §5.2, Appendix A;
|
|
73
|
+
* MR 3).
|
|
74
|
+
*
|
|
75
|
+
* Both were declared by `_i18n` until this merge request, not because
|
|
76
|
+
* anybody judged them the platform's but because the deleted prefix chain
|
|
77
|
+
* had no rule for them and its last line was `return 'core'`. **D-121 T1
|
|
78
|
+
* puts them here**: the noun in both is a shopping list, which is this
|
|
79
|
+
* module's own entity, and both refusals are invariants of it — the default
|
|
80
|
+
* list is the customer's permanent anchor and cannot be deleted, and an
|
|
81
|
+
* account always keeps at least one list. This module is also the only one
|
|
82
|
+
* that raises them, from `services/shopping-list-service.ts`.
|
|
83
|
+
*
|
|
84
|
+
* **Unlike the rest of Tier A, these two arrive with a sentence.** They had
|
|
85
|
+
* none in either language anywhere in the tree and were on
|
|
86
|
+
* `UNTRANSLATED_ERROR_CODES` under `_i18n`; §5.4's default for a receiver
|
|
87
|
+
* with nothing to put in its new bundle is an empty `{}` plus a ledger entry
|
|
88
|
+
* under the receiver's group, and it says in terms that writing the prose
|
|
89
|
+
* instead is always available and is not a re-opening of D-186 §2 — that
|
|
90
|
+
* ruling refuses *carrying* a placeholder, not *writing* a sentence. Two
|
|
91
|
+
* codes is four strings, both refusals are read by a **buyer** on the
|
|
92
|
+
* storefront rather than by an operator, and each sentence written deletes a
|
|
93
|
+
* ledger entry instead of adding one. So they are written, and this module's
|
|
94
|
+
* bundle is not an empty one.
|
|
95
|
+
*
|
|
96
|
+
* **`tokens` is derived from the raise sites, not from the bundle**
|
|
97
|
+
* (runbook §5), and there are none: both raises are a bare
|
|
98
|
+
* `HttpError(409, code, message)` with no `details`.
|
|
99
|
+
*/
|
|
100
|
+
errorCodes: [
|
|
101
|
+
{ code: 'SHOPPING_LIST_CANNOT_DELETE_DEFAULT' },
|
|
102
|
+
{ code: 'SHOPPING_LIST_CANNOT_DELETE_LAST' },
|
|
103
|
+
],
|
|
104
|
+
});
|
|
105
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAEhG,MAAM,CAAC,MAAM,6BAA6B,GAAG,4BAA4B,CAAC;IACxE,UAAU,EAAE,gBAAgB;IAC5B,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC;IAC5D,QAAQ,EAAE;QACR;YACE,IAAI,EAAE,wBAAwB;YAC9B,IAAI,EAAE,wBAAwB;YAC9B,WAAW,EACT,uOAAuO;YACzO,SAAS,EAAE,gBAAgB;YAC3B,SAAS,EAAE,SAAS;YACpB,YAAY,EAAE,IAAI;SACnB;KACF;CACF,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,oBAAoB,CAAC;IAC3C,EAAE,EAAE,gBAAgB;IACpB,IAAI,EAAE,gBAAgB;IACtB,WAAW,EACT,sCAAsC;IACxC,OAAO,EAAE,OAAO;IAChB,2EAA2E;IAC3E,0BAA0B;IAC1B,EAAE;IACF,0EAA0E;IAC1E,yEAAyE;IACzE,4EAA4E;IAC5E,yEAAyE;IACzE,2EAA2E;IAC3E,2EAA2E;IAC3E,0EAA0E;IAC1E,+BAA+B;IAC/B,YAAY,EAAE;QACZ,MAAM;QACN,OAAO;QACP,SAAS;QACT,QAAQ;QACR,eAAe;QACf,gBAAgB;QAChB,UAAU;KACX;IACD,QAAQ,EAAE,6BAA6B;IACvC,0EAA0E;IAC1E,2EAA2E;IAC3E,2EAA2E;IAC3E,UAAU,EAAE,EAAE,WAAW,EAAE,wBAAwB,EAAE,OAAO,EAAE,IAAI,EAAE;IACpE;;;;;;;;;;;;;OAaG;IACH,IAAI,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE;IAC5B,IAAI,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE;IACrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,UAAU,EAAE;QACV,EAAE,IAAI,EAAE,qCAAqC,EAAE;QAC/C,EAAE,IAAI,EAAE,kCAAkC,EAAE;KAC7C;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
/**
|
|
3
|
+
* shopping_lists module — Phase 5 (T200).
|
|
4
|
+
*
|
|
5
|
+
* Schema:
|
|
6
|
+
* - shopping_lists: scoped by (organization_id, customer_account_id) for
|
|
7
|
+
* owner queries. No FK constraint on customer_account_id /
|
|
8
|
+
* organization_id since those tables already exist; we keep the same
|
|
9
|
+
* "plain uuid columns" pattern the rest of the codebase uses.
|
|
10
|
+
* - shopping_list_items: cascade delete on the parent list, plain uuid
|
|
11
|
+
* FK to products so product archival doesn't drop list rows (the
|
|
12
|
+
* conversion service surfaces archived rows in a skip report).
|
|
13
|
+
*/
|
|
14
|
+
export declare class Migration20260426T135443ShoppingListsInit extends Migration {
|
|
15
|
+
up(): Promise<void>;
|
|
16
|
+
down(): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=20260426T135443_shopping_lists_init.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20260426T135443_shopping_lists_init.d.ts","sourceRoot":"","sources":["../../src/migrations/20260426T135443_shopping_lists_init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;GAWG;AACH,qBAAa,yCAA0C,SAAQ,SAAS;IACvD,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC;IA4CnB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAIrC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
/**
|
|
3
|
+
* shopping_lists module — Phase 5 (T200).
|
|
4
|
+
*
|
|
5
|
+
* Schema:
|
|
6
|
+
* - shopping_lists: scoped by (organization_id, customer_account_id) for
|
|
7
|
+
* owner queries. No FK constraint on customer_account_id /
|
|
8
|
+
* organization_id since those tables already exist; we keep the same
|
|
9
|
+
* "plain uuid columns" pattern the rest of the codebase uses.
|
|
10
|
+
* - shopping_list_items: cascade delete on the parent list, plain uuid
|
|
11
|
+
* FK to products so product archival doesn't drop list rows (the
|
|
12
|
+
* conversion service surfaces archived rows in a skip report).
|
|
13
|
+
*/
|
|
14
|
+
export class Migration20260426T135443ShoppingListsInit extends Migration {
|
|
15
|
+
async up() {
|
|
16
|
+
this.addSql(`
|
|
17
|
+
create table "shopping_lists" (
|
|
18
|
+
"id" uuid not null,
|
|
19
|
+
"organization_id" uuid not null,
|
|
20
|
+
"customer_account_id" uuid not null,
|
|
21
|
+
"name" varchar(160) not null,
|
|
22
|
+
"created_at" timestamptz not null,
|
|
23
|
+
"updated_at" timestamptz not null,
|
|
24
|
+
constraint "shopping_lists_pkey" primary key ("id")
|
|
25
|
+
);
|
|
26
|
+
`);
|
|
27
|
+
this.addSql('create index "shopping_lists_organization_id_index" on "shopping_lists" ("organization_id");');
|
|
28
|
+
this.addSql('create index "shopping_lists_customer_account_id_index" on "shopping_lists" ("customer_account_id");');
|
|
29
|
+
this.addSql(`
|
|
30
|
+
create table "shopping_list_items" (
|
|
31
|
+
"id" uuid not null,
|
|
32
|
+
"shopping_list_id" uuid not null,
|
|
33
|
+
"product_id" uuid not null,
|
|
34
|
+
"variant_id" uuid null,
|
|
35
|
+
"quantity" int not null,
|
|
36
|
+
"note" text null,
|
|
37
|
+
"created_at" timestamptz not null,
|
|
38
|
+
"updated_at" timestamptz not null,
|
|
39
|
+
constraint "shopping_list_items_pkey" primary key ("id")
|
|
40
|
+
);
|
|
41
|
+
`);
|
|
42
|
+
this.addSql('create index "shopping_list_items_shopping_list_id_index" on "shopping_list_items" ("shopping_list_id");');
|
|
43
|
+
this.addSql('create index "shopping_list_items_product_id_index" on "shopping_list_items" ("product_id");');
|
|
44
|
+
this.addSql(`alter table "shopping_list_items" add constraint "shopping_list_items_list_fk"
|
|
45
|
+
foreign key ("shopping_list_id") references "shopping_lists" ("id") on delete cascade;`);
|
|
46
|
+
}
|
|
47
|
+
async down() {
|
|
48
|
+
this.addSql('drop table if exists "shopping_list_items" cascade;');
|
|
49
|
+
this.addSql('drop table if exists "shopping_lists" cascade;');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=20260426T135443_shopping_lists_init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20260426T135443_shopping_lists_init.js","sourceRoot":"","sources":["../../src/migrations/20260426T135443_shopping_lists_init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,yCAA0C,SAAQ,SAAS;IAC7D,KAAK,CAAC,EAAE;QACf,IAAI,CAAC,MAAM,CAAC;;;;;;;;;;KAUX,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CACT,8FAA8F,CAC/F,CAAC;QACF,IAAI,CAAC,MAAM,CACT,sGAAsG,CACvG,CAAC;QAEF,IAAI,CAAC,MAAM,CAAC;;;;;;;;;;;;KAYX,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CACT,0GAA0G,CAC3G,CAAC;QACF,IAAI,CAAC,MAAM,CACT,8FAA8F,CAC/F,CAAC;QACF,IAAI,CAAC,MAAM,CACT;+FACyF,CAC1F,CAAC;IACJ,CAAC;IAEQ,KAAK,CAAC,IAAI;QACjB,IAAI,CAAC,MAAM,CAAC,qDAAqD,CAAC,CAAC;QACnE,IAAI,CAAC,MAAM,CAAC,gDAAgD,CAAC,CAAC;IAChE,CAAC;CACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
/**
|
|
3
|
+
* Default shopping list.
|
|
4
|
+
*
|
|
5
|
+
* Adds `shopping_lists.is_default`: at most one list per (organization,
|
|
6
|
+
* customer) is the customer's default — the target of the storefront
|
|
7
|
+
* "add to shopping list" affordances. Existing customers with at least one
|
|
8
|
+
* list have their earliest-created list promoted to default by the backfill;
|
|
9
|
+
* customers with no list get a "Default" list lazily on first access
|
|
10
|
+
* (ShoppingListService.ensureDefault).
|
|
11
|
+
*/
|
|
12
|
+
export declare class Migration20260611T140418ShoppingListsDefault extends Migration {
|
|
13
|
+
up(): Promise<void>;
|
|
14
|
+
down(): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=20260611T140418_shopping_lists_default.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20260611T140418_shopping_lists_default.d.ts","sourceRoot":"","sources":["../../src/migrations/20260611T140418_shopping_lists_default.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;GASG;AACH,qBAAa,4CAA6C,SAAQ,SAAS;IAC1D,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBnB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAGrC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
/**
|
|
3
|
+
* Default shopping list.
|
|
4
|
+
*
|
|
5
|
+
* Adds `shopping_lists.is_default`: at most one list per (organization,
|
|
6
|
+
* customer) is the customer's default — the target of the storefront
|
|
7
|
+
* "add to shopping list" affordances. Existing customers with at least one
|
|
8
|
+
* list have their earliest-created list promoted to default by the backfill;
|
|
9
|
+
* customers with no list get a "Default" list lazily on first access
|
|
10
|
+
* (ShoppingListService.ensureDefault).
|
|
11
|
+
*/
|
|
12
|
+
export class Migration20260611T140418ShoppingListsDefault extends Migration {
|
|
13
|
+
async up() {
|
|
14
|
+
this.addSql(`alter table "shopping_lists" add column "is_default" boolean not null default false;`);
|
|
15
|
+
// Promote the earliest-created list per (organization, customer) to default.
|
|
16
|
+
this.addSql(`
|
|
17
|
+
update "shopping_lists" s set "is_default" = true
|
|
18
|
+
where s."id" = (
|
|
19
|
+
select s2."id" from "shopping_lists" s2
|
|
20
|
+
where s2."organization_id" = s."organization_id"
|
|
21
|
+
and s2."customer_account_id" = s."customer_account_id"
|
|
22
|
+
order by s2."created_at" asc, s2."id" asc
|
|
23
|
+
limit 1
|
|
24
|
+
);
|
|
25
|
+
`);
|
|
26
|
+
}
|
|
27
|
+
async down() {
|
|
28
|
+
this.addSql(`alter table "shopping_lists" drop column "is_default";`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=20260611T140418_shopping_lists_default.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20260611T140418_shopping_lists_default.js","sourceRoot":"","sources":["../../src/migrations/20260611T140418_shopping_lists_default.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;GASG;AACH,MAAM,OAAO,4CAA6C,SAAQ,SAAS;IAChE,KAAK,CAAC,EAAE;QACf,IAAI,CAAC,MAAM,CACT,sFAAsF,CACvF,CAAC;QACF,6EAA6E;QAC7E,IAAI,CAAC,MAAM,CAAC;;;;;;;;;KASX,CAAC,CAAC;IACL,CAAC;IAEQ,KAAK,CAAC,IAAI;QACjB,IAAI,CAAC,MAAM,CAAC,wDAAwD,CAAC,CAAC;IACxE,CAAC;CACF"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `./migrations` subpath — every migration class this module owns, as one
|
|
3
|
+
* ordered `migrations` array.
|
|
4
|
+
*
|
|
5
|
+
* The array is what the platform reads when this module is **installed**:
|
|
6
|
+
* `src/packages/package-runtime.ts` takes `exported['migrations']` and refuses
|
|
7
|
+
* the package outright when it is absent (D-168).
|
|
8
|
+
*
|
|
9
|
+
* Listed in ascending timestamp, which is the order of this module's own
|
|
10
|
+
* migrations and of nothing else (feature 081): a manifest `dependencies` array
|
|
11
|
+
* is the only thing ordering this block against another module's.
|
|
12
|
+
*
|
|
13
|
+
* The **named** exports stay beside the array, and the asymmetry with
|
|
14
|
+
* `./backend` — which publishes an array and no named class (D-168) — is
|
|
15
|
+
* deliberate. `db/migrations-registry.generated.ts` imports each class by name
|
|
16
|
+
* from this specifier, and a migration class name is contract in a way an entity
|
|
17
|
+
* class name is not: `mikro_orm_migrations` persists it, so it is a string every
|
|
18
|
+
* already-migrated database holds.
|
|
19
|
+
*
|
|
20
|
+
* A class that is in neither the array nor the barrel is a migration that does
|
|
21
|
+
* not run: `migration:pending` reports nothing pending and the first symptom is
|
|
22
|
+
* a query against a table nobody created.
|
|
23
|
+
*/
|
|
24
|
+
import { Migration20260426T135443ShoppingListsInit } from './20260426T135443_shopping_lists_init.js';
|
|
25
|
+
import { Migration20260611T140418ShoppingListsDefault } from './20260611T140418_shopping_lists_default.js';
|
|
26
|
+
export declare const migrations: (typeof Migration20260426T135443ShoppingListsInit)[];
|
|
27
|
+
export { Migration20260426T135443ShoppingListsInit, Migration20260611T140418ShoppingListsDefault, };
|
|
28
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,yCAAyC,EAAE,MAAM,0CAA0C,CAAC;AACrG,OAAO,EAAE,4CAA4C,EAAE,MAAM,6CAA6C,CAAC;AAE3G,eAAO,MAAM,UAAU,sDAGtB,CAAC;AAEF,OAAO,EACL,yCAAyC,EACzC,4CAA4C,GAC7C,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `./migrations` subpath — every migration class this module owns, as one
|
|
3
|
+
* ordered `migrations` array.
|
|
4
|
+
*
|
|
5
|
+
* The array is what the platform reads when this module is **installed**:
|
|
6
|
+
* `src/packages/package-runtime.ts` takes `exported['migrations']` and refuses
|
|
7
|
+
* the package outright when it is absent (D-168).
|
|
8
|
+
*
|
|
9
|
+
* Listed in ascending timestamp, which is the order of this module's own
|
|
10
|
+
* migrations and of nothing else (feature 081): a manifest `dependencies` array
|
|
11
|
+
* is the only thing ordering this block against another module's.
|
|
12
|
+
*
|
|
13
|
+
* The **named** exports stay beside the array, and the asymmetry with
|
|
14
|
+
* `./backend` — which publishes an array and no named class (D-168) — is
|
|
15
|
+
* deliberate. `db/migrations-registry.generated.ts` imports each class by name
|
|
16
|
+
* from this specifier, and a migration class name is contract in a way an entity
|
|
17
|
+
* class name is not: `mikro_orm_migrations` persists it, so it is a string every
|
|
18
|
+
* already-migrated database holds.
|
|
19
|
+
*
|
|
20
|
+
* A class that is in neither the array nor the barrel is a migration that does
|
|
21
|
+
* not run: `migration:pending` reports nothing pending and the first symptom is
|
|
22
|
+
* a query against a table nobody created.
|
|
23
|
+
*/
|
|
24
|
+
import { Migration20260426T135443ShoppingListsInit } from './20260426T135443_shopping_lists_init.js';
|
|
25
|
+
import { Migration20260611T140418ShoppingListsDefault } from './20260611T140418_shopping_lists_default.js';
|
|
26
|
+
export const migrations = [
|
|
27
|
+
Migration20260426T135443ShoppingListsInit,
|
|
28
|
+
Migration20260611T140418ShoppingListsDefault,
|
|
29
|
+
];
|
|
30
|
+
export { Migration20260426T135443ShoppingListsInit, Migration20260611T140418ShoppingListsDefault, };
|
|
31
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,yCAAyC,EAAE,MAAM,0CAA0C,CAAC;AACrG,OAAO,EAAE,4CAA4C,EAAE,MAAM,6CAA6C,CAAC;AAE3G,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,yCAAyC;IACzC,4CAA4C;CAC7C,CAAC;AAEF,OAAO,EACL,yCAAyC,EACzC,4CAA4C,GAC7C,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: shopping_lists
|
|
3
|
+
description: Per-customer named bundles convertible to Cart or RFQ
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# `shopping_lists`
|
|
7
|
+
|
|
8
|
+
Per-customer named bundles of (product, variant?, quantity, note?) rows.
|
|
9
|
+
Buyers save recurring orders, then convert all or a selected subset of
|
|
10
|
+
items into a Cart or a draft Quote Request in a single click. Conversions
|
|
11
|
+
skip archived products and report them rather than failing the whole batch.
|
|
12
|
+
|
|
13
|
+
## Public surface
|
|
14
|
+
|
|
15
|
+
All endpoints require an authenticated customer session and are scoped to
|
|
16
|
+
the `(customer, organization)` pair on the session.
|
|
17
|
+
|
|
18
|
+
| Verb + Path | Purpose |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| `GET /api/v1/shopping-lists` | List the buyer's lists with their items |
|
|
21
|
+
| `POST /api/v1/shopping-lists` | Create a new list |
|
|
22
|
+
| `GET /api/v1/shopping-lists/:id` | One list with its items |
|
|
23
|
+
| `PATCH /api/v1/shopping-lists/:id` | Rename |
|
|
24
|
+
| `DELETE /api/v1/shopping-lists/:id` | Delete (cascades to items) |
|
|
25
|
+
| `POST /api/v1/shopping-lists/:id/items` | Add an item |
|
|
26
|
+
| `PATCH /api/v1/shopping-lists/:id/items/:itemId` | Update quantity / note |
|
|
27
|
+
| `DELETE /api/v1/shopping-lists/:id/items/:itemId` | Remove |
|
|
28
|
+
| `POST /api/v1/shopping-lists/:id/convert-to-cart` | Add items to the active cart; archived rows are skipped + reported |
|
|
29
|
+
| `POST /api/v1/shopping-lists/:id/convert-to-rfq` | Add items to the draft RFQ; returns the `rfqId` for deep-linking |
|
|
30
|
+
|
|
31
|
+
The two converters accept an optional `itemIds: string[]` body. Empty or
|
|
32
|
+
missing array means "convert every item on the list".
|
|
33
|
+
|
|
34
|
+
## Conversion semantics
|
|
35
|
+
|
|
36
|
+
`ShoppingListService` partitions the requested items by their referencing
|
|
37
|
+
product's status:
|
|
38
|
+
|
|
39
|
+
- `active` products go into the cart / RFQ via the existing
|
|
40
|
+
`CartService.addItem` / `RfqService.addItem` so cart aggregation and
|
|
41
|
+
RFQ draft version bumps stay in one place.
|
|
42
|
+
- `archived` products (or rows whose product no longer exists) are
|
|
43
|
+
reported in `skipped[]` with one of:
|
|
44
|
+
- `product_archived`
|
|
45
|
+
- `product_not_found`
|
|
46
|
+
- `variant_unavailable`
|
|
47
|
+
|
|
48
|
+
The HTTP response is always `200 OK` with `{ added, skipped }` so the UI
|
|
49
|
+
can show a friendly message rather than treating the partial result as
|
|
50
|
+
an error.
|
|
51
|
+
|
|
52
|
+
## Entities
|
|
53
|
+
|
|
54
|
+
`ShoppingList` — `(organizationId, customerAccountId, name)`.
|
|
55
|
+
|
|
56
|
+
`ShoppingListItem` — cascade-deletes with its parent list. The FK to
|
|
57
|
+
`products` is intentionally a plain uuid column without a CASCADE so a
|
|
58
|
+
product archive doesn't drop historical rows; the conversion service
|
|
59
|
+
surfaces them in the skip report instead.
|
|
60
|
+
|
|
61
|
+
## Extension points
|
|
62
|
+
|
|
63
|
+
- **Sharing within an organization** — the entity has no `isShared`
|
|
64
|
+
column today; adding org-wide sharing means a column flip plus a
|
|
65
|
+
one-line tweak to `#owned()` in `shopping-list-service.ts`.
|
|
66
|
+
- **Per-list deadlines** — extend `ShoppingList` with `expiresAt` for
|
|
67
|
+
procurement workflows that auto-archive stale lists.
|
|
68
|
+
- **Convert-to-quote-only-mode** — the converter already returns an RFQ
|
|
69
|
+
id; the storefront can deep-link the buyer into the draft to add a
|
|
70
|
+
requester note before submission.
|
package/i18n/en.json
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{
|
|
2
|
+
"errors.SHOPPING_LIST_CANNOT_DELETE_DEFAULT": "Your default shopping list is the one new items are saved to, so it cannot be deleted. Empty it if you no longer need what is on it, or make another list the default first — then this one can go.",
|
|
3
|
+
"errors.SHOPPING_LIST_CANNOT_DELETE_LAST": "This is your only shopping list, and an account always keeps at least one. Create another list first, and then this one can be deleted."
|
|
4
|
+
}
|
package/i18n/pl.json
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{
|
|
2
|
+
"errors.SHOPPING_LIST_CANNOT_DELETE_DEFAULT": "Domyślna lista zakupowa to ta, na której zapisują się nowe produkty, dlatego nie można jej usunąć. Opróżnij ją, jeśli nie potrzebujesz już jej zawartości, albo najpierw ustaw jako domyślną inną listę — wtedy tę będzie można usunąć.",
|
|
3
|
+
"errors.SHOPPING_LIST_CANNOT_DELETE_LAST": "To Twoja jedyna lista zakupowa, a na koncie zawsze musi pozostać co najmniej jedna. Najpierw utwórz kolejną listę, a potem tę będzie można usunąć."
|
|
4
|
+
}
|