@open-mercato/core 0.4.5-develop-3ce83a8b24 → 0.4.5-develop-539cff4960
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/dist/generated/entities/catalog_product/index.js +16 -0
- package/dist/generated/entities/catalog_product/index.js.map +2 -2
- package/dist/generated/entities/catalog_product_unit_conversion/index.js +27 -0
- package/dist/generated/entities/catalog_product_unit_conversion/index.js.map +7 -0
- package/dist/generated/entities/sales_credit_memo_line/index.js +7 -1
- package/dist/generated/entities/sales_credit_memo_line/index.js.map +2 -2
- package/dist/generated/entities/sales_invoice_line/index.js +7 -1
- package/dist/generated/entities/sales_invoice_line/index.js.map +2 -2
- package/dist/generated/entities/sales_order_line/index.js +6 -0
- package/dist/generated/entities/sales_order_line/index.js.map +2 -2
- package/dist/generated/entities/sales_quote_line/index.js +6 -0
- package/dist/generated/entities/sales_quote_line/index.js.map +2 -2
- package/dist/generated/entities.ids.generated.js +1 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/catalog/api/prices/route.js +123 -8
- package/dist/modules/catalog/api/prices/route.js.map +2 -2
- package/dist/modules/catalog/api/product-unit-conversions/route.js +194 -0
- package/dist/modules/catalog/api/product-unit-conversions/route.js.map +7 -0
- package/dist/modules/catalog/api/products/route.js +351 -201
- package/dist/modules/catalog/api/products/route.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js +1267 -497
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/create/page.js +733 -210
- package/dist/modules/catalog/backend/catalog/products/create/page.js.map +2 -2
- package/dist/modules/catalog/commands/index.js +1 -0
- package/dist/modules/catalog/commands/index.js.map +2 -2
- package/dist/modules/catalog/commands/productUnitConversions.js +503 -0
- package/dist/modules/catalog/commands/productUnitConversions.js.map +7 -0
- package/dist/modules/catalog/commands/products.js +355 -73
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/catalog/commands/shared.js +18 -4
- package/dist/modules/catalog/commands/shared.js.map +2 -2
- package/dist/modules/catalog/components/products/ProductUomSection.js +591 -0
- package/dist/modules/catalog/components/products/ProductUomSection.js.map +7 -0
- package/dist/modules/catalog/components/products/productForm.js +66 -5
- package/dist/modules/catalog/components/products/productForm.js.map +2 -2
- package/dist/modules/catalog/components/products/productFormUtils.js +68 -0
- package/dist/modules/catalog/components/products/productFormUtils.js.map +7 -0
- package/dist/modules/catalog/data/entities.js +86 -0
- package/dist/modules/catalog/data/entities.js.map +2 -2
- package/dist/modules/catalog/data/validators.js +65 -3
- package/dist/modules/catalog/data/validators.js.map +2 -2
- package/dist/modules/catalog/events.js +3 -0
- package/dist/modules/catalog/events.js.map +2 -2
- package/dist/modules/catalog/lib/unitCodes.js +7 -0
- package/dist/modules/catalog/lib/unitCodes.js.map +7 -0
- package/dist/modules/catalog/lib/unitResolution.js +53 -0
- package/dist/modules/catalog/lib/unitResolution.js.map +7 -0
- package/dist/modules/catalog/migrations/Migration20260218225422.js +19 -0
- package/dist/modules/catalog/migrations/Migration20260218225422.js.map +7 -0
- package/dist/modules/catalog/migrations/Migration20260219084500.js +27 -0
- package/dist/modules/catalog/migrations/Migration20260219084500.js.map +7 -0
- package/dist/modules/catalog/search.js +69 -1
- package/dist/modules/catalog/search.js.map +2 -2
- package/dist/modules/catalog/seed/examples.js +91 -42
- package/dist/modules/catalog/seed/examples.js.map +2 -2
- package/dist/modules/dashboards/seed/analytics.js +3 -0
- package/dist/modules/dashboards/seed/analytics.js.map +2 -2
- package/dist/modules/sales/api/order-lines/route.js +98 -15
- package/dist/modules/sales/api/order-lines/route.js.map +2 -2
- package/dist/modules/sales/api/quote-lines/route.js +101 -14
- package/dist/modules/sales/api/quote-lines/route.js.map +2 -2
- package/dist/modules/sales/api/quotes/public/[token]/route.js +87 -12
- package/dist/modules/sales/api/quotes/public/[token]/route.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +1424 -260
- package/dist/modules/sales/commands/documents.js.map +3 -3
- package/dist/modules/sales/commands/shared.js +6 -2
- package/dist/modules/sales/commands/shared.js.map +2 -2
- package/dist/modules/sales/components/documents/ItemsSection.js +216 -86
- package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +913 -241
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +3 -3
- package/dist/modules/sales/components/documents/ShipmentsSection.js +15 -3
- package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
- package/dist/modules/sales/data/entities.js +59 -3
- package/dist/modules/sales/data/entities.js.map +2 -2
- package/dist/modules/sales/data/validators.js +35 -0
- package/dist/modules/sales/data/validators.js.map +2 -2
- package/dist/modules/sales/frontend/quote/[token]/page.js +15 -1
- package/dist/modules/sales/frontend/quote/[token]/page.js.map +2 -2
- package/dist/modules/sales/migrations/Migration20260218225423.js +31 -0
- package/dist/modules/sales/migrations/Migration20260218225423.js.map +7 -0
- package/dist/modules/sales/migrations/Migration20260219084501.js +71 -0
- package/dist/modules/sales/migrations/Migration20260219084501.js.map +7 -0
- package/dist/modules/sales/search.js +28 -0
- package/dist/modules/sales/search.js.map +2 -2
- package/dist/modules/sales/seed/examples.js +14 -1
- package/dist/modules/sales/seed/examples.js.map +2 -2
- package/dist/modules/sales/widgets/injection/document-history/widget.client.js +1 -1
- package/dist/modules/sales/widgets/injection/document-history/widget.client.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js +28 -15
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
- package/dist/modules/staff/translations.js +9 -0
- package/dist/modules/staff/translations.js.map +7 -0
- package/dist/modules/translations/components/TranslationDrawerAction.js +97 -0
- package/dist/modules/translations/components/TranslationDrawerAction.js.map +7 -0
- package/dist/modules/translations/lib/extract-record-id.js +31 -2
- package/dist/modules/translations/lib/extract-record-id.js.map +2 -2
- package/dist/modules/translations/lib/resolve-field-list.js +3 -0
- package/dist/modules/translations/lib/resolve-field-list.js.map +2 -2
- package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js +105 -36
- package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js.map +2 -2
- package/dist/modules/translations/widgets/injection-table.js +18 -29
- package/dist/modules/translations/widgets/injection-table.js.map +2 -2
- package/generated/entities/catalog_product/index.ts +8 -0
- package/generated/entities/catalog_product_unit_conversion/index.ts +12 -0
- package/generated/entities/sales_credit_memo_line/index.ts +3 -0
- package/generated/entities/sales_invoice_line/index.ts +3 -0
- package/generated/entities/sales_order_line/index.ts +3 -0
- package/generated/entities/sales_quote_line/index.ts +3 -0
- package/generated/entities.ids.generated.ts +1 -0
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +2 -2
- package/src/modules/auth/i18n/de.json +1 -1
- package/src/modules/auth/i18n/en.json +1 -1
- package/src/modules/auth/i18n/es.json +1 -1
- package/src/modules/auth/i18n/pl.json +1 -1
- package/src/modules/catalog/api/prices/route.ts +213 -81
- package/src/modules/catalog/api/product-unit-conversions/route.ts +195 -0
- package/src/modules/catalog/api/products/route.ts +638 -402
- package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +2085 -1072
- package/src/modules/catalog/backend/catalog/products/create/page.tsx +1288 -593
- package/src/modules/catalog/commands/index.ts +1 -0
- package/src/modules/catalog/commands/productUnitConversions.ts +626 -0
- package/src/modules/catalog/commands/products.ts +1151 -693
- package/src/modules/catalog/commands/shared.ts +19 -5
- package/src/modules/catalog/components/products/ProductUomSection.tsx +745 -0
- package/src/modules/catalog/components/products/productForm.ts +369 -256
- package/src/modules/catalog/components/products/productFormUtils.ts +82 -0
- package/src/modules/catalog/data/entities.ts +82 -1
- package/src/modules/catalog/data/validators.ts +118 -34
- package/src/modules/catalog/events.ts +3 -0
- package/src/modules/catalog/i18n/de.json +56 -0
- package/src/modules/catalog/i18n/en.json +56 -0
- package/src/modules/catalog/i18n/es.json +56 -0
- package/src/modules/catalog/i18n/pl.json +56 -0
- package/src/modules/catalog/lib/unitCodes.ts +1 -0
- package/src/modules/catalog/lib/unitResolution.ts +62 -0
- package/src/modules/catalog/migrations/.snapshot-open-mercato.json +245 -0
- package/src/modules/catalog/migrations/Migration20260218225422.ts +21 -0
- package/src/modules/catalog/migrations/Migration20260219084500.ts +26 -0
- package/src/modules/catalog/search.ts +73 -1
- package/src/modules/catalog/seed/examples.ts +552 -479
- package/src/modules/dashboards/i18n/de.json +1 -1
- package/src/modules/dashboards/i18n/en.json +1 -1
- package/src/modules/dashboards/i18n/es.json +1 -1
- package/src/modules/dashboards/i18n/pl.json +1 -1
- package/src/modules/dashboards/seed/analytics.ts +3 -0
- package/src/modules/sales/api/order-lines/route.ts +158 -68
- package/src/modules/sales/api/quote-lines/route.ts +161 -67
- package/src/modules/sales/api/quotes/public/[token]/route.ts +122 -36
- package/src/modules/sales/commands/documents.ts +4250 -2424
- package/src/modules/sales/commands/shared.ts +7 -2
- package/src/modules/sales/components/documents/ItemsSection.tsx +580 -310
- package/src/modules/sales/components/documents/LineItemDialog.tsx +1988 -833
- package/src/modules/sales/components/documents/ShipmentsSection.tsx +17 -3
- package/src/modules/sales/components/documents/lineItemTypes.ts +6 -0
- package/src/modules/sales/data/entities.ts +53 -0
- package/src/modules/sales/data/validators.ts +36 -0
- package/src/modules/sales/frontend/quote/[token]/page.tsx +25 -1
- package/src/modules/sales/i18n/de.json +23 -3
- package/src/modules/sales/i18n/en.json +23 -3
- package/src/modules/sales/i18n/es.json +23 -3
- package/src/modules/sales/i18n/pl.json +23 -3
- package/src/modules/sales/lib/types.ts +30 -0
- package/src/modules/sales/migrations/.snapshot-open-mercato.json +172 -0
- package/src/modules/sales/migrations/Migration20260218225423.ts +37 -0
- package/src/modules/sales/migrations/Migration20260219084501.ts +73 -0
- package/src/modules/sales/search.ts +28 -0
- package/src/modules/sales/seed/examples.ts +20 -1
- package/src/modules/sales/widgets/injection/document-history/widget.client.tsx +1 -1
- package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +8 -0
- package/src/modules/staff/translations.ts +5 -0
- package/src/modules/translations/components/TranslationDrawerAction.tsx +107 -0
- package/src/modules/translations/lib/extract-record-id.ts +47 -3
- package/src/modules/translations/lib/resolve-field-list.ts +4 -0
- package/src/modules/translations/widgets/injection/translation-manager/widget.client.tsx +108 -36
- package/src/modules/translations/widgets/injection-table.ts +19 -33
- package/src/modules/workflows/i18n/de.json +4 -4
- package/src/modules/workflows/i18n/en.json +4 -4
- package/src/modules/workflows/i18n/es.json +4 -4
- package/src/modules/workflows/i18n/pl.json +4 -4
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { makeCrudRoute } from "@open-mercato/shared/lib/crud/factory";
|
|
3
3
|
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
buildCustomFieldFiltersFromQuery,
|
|
6
|
+
extractAllCustomFieldEntries
|
|
7
|
+
} from "@open-mercato/shared/lib/crud/custom-fields";
|
|
5
8
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
6
9
|
import {
|
|
7
10
|
CatalogOffer,
|
|
@@ -9,11 +12,15 @@ import {
|
|
|
9
12
|
CatalogProductCategory,
|
|
10
13
|
CatalogProductCategoryAssignment,
|
|
11
14
|
CatalogProductPrice,
|
|
15
|
+
CatalogProductUnitConversion,
|
|
12
16
|
CatalogProductVariant,
|
|
13
17
|
CatalogProductTagAssignment
|
|
14
18
|
} from "../../data/entities.js";
|
|
15
19
|
import { CATALOG_PRODUCT_TYPES } from "../../data/types.js";
|
|
16
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
productCreateSchema,
|
|
22
|
+
productUpdateSchema
|
|
23
|
+
} from "../../data/validators.js";
|
|
17
24
|
import { parseScopedCommandInput, resolveCrudRecordId } from "../utils.js";
|
|
18
25
|
import { splitCustomFieldPayload } from "@open-mercato/shared/lib/crud/custom-fields";
|
|
19
26
|
import { E } from "../../../../generated/entities.ids.generated.js";
|
|
@@ -35,6 +42,7 @@ import {
|
|
|
35
42
|
defaultOkResponseSchema
|
|
36
43
|
} from "../openapi.js";
|
|
37
44
|
import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
45
|
+
import { canonicalizeUnitCode, toUnitLookupKey } from "../../lib/unitCodes.js";
|
|
38
46
|
const rawBodySchema = z.object({}).passthrough();
|
|
39
47
|
const UUID_REGEX = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/;
|
|
40
48
|
const listSchema = z.object({
|
|
@@ -56,6 +64,7 @@ const listSchema = z.object({
|
|
|
56
64
|
customerId: z.string().uuid().optional(),
|
|
57
65
|
customerGroupId: z.string().uuid().optional(),
|
|
58
66
|
quantity: z.coerce.number().min(1).max(1e5).optional(),
|
|
67
|
+
quantityUnit: z.string().trim().max(50).optional(),
|
|
59
68
|
priceDate: z.string().optional(),
|
|
60
69
|
sortField: z.string().optional(),
|
|
61
70
|
sortDir: z.enum(["asc", "desc"]).optional(),
|
|
@@ -148,25 +157,36 @@ async function buildProductFilters(query, ctx) {
|
|
|
148
157
|
if (query.productType) {
|
|
149
158
|
filters.product_type = { $eq: query.productType };
|
|
150
159
|
}
|
|
160
|
+
const scope = {
|
|
161
|
+
organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
|
|
162
|
+
tenantId: ctx.auth?.tenantId ?? null
|
|
163
|
+
};
|
|
151
164
|
const channelFilterIds = parseIdList(query.channelIds);
|
|
152
165
|
if (channelFilterIds.length) {
|
|
153
|
-
const offerRows = await
|
|
166
|
+
const offerRows = await findWithDecryption(
|
|
167
|
+
em,
|
|
154
168
|
CatalogOffer,
|
|
155
169
|
{
|
|
156
170
|
channelId: { $in: channelFilterIds },
|
|
157
|
-
deletedAt: null
|
|
171
|
+
deletedAt: null,
|
|
172
|
+
...scope
|
|
158
173
|
},
|
|
159
|
-
{ fields: ["id", "product"] }
|
|
174
|
+
{ fields: ["id", "product"] },
|
|
175
|
+
scope
|
|
160
176
|
);
|
|
161
|
-
const productIds = offerRows.map(
|
|
177
|
+
const productIds = offerRows.map(
|
|
178
|
+
(offer) => typeof offer.product === "string" ? offer.product : offer.product?.id ?? null
|
|
179
|
+
).filter((id) => !!id);
|
|
162
180
|
intersectProductIds(productIds);
|
|
163
181
|
}
|
|
164
182
|
const categoryFilterIds = parseIdList(query.categoryIds);
|
|
165
183
|
if (categoryFilterIds.length) {
|
|
166
|
-
const assignments = await
|
|
184
|
+
const assignments = await findWithDecryption(
|
|
185
|
+
em,
|
|
167
186
|
CatalogProductCategoryAssignment,
|
|
168
|
-
{ category: { $in: categoryFilterIds } },
|
|
169
|
-
{ fields: ["id", "product"] }
|
|
187
|
+
{ category: { $in: categoryFilterIds }, ...scope },
|
|
188
|
+
{ fields: ["id", "product"] },
|
|
189
|
+
scope
|
|
170
190
|
);
|
|
171
191
|
const productIds = assignments.map(
|
|
172
192
|
(assignment) => typeof assignment.product === "string" ? assignment.product : assignment.product?.id ?? null
|
|
@@ -175,10 +195,12 @@ async function buildProductFilters(query, ctx) {
|
|
|
175
195
|
}
|
|
176
196
|
const tagFilterIds = parseIdList(query.tagIds);
|
|
177
197
|
if (tagFilterIds.length) {
|
|
178
|
-
const assignments = await
|
|
198
|
+
const assignments = await findWithDecryption(
|
|
199
|
+
em,
|
|
179
200
|
CatalogProductTagAssignment,
|
|
180
|
-
{ tag: { $in: tagFilterIds } },
|
|
181
|
-
{ fields: ["id", "product"] }
|
|
201
|
+
{ tag: { $in: tagFilterIds }, ...scope },
|
|
202
|
+
{ fields: ["id", "product"] },
|
|
203
|
+
scope
|
|
182
204
|
);
|
|
183
205
|
const productIds = assignments.map(
|
|
184
206
|
(assignment) => typeof assignment.product === "string" ? assignment.product : assignment.product?.id ?? null
|
|
@@ -197,7 +219,8 @@ async function buildProductFilters(query, ctx) {
|
|
|
197
219
|
fieldset: customFieldset ?? void 0
|
|
198
220
|
});
|
|
199
221
|
Object.assign(filters, cfFilters);
|
|
200
|
-
} catch {
|
|
222
|
+
} catch (err) {
|
|
223
|
+
if (process.env.NODE_ENV === "development") console.warn("[catalog:products] custom field filter error", err);
|
|
201
224
|
}
|
|
202
225
|
applyRestrictedProducts();
|
|
203
226
|
return filters;
|
|
@@ -222,198 +245,274 @@ async function decorateProductsAfterList(payload, ctx) {
|
|
|
222
245
|
if (!items.length) return;
|
|
223
246
|
const productIds = items.map((item) => typeof item.id === "string" ? item.id : null).filter((id) => !!id);
|
|
224
247
|
if (!productIds.length) return;
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const
|
|
239
|
-
|
|
248
|
+
try {
|
|
249
|
+
const em = ctx.container.resolve("em").fork();
|
|
250
|
+
const scope = {
|
|
251
|
+
organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
|
|
252
|
+
tenantId: ctx.auth?.tenantId ?? null
|
|
253
|
+
};
|
|
254
|
+
const offers = await findWithDecryption(
|
|
255
|
+
em,
|
|
256
|
+
CatalogOffer,
|
|
257
|
+
{ product: { $in: productIds }, deletedAt: null, ...scope },
|
|
258
|
+
{ orderBy: { createdAt: "asc" } },
|
|
259
|
+
scope
|
|
260
|
+
);
|
|
261
|
+
const channelIds = Array.from(
|
|
262
|
+
new Set(
|
|
263
|
+
offers.map((offer) => offer.channelId).filter(
|
|
264
|
+
(id) => typeof id === "string" && id.length > 0
|
|
265
|
+
)
|
|
266
|
+
)
|
|
267
|
+
);
|
|
268
|
+
const channelLookup = /* @__PURE__ */ new Map();
|
|
269
|
+
if (channelIds.length) {
|
|
270
|
+
const scopedChannelsWhere = buildScopedWhere(
|
|
271
|
+
{ id: { $in: channelIds } },
|
|
272
|
+
{
|
|
273
|
+
organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
|
|
274
|
+
organizationIds: Array.isArray(ctx.organizationIds) ? ctx.organizationIds : void 0,
|
|
275
|
+
tenantId: ctx.auth?.tenantId ?? null
|
|
276
|
+
}
|
|
277
|
+
);
|
|
278
|
+
const channels = await findWithDecryption(em, SalesChannel, scopedChannelsWhere, {
|
|
279
|
+
fields: ["id", "name", "code"]
|
|
280
|
+
});
|
|
281
|
+
for (const channel of channels) {
|
|
282
|
+
channelLookup.set(channel.id, {
|
|
283
|
+
name: channel.name,
|
|
284
|
+
code: channel.code ?? null
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const offersByProduct = /* @__PURE__ */ new Map();
|
|
289
|
+
for (const offer of offers) {
|
|
290
|
+
const productId = typeof offer.product === "string" ? offer.product : offer.product?.id ?? null;
|
|
291
|
+
if (!productId) continue;
|
|
292
|
+
const channelInfo = channelLookup.get(offer.channelId);
|
|
293
|
+
const entry = offersByProduct.get(productId) ?? [];
|
|
294
|
+
entry.push({
|
|
295
|
+
id: offer.id,
|
|
296
|
+
channelId: offer.channelId,
|
|
297
|
+
channelName: channelInfo?.name ?? null,
|
|
298
|
+
channelCode: channelInfo?.code ?? null,
|
|
299
|
+
title: offer.title,
|
|
300
|
+
description: offer.description ?? null,
|
|
301
|
+
isActive: offer.isActive,
|
|
302
|
+
defaultMediaId: offer.defaultMediaId ?? null,
|
|
303
|
+
defaultMediaUrl: offer.defaultMediaUrl ?? null,
|
|
304
|
+
metadata: offer.metadata ?? null
|
|
305
|
+
});
|
|
306
|
+
offersByProduct.set(productId, entry);
|
|
307
|
+
}
|
|
308
|
+
const categoryAssignments = await findWithDecryption(
|
|
309
|
+
em,
|
|
310
|
+
CatalogProductCategoryAssignment,
|
|
311
|
+
{ product: { $in: productIds }, ...scope },
|
|
312
|
+
{ populate: ["category"], orderBy: { position: "asc" } },
|
|
313
|
+
scope
|
|
314
|
+
);
|
|
315
|
+
const parentIds = /* @__PURE__ */ new Set();
|
|
316
|
+
for (const assignment of categoryAssignments) {
|
|
317
|
+
const category = typeof assignment.category === "string" ? null : assignment.category ?? null;
|
|
318
|
+
if (!category) continue;
|
|
319
|
+
const parentId = category.parentId ?? null;
|
|
320
|
+
if (parentId) parentIds.add(parentId);
|
|
321
|
+
}
|
|
322
|
+
const parentCategories = parentIds.size ? await findWithDecryption(
|
|
323
|
+
em,
|
|
324
|
+
CatalogProductCategory,
|
|
325
|
+
{ id: { $in: Array.from(parentIds) }, ...scope },
|
|
326
|
+
{ fields: ["id", "name"] },
|
|
327
|
+
scope
|
|
328
|
+
) : [];
|
|
329
|
+
const parentNameById = /* @__PURE__ */ new Map();
|
|
330
|
+
for (const parent of parentCategories) {
|
|
331
|
+
parentNameById.set(parent.id, parent.name ?? null);
|
|
332
|
+
}
|
|
333
|
+
const categoriesByProduct = /* @__PURE__ */ new Map();
|
|
334
|
+
for (const assignment of categoryAssignments) {
|
|
335
|
+
const productId = typeof assignment.product === "string" ? assignment.product : assignment.product?.id ?? null;
|
|
336
|
+
if (!productId) continue;
|
|
337
|
+
const category = typeof assignment.category === "string" ? null : assignment.category ?? null;
|
|
338
|
+
if (!category) continue;
|
|
339
|
+
const parentId = category.parentId ?? null;
|
|
340
|
+
const parentName = parentId ? parentNameById.get(parentId) ?? null : null;
|
|
341
|
+
const bucket = categoriesByProduct.get(productId) ?? [];
|
|
342
|
+
bucket.push({
|
|
343
|
+
id: category.id,
|
|
344
|
+
name: category.name ?? null,
|
|
345
|
+
treePath: category.treePath ?? null,
|
|
346
|
+
parentId,
|
|
347
|
+
parentName
|
|
348
|
+
});
|
|
349
|
+
categoriesByProduct.set(productId, bucket);
|
|
350
|
+
}
|
|
351
|
+
const tagAssignments = await findWithDecryption(
|
|
352
|
+
em,
|
|
353
|
+
CatalogProductTagAssignment,
|
|
354
|
+
{ product: { $in: productIds } },
|
|
355
|
+
{ populate: ["tag"] },
|
|
240
356
|
{
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
tenantId: ctx.auth?.tenantId ?? null
|
|
357
|
+
tenantId: ctx.auth?.tenantId ?? null,
|
|
358
|
+
organizationId: ctx.auth?.orgId ?? null
|
|
244
359
|
}
|
|
245
360
|
);
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
361
|
+
const tagsByProduct = /* @__PURE__ */ new Map();
|
|
362
|
+
for (const assignment of tagAssignments) {
|
|
363
|
+
const productId = typeof assignment.product === "string" ? assignment.product : assignment.product?.id ?? null;
|
|
364
|
+
if (!productId) continue;
|
|
365
|
+
const tag = typeof assignment.tag === "string" ? null : assignment.tag ?? null;
|
|
366
|
+
if (!tag) continue;
|
|
367
|
+
const label = typeof tag.label === "string" && tag.label.trim().length ? tag.label : null;
|
|
368
|
+
if (!label) continue;
|
|
369
|
+
const bucket = tagsByProduct.get(productId) ?? [];
|
|
370
|
+
bucket.push(label);
|
|
371
|
+
tagsByProduct.set(productId, bucket);
|
|
372
|
+
}
|
|
373
|
+
const variants = await findWithDecryption(
|
|
374
|
+
em,
|
|
375
|
+
CatalogProductVariant,
|
|
376
|
+
{ product: { $in: productIds }, deletedAt: null, ...scope },
|
|
377
|
+
{ fields: ["id", "product"] },
|
|
378
|
+
scope
|
|
379
|
+
);
|
|
380
|
+
const variantToProduct = /* @__PURE__ */ new Map();
|
|
381
|
+
for (const variant of variants) {
|
|
382
|
+
const productId = typeof variant.product === "string" ? variant.product : variant.product?.id ?? null;
|
|
383
|
+
if (!productId) continue;
|
|
384
|
+
variantToProduct.set(variant.id, productId);
|
|
385
|
+
}
|
|
386
|
+
const variantIds = Array.from(variantToProduct.keys());
|
|
387
|
+
const priceWhere = variantIds.length > 0 ? {
|
|
388
|
+
$or: [
|
|
389
|
+
{ product: { $in: productIds } },
|
|
390
|
+
{ variant: { $in: variantIds } }
|
|
391
|
+
]
|
|
392
|
+
} : { product: { $in: productIds } };
|
|
393
|
+
const priceRows = await findWithDecryption(
|
|
394
|
+
em,
|
|
395
|
+
CatalogProductPrice,
|
|
396
|
+
{ ...priceWhere, ...scope },
|
|
397
|
+
{ populate: ["offer", "variant", "product", "priceKind"] },
|
|
398
|
+
scope
|
|
250
399
|
);
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
400
|
+
const pricesByProduct = /* @__PURE__ */ new Map();
|
|
401
|
+
for (const price of priceRows) {
|
|
402
|
+
let productId = null;
|
|
403
|
+
if (price.product) {
|
|
404
|
+
productId = typeof price.product === "string" ? price.product : price.product?.id ?? null;
|
|
405
|
+
} else if (price.variant) {
|
|
406
|
+
const variantId = typeof price.variant === "string" ? price.variant : price.variant.id;
|
|
407
|
+
productId = variantToProduct.get(variantId) ?? null;
|
|
408
|
+
}
|
|
409
|
+
if (!productId) continue;
|
|
410
|
+
const entry = pricesByProduct.get(productId) ?? [];
|
|
411
|
+
entry.push(price);
|
|
412
|
+
pricesByProduct.set(productId, entry);
|
|
256
413
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const parentId = category.parentId ?? null;
|
|
288
|
-
if (parentId) parentIds.add(parentId);
|
|
289
|
-
}
|
|
290
|
-
const parentCategories = parentIds.size ? await em.find(
|
|
291
|
-
CatalogProductCategory,
|
|
292
|
-
{ id: { $in: Array.from(parentIds) } },
|
|
293
|
-
{ fields: ["id", "name"] }
|
|
294
|
-
) : [];
|
|
295
|
-
const parentNameById = /* @__PURE__ */ new Map();
|
|
296
|
-
for (const parent of parentCategories) {
|
|
297
|
-
parentNameById.set(parent.id, parent.name ?? null);
|
|
298
|
-
}
|
|
299
|
-
const categoriesByProduct = /* @__PURE__ */ new Map();
|
|
300
|
-
for (const assignment of categoryAssignments) {
|
|
301
|
-
const productId = typeof assignment.product === "string" ? assignment.product : assignment.product?.id ?? null;
|
|
302
|
-
if (!productId) continue;
|
|
303
|
-
const category = typeof assignment.category === "string" ? null : assignment.category ?? null;
|
|
304
|
-
if (!category) continue;
|
|
305
|
-
const parentId = category.parentId ?? null;
|
|
306
|
-
const parentName = parentId ? parentNameById.get(parentId) ?? null : null;
|
|
307
|
-
const bucket = categoriesByProduct.get(productId) ?? [];
|
|
308
|
-
bucket.push({
|
|
309
|
-
id: category.id,
|
|
310
|
-
name: category.name ?? null,
|
|
311
|
-
treePath: category.treePath ?? null,
|
|
312
|
-
parentId,
|
|
313
|
-
parentName
|
|
314
|
-
});
|
|
315
|
-
categoriesByProduct.set(productId, bucket);
|
|
316
|
-
}
|
|
317
|
-
const tagAssignments = await findWithDecryption(
|
|
318
|
-
em,
|
|
319
|
-
CatalogProductTagAssignment,
|
|
320
|
-
{ product: { $in: productIds } },
|
|
321
|
-
{ populate: ["tag"] },
|
|
322
|
-
{ tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.auth?.orgId ?? null }
|
|
323
|
-
);
|
|
324
|
-
const tagsByProduct = /* @__PURE__ */ new Map();
|
|
325
|
-
for (const assignment of tagAssignments) {
|
|
326
|
-
const productId = typeof assignment.product === "string" ? assignment.product : assignment.product?.id ?? null;
|
|
327
|
-
if (!productId) continue;
|
|
328
|
-
const tag = typeof assignment.tag === "string" ? null : assignment.tag ?? null;
|
|
329
|
-
if (!tag) continue;
|
|
330
|
-
const label = typeof tag.label === "string" && tag.label.trim().length ? tag.label : null;
|
|
331
|
-
if (!label) continue;
|
|
332
|
-
const bucket = tagsByProduct.get(productId) ?? [];
|
|
333
|
-
bucket.push(label);
|
|
334
|
-
tagsByProduct.set(productId, bucket);
|
|
335
|
-
}
|
|
336
|
-
const variants = await em.find(
|
|
337
|
-
CatalogProductVariant,
|
|
338
|
-
{ product: { $in: productIds }, deletedAt: null },
|
|
339
|
-
{ fields: ["id", "product"] }
|
|
340
|
-
);
|
|
341
|
-
const variantToProduct = /* @__PURE__ */ new Map();
|
|
342
|
-
for (const variant of variants) {
|
|
343
|
-
const productId = typeof variant.product === "string" ? variant.product : variant.product?.id ?? null;
|
|
344
|
-
if (!productId) continue;
|
|
345
|
-
variantToProduct.set(variant.id, productId);
|
|
346
|
-
}
|
|
347
|
-
const variantIds = Array.from(variantToProduct.keys());
|
|
348
|
-
const priceWhere = variantIds.length > 0 ? {
|
|
349
|
-
$or: [{ product: { $in: productIds } }, { variant: { $in: variantIds } }]
|
|
350
|
-
} : { product: { $in: productIds } };
|
|
351
|
-
const priceRows = await em.find(
|
|
352
|
-
CatalogProductPrice,
|
|
353
|
-
priceWhere,
|
|
354
|
-
{ populate: ["offer", "variant", "product", "priceKind"] }
|
|
355
|
-
);
|
|
356
|
-
const pricesByProduct = /* @__PURE__ */ new Map();
|
|
357
|
-
for (const price of priceRows) {
|
|
358
|
-
let productId = null;
|
|
359
|
-
if (price.product) {
|
|
360
|
-
productId = typeof price.product === "string" ? price.product : price.product?.id ?? null;
|
|
361
|
-
} else if (price.variant) {
|
|
362
|
-
const variantId = typeof price.variant === "string" ? price.variant : price.variant.id;
|
|
363
|
-
productId = variantToProduct.get(variantId) ?? null;
|
|
414
|
+
const requestQuantityUnitKey = toUnitLookupKey(
|
|
415
|
+
ctx.query.quantityUnit
|
|
416
|
+
);
|
|
417
|
+
const conversionsByProduct = /* @__PURE__ */ new Map();
|
|
418
|
+
const conversionOrganizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null;
|
|
419
|
+
const conversionTenantId = ctx.auth?.tenantId ?? null;
|
|
420
|
+
if (requestQuantityUnitKey && productIds.length && conversionOrganizationId && conversionTenantId) {
|
|
421
|
+
const conversionRows = await findWithDecryption(
|
|
422
|
+
em,
|
|
423
|
+
CatalogProductUnitConversion,
|
|
424
|
+
{
|
|
425
|
+
product: { $in: productIds },
|
|
426
|
+
organizationId: conversionOrganizationId,
|
|
427
|
+
tenantId: conversionTenantId,
|
|
428
|
+
deletedAt: null,
|
|
429
|
+
isActive: true
|
|
430
|
+
},
|
|
431
|
+
{ fields: ["id", "product", "unitCode", "toBaseFactor"] },
|
|
432
|
+
{ organizationId: conversionOrganizationId, tenantId: conversionTenantId }
|
|
433
|
+
);
|
|
434
|
+
for (const row of conversionRows) {
|
|
435
|
+
const productId = typeof row.product === "string" ? row.product : row.product?.id ?? null;
|
|
436
|
+
const unitKey = toUnitLookupKey(row.unitCode);
|
|
437
|
+
const factor = Number(row.toBaseFactor);
|
|
438
|
+
if (!productId || !unitKey || !Number.isFinite(factor) || factor <= 0)
|
|
439
|
+
continue;
|
|
440
|
+
const bucket = conversionsByProduct.get(productId) ?? /* @__PURE__ */ new Map();
|
|
441
|
+
bucket.set(unitKey, factor);
|
|
442
|
+
conversionsByProduct.set(productId, bucket);
|
|
443
|
+
}
|
|
364
444
|
}
|
|
365
|
-
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
const channelFilterIds = parseIdList(ctx.query.channelIds);
|
|
371
|
-
const channelContext = ctx.query.channelId ?? (channelFilterIds.length === 1 ? channelFilterIds[0] : null);
|
|
372
|
-
const pricingContext = buildPricingContext(ctx.query, channelContext);
|
|
373
|
-
const pricingService = ctx.container.resolve("catalogPricingService");
|
|
374
|
-
for (const item of items) {
|
|
375
|
-
const id = typeof item.id === "string" ? item.id : null;
|
|
376
|
-
if (!id) continue;
|
|
377
|
-
const offerEntries = offersByProduct.get(id) ?? [];
|
|
378
|
-
item.offers = offerEntries;
|
|
379
|
-
const channelIds2 = Array.from(
|
|
380
|
-
new Set(
|
|
381
|
-
offerEntries.map((offer) => typeof offer.channelId === "string" ? offer.channelId : null).filter((channelId) => !!channelId)
|
|
382
|
-
)
|
|
445
|
+
const channelFilterIds = parseIdList(ctx.query.channelIds);
|
|
446
|
+
const channelContext = ctx.query.channelId ?? (channelFilterIds.length === 1 ? channelFilterIds[0] : null);
|
|
447
|
+
const pricingContext = buildPricingContext(ctx.query, channelContext);
|
|
448
|
+
const pricingService = ctx.container.resolve(
|
|
449
|
+
"catalogPricingService"
|
|
383
450
|
);
|
|
384
|
-
item
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
451
|
+
for (const item of items) {
|
|
452
|
+
const id = typeof item.id === "string" ? item.id : null;
|
|
453
|
+
if (!id) continue;
|
|
454
|
+
const offerEntries = offersByProduct.get(id) ?? [];
|
|
455
|
+
item.offers = offerEntries;
|
|
456
|
+
const channelIds2 = Array.from(
|
|
457
|
+
new Set(
|
|
458
|
+
offerEntries.map(
|
|
459
|
+
(offer) => typeof offer.channelId === "string" ? offer.channelId : null
|
|
460
|
+
).filter((channelId) => !!channelId)
|
|
461
|
+
)
|
|
462
|
+
);
|
|
463
|
+
item.channelIds = channelIds2;
|
|
464
|
+
const categories = categoriesByProduct.get(id) ?? [];
|
|
465
|
+
item.categories = categories;
|
|
466
|
+
item.categoryIds = categories.map((category) => category.id);
|
|
467
|
+
item.tags = tagsByProduct.get(id) ?? [];
|
|
468
|
+
const priceCandidates = pricesByProduct.get(id) ?? [];
|
|
469
|
+
const normalizedQuantityForPricing = (() => {
|
|
470
|
+
if (!requestQuantityUnitKey) return pricingContext.quantity;
|
|
471
|
+
const baseUnit = toUnitLookupKey(item.default_unit);
|
|
472
|
+
if (!baseUnit || requestQuantityUnitKey === baseUnit)
|
|
473
|
+
return pricingContext.quantity;
|
|
474
|
+
const productConversions = conversionsByProduct.get(id);
|
|
475
|
+
const factor = productConversions?.get(requestQuantityUnitKey) ?? null;
|
|
476
|
+
if (!factor || !Number.isFinite(factor) || factor <= 0) {
|
|
477
|
+
if (process.env.NODE_ENV === "development") console.warn(`[catalog.products] Invalid conversion factor for product=${id} unit=${requestQuantityUnitKey} factor=${factor}`);
|
|
478
|
+
return pricingContext.quantity;
|
|
412
479
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
480
|
+
const normalized = pricingContext.quantity * factor;
|
|
481
|
+
return Number.isFinite(normalized) && normalized > 0 ? normalized : pricingContext.quantity;
|
|
482
|
+
})();
|
|
483
|
+
const channelScopedContext = pricingContext.channelId || channelIds2.length !== 1 ? pricingContext : { ...pricingContext, channelId: channelIds2[0] };
|
|
484
|
+
const best = await pricingService.resolvePrice(priceCandidates, {
|
|
485
|
+
...channelScopedContext,
|
|
486
|
+
quantity: normalizedQuantityForPricing
|
|
487
|
+
});
|
|
488
|
+
if (best) {
|
|
489
|
+
item.pricing = {
|
|
490
|
+
kind: resolvePriceKindCode(best),
|
|
491
|
+
price_kind_id: typeof best.priceKind === "string" ? best.priceKind : best.priceKind?.id ?? null,
|
|
492
|
+
price_kind_code: resolvePriceKindCode(best),
|
|
493
|
+
currency_code: best.currencyCode,
|
|
494
|
+
unit_price_net: best.unitPriceNet,
|
|
495
|
+
unit_price_gross: best.unitPriceGross,
|
|
496
|
+
min_quantity: best.minQuantity,
|
|
497
|
+
max_quantity: best.maxQuantity ?? null,
|
|
498
|
+
tax_rate: best.taxRate ?? null,
|
|
499
|
+
tax_amount: best.taxAmount ?? null,
|
|
500
|
+
scope: {
|
|
501
|
+
variant_id: resolvePriceVariantId(best),
|
|
502
|
+
offer_id: resolvePriceOfferId(best),
|
|
503
|
+
channel_id: resolvePriceChannelId(best),
|
|
504
|
+
user_id: best.userId ?? null,
|
|
505
|
+
user_group_id: best.userGroupId ?? null,
|
|
506
|
+
customer_id: best.customerId ?? null,
|
|
507
|
+
customer_group_id: best.customerGroupId ?? null
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
} else {
|
|
511
|
+
item.pricing = null;
|
|
512
|
+
}
|
|
416
513
|
}
|
|
514
|
+
} catch (error) {
|
|
515
|
+
console.error("[decorateProductsAfterList] Failed to load unit conversions", error);
|
|
417
516
|
}
|
|
418
517
|
}
|
|
419
518
|
const crud = makeCrudRoute({
|
|
@@ -444,6 +543,13 @@ const crud = makeCrudRoute({
|
|
|
444
543
|
F.status_entry_id,
|
|
445
544
|
F.primary_currency_code,
|
|
446
545
|
F.default_unit,
|
|
546
|
+
"default_sales_unit",
|
|
547
|
+
"default_sales_unit_quantity",
|
|
548
|
+
"uom_rounding_scale",
|
|
549
|
+
"uom_rounding_mode",
|
|
550
|
+
"unit_price_enabled",
|
|
551
|
+
"unit_price_reference_unit",
|
|
552
|
+
"unit_price_base_quantity",
|
|
447
553
|
F.default_media_id,
|
|
448
554
|
F.default_media_url,
|
|
449
555
|
F.weight_value,
|
|
@@ -474,7 +580,21 @@ const crud = makeCrudRoute({
|
|
|
474
580
|
delete normalized[key];
|
|
475
581
|
}
|
|
476
582
|
}
|
|
477
|
-
|
|
583
|
+
const defaultUnit = canonicalizeUnitCode(normalized.default_unit) ?? null;
|
|
584
|
+
const defaultSalesUnit = canonicalizeUnitCode(normalized.default_sales_unit) ?? null;
|
|
585
|
+
const unitPriceReferenceUnit = canonicalizeUnitCode(normalized.unit_price_reference_unit) ?? null;
|
|
586
|
+
return {
|
|
587
|
+
...normalized,
|
|
588
|
+
default_unit: defaultUnit,
|
|
589
|
+
default_sales_unit: defaultSalesUnit,
|
|
590
|
+
unit_price_reference_unit: unitPriceReferenceUnit,
|
|
591
|
+
...cfEntries,
|
|
592
|
+
unit_price: {
|
|
593
|
+
enabled: Boolean(normalized.unit_price_enabled),
|
|
594
|
+
reference_unit: unitPriceReferenceUnit,
|
|
595
|
+
base_quantity: normalized.unit_price_base_quantity ?? null
|
|
596
|
+
}
|
|
597
|
+
};
|
|
478
598
|
}
|
|
479
599
|
},
|
|
480
600
|
hooks: {
|
|
@@ -486,11 +606,18 @@ const crud = makeCrudRoute({
|
|
|
486
606
|
schema: rawBodySchema,
|
|
487
607
|
mapInput: async ({ raw, ctx }) => {
|
|
488
608
|
const { translate } = await resolveTranslations();
|
|
489
|
-
const parsed = parseScopedCommandInput(
|
|
609
|
+
const parsed = parseScopedCommandInput(
|
|
610
|
+
productCreateSchema,
|
|
611
|
+
raw ?? {},
|
|
612
|
+
ctx,
|
|
613
|
+
translate
|
|
614
|
+
);
|
|
490
615
|
const { base, custom } = splitCustomFieldPayload(parsed);
|
|
491
616
|
return Object.keys(custom).length ? { ...base, customFields: custom } : base;
|
|
492
617
|
},
|
|
493
|
-
response: ({ result }) => ({
|
|
618
|
+
response: ({ result }) => ({
|
|
619
|
+
id: result?.productId ?? result?.id ?? null
|
|
620
|
+
}),
|
|
494
621
|
status: 201
|
|
495
622
|
},
|
|
496
623
|
update: {
|
|
@@ -498,7 +625,12 @@ const crud = makeCrudRoute({
|
|
|
498
625
|
schema: rawBodySchema,
|
|
499
626
|
mapInput: async ({ raw, ctx }) => {
|
|
500
627
|
const { translate } = await resolveTranslations();
|
|
501
|
-
const parsed = parseScopedCommandInput(
|
|
628
|
+
const parsed = parseScopedCommandInput(
|
|
629
|
+
productUpdateSchema,
|
|
630
|
+
raw ?? {},
|
|
631
|
+
ctx,
|
|
632
|
+
translate
|
|
633
|
+
);
|
|
502
634
|
const { base, custom } = splitCustomFieldPayload(parsed);
|
|
503
635
|
return Object.keys(custom).length ? { ...base, customFields: custom } : base;
|
|
504
636
|
},
|
|
@@ -510,7 +642,13 @@ const crud = makeCrudRoute({
|
|
|
510
642
|
mapInput: async ({ parsed, ctx }) => {
|
|
511
643
|
const { translate } = await resolveTranslations();
|
|
512
644
|
const id = resolveCrudRecordId(parsed, ctx, translate);
|
|
513
|
-
if (!id)
|
|
645
|
+
if (!id)
|
|
646
|
+
throw new CrudHttpError(400, {
|
|
647
|
+
error: translate(
|
|
648
|
+
"catalog.errors.id_required",
|
|
649
|
+
"Product id is required."
|
|
650
|
+
)
|
|
651
|
+
});
|
|
514
652
|
return { id };
|
|
515
653
|
},
|
|
516
654
|
response: () => ({ ok: true })
|
|
@@ -532,6 +670,18 @@ const productListItemSchema = z.object({
|
|
|
532
670
|
status_entry_id: z.string().uuid().nullable().optional(),
|
|
533
671
|
primary_currency_code: z.string().nullable().optional(),
|
|
534
672
|
default_unit: z.string().nullable().optional(),
|
|
673
|
+
default_sales_unit: z.string().nullable().optional(),
|
|
674
|
+
default_sales_unit_quantity: z.number().nullable().optional(),
|
|
675
|
+
uom_rounding_scale: z.number().nullable().optional(),
|
|
676
|
+
uom_rounding_mode: z.enum(["half_up", "down", "up"]).nullable().optional(),
|
|
677
|
+
unit_price_enabled: z.boolean().nullable().optional(),
|
|
678
|
+
unit_price_reference_unit: z.enum(["kg", "l", "m2", "m3", "pc"]).nullable().optional(),
|
|
679
|
+
unit_price_base_quantity: z.number().nullable().optional(),
|
|
680
|
+
unit_price: z.object({
|
|
681
|
+
enabled: z.boolean(),
|
|
682
|
+
reference_unit: z.enum(["kg", "l", "m2", "m3", "pc"]).nullable(),
|
|
683
|
+
base_quantity: z.number().nullable()
|
|
684
|
+
}).optional(),
|
|
535
685
|
default_media_id: z.string().uuid().nullable().optional(),
|
|
536
686
|
default_media_url: z.string().nullable().optional(),
|
|
537
687
|
weight_value: z.number().nullable().optional(),
|