@open-mercato/core 0.4.5-develop-f4858e0ef3 → 0.4.5-develop-4849712ccb
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/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/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,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { registerCommand } from "@open-mercato/shared/lib/commands";
|
|
3
3
|
import {
|
|
4
|
+
buildChanges,
|
|
4
5
|
requireId,
|
|
5
6
|
parseWithCustomFields,
|
|
6
7
|
setCustomFieldsIfAny,
|
|
@@ -10,7 +11,10 @@ import {
|
|
|
10
11
|
import { UniqueConstraintViolationException } from "@mikro-orm/core";
|
|
11
12
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
12
13
|
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
13
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
loadCustomFieldSnapshot,
|
|
16
|
+
buildCustomFieldResetMap
|
|
17
|
+
} from "@open-mercato/shared/lib/commands/customFieldSnapshots";
|
|
14
18
|
import { E } from "../../../generated/entities.ids.generated.js";
|
|
15
19
|
import { slugifyTagLabel } from "@open-mercato/shared/lib/utils";
|
|
16
20
|
import { parseObjectLike } from "@open-mercato/shared/lib/json/parseObjectLike";
|
|
@@ -19,6 +23,7 @@ import {
|
|
|
19
23
|
CatalogProduct,
|
|
20
24
|
CatalogProductVariant,
|
|
21
25
|
CatalogProductPrice,
|
|
26
|
+
CatalogProductUnitConversion,
|
|
22
27
|
CatalogOptionSchemaTemplate,
|
|
23
28
|
CatalogProductCategory,
|
|
24
29
|
CatalogProductCategoryAssignment,
|
|
@@ -40,9 +45,77 @@ import {
|
|
|
40
45
|
resolveOptionSchemaCode,
|
|
41
46
|
emitCatalogQueryIndexEvent,
|
|
42
47
|
randomSuffix,
|
|
43
|
-
toNumericString
|
|
48
|
+
toNumericString,
|
|
49
|
+
getErrorConstraint,
|
|
50
|
+
getErrorMessage
|
|
44
51
|
} from "./shared.js";
|
|
45
|
-
import {
|
|
52
|
+
import {
|
|
53
|
+
findWithDecryption,
|
|
54
|
+
findOneWithDecryption
|
|
55
|
+
} from "@open-mercato/shared/lib/encryption/find";
|
|
56
|
+
import { canonicalizeUnitCode } from "../lib/unitCodes.js";
|
|
57
|
+
import {
|
|
58
|
+
resolveCanonicalUnitCode
|
|
59
|
+
} from "../lib/unitResolution.js";
|
|
60
|
+
async function resolveProductUnitDefaults(em, params) {
|
|
61
|
+
const defaultUnitInput = canonicalizeUnitCode(params.defaultUnit);
|
|
62
|
+
const defaultSalesUnitInput = canonicalizeUnitCode(params.defaultSalesUnit);
|
|
63
|
+
if (!defaultUnitInput && defaultSalesUnitInput) {
|
|
64
|
+
throw new CrudHttpError(400, { error: "uom.default_unit_missing" });
|
|
65
|
+
}
|
|
66
|
+
const defaultUnit = defaultUnitInput ? await resolveCanonicalUnitCode(em, {
|
|
67
|
+
organizationId: params.organizationId,
|
|
68
|
+
tenantId: params.tenantId,
|
|
69
|
+
unitCode: defaultUnitInput
|
|
70
|
+
}) : null;
|
|
71
|
+
const defaultSalesUnit = defaultSalesUnitInput ? await resolveCanonicalUnitCode(em, {
|
|
72
|
+
organizationId: params.organizationId,
|
|
73
|
+
tenantId: params.tenantId,
|
|
74
|
+
unitCode: defaultSalesUnitInput
|
|
75
|
+
}) : null;
|
|
76
|
+
return { defaultUnit, defaultSalesUnit };
|
|
77
|
+
}
|
|
78
|
+
async function ensureBaseUnitCanBeRemoved(em, params) {
|
|
79
|
+
if (params.defaultUnit) return;
|
|
80
|
+
if (params.defaultSalesUnit) {
|
|
81
|
+
throw new CrudHttpError(400, { error: "uom.default_unit_missing" });
|
|
82
|
+
}
|
|
83
|
+
const activeConversionCount = await em.count(CatalogProductUnitConversion, {
|
|
84
|
+
product: params.productId,
|
|
85
|
+
organizationId: params.organizationId,
|
|
86
|
+
tenantId: params.tenantId,
|
|
87
|
+
deletedAt: null,
|
|
88
|
+
isActive: true
|
|
89
|
+
});
|
|
90
|
+
if (activeConversionCount > 0) {
|
|
91
|
+
throw new CrudHttpError(400, { error: "uom.default_unit_missing" });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function resolveUnitPriceInput(parsed) {
|
|
95
|
+
const enabledFromNested = parsed.unitPrice?.enabled;
|
|
96
|
+
const enabledFromFlat = parsed.unitPriceEnabled;
|
|
97
|
+
const referenceFromNested = parsed.unitPrice?.referenceUnit;
|
|
98
|
+
const referenceFromFlat = parsed.unitPriceReferenceUnit;
|
|
99
|
+
const baseFromNested = parsed.unitPrice?.baseQuantity;
|
|
100
|
+
const baseFromFlat = parsed.unitPriceBaseQuantity;
|
|
101
|
+
const enabledProvided = enabledFromNested !== void 0 || enabledFromFlat !== void 0;
|
|
102
|
+
const referenceProvided = referenceFromNested !== void 0 || referenceFromFlat !== void 0;
|
|
103
|
+
const baseProvided = baseFromNested !== void 0 || baseFromFlat !== void 0;
|
|
104
|
+
const enabled = enabledFromNested ?? enabledFromFlat;
|
|
105
|
+
const referenceUnit = canonicalizeUnitCode(
|
|
106
|
+
referenceFromNested ?? referenceFromFlat ?? null
|
|
107
|
+
);
|
|
108
|
+
const baseQuantitySource = baseFromNested ?? baseFromFlat;
|
|
109
|
+
const baseQuantity = baseQuantitySource === void 0 ? void 0 : toNumericString(baseQuantitySource) ?? null;
|
|
110
|
+
return {
|
|
111
|
+
enabled,
|
|
112
|
+
referenceUnit,
|
|
113
|
+
baseQuantity,
|
|
114
|
+
enabledProvided,
|
|
115
|
+
referenceProvided,
|
|
116
|
+
baseProvided
|
|
117
|
+
};
|
|
118
|
+
}
|
|
46
119
|
const productCrudEvents = {
|
|
47
120
|
module: "catalog",
|
|
48
121
|
entity: "product",
|
|
@@ -108,14 +181,20 @@ async function resolveScopedTaxRate(em, taxRateId, taxRateInput, organizationId,
|
|
|
108
181
|
if (!taxRateId) {
|
|
109
182
|
return { taxRateId: null, taxRate: normalizedRate };
|
|
110
183
|
}
|
|
111
|
-
const record = await em
|
|
184
|
+
const record = await findOneWithDecryption(em, SalesTaxRate, {
|
|
112
185
|
id: taxRateId,
|
|
113
186
|
organizationId,
|
|
114
187
|
tenantId,
|
|
115
188
|
deletedAt: null
|
|
116
189
|
});
|
|
117
190
|
if (!record) {
|
|
118
|
-
|
|
191
|
+
const { translate } = await resolveTranslations();
|
|
192
|
+
throw new CrudHttpError(400, {
|
|
193
|
+
error: translate(
|
|
194
|
+
"catalog.products.errors.taxClassNotFound",
|
|
195
|
+
"Tax class not found"
|
|
196
|
+
)
|
|
197
|
+
});
|
|
119
198
|
}
|
|
120
199
|
return { taxRateId, taxRate: record.rate ?? normalizedRate };
|
|
121
200
|
}
|
|
@@ -123,7 +202,8 @@ function slugifyCode(input) {
|
|
|
123
202
|
return input.toLowerCase().trim().replace(/[^a-z0-9\-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
124
203
|
}
|
|
125
204
|
function normalizeCatalogOptionSchema(input) {
|
|
126
|
-
if (!input || !Array.isArray(input.options) || !input.options.length)
|
|
205
|
+
if (!input || !Array.isArray(input.options) || !input.options.length)
|
|
206
|
+
return null;
|
|
127
207
|
const options = input.options.map((option) => {
|
|
128
208
|
if (!option) return null;
|
|
129
209
|
const label = typeof option.label === "string" && option.label.trim().length ? option.label.trim() : null;
|
|
@@ -170,10 +250,13 @@ function convertLegacyOptionSchema(raw) {
|
|
|
170
250
|
if (!title) return null;
|
|
171
251
|
const values = Array.isArray(source["values"]) ? source["values"].map((value) => {
|
|
172
252
|
if (!value || typeof value !== "object") return null;
|
|
173
|
-
const
|
|
253
|
+
const choice = value;
|
|
254
|
+
const label = typeof choice.label === "string" && choice.label.trim().length ? choice.label.trim() : null;
|
|
174
255
|
if (!label) return null;
|
|
175
256
|
return { code: slugifyCode(label), label };
|
|
176
|
-
}).filter(
|
|
257
|
+
}).filter(
|
|
258
|
+
(choice) => !!choice
|
|
259
|
+
) : [];
|
|
177
260
|
return {
|
|
178
261
|
code: slugifyCode(title),
|
|
179
262
|
label: title,
|
|
@@ -239,7 +322,12 @@ function normalizeWeightInput(raw) {
|
|
|
239
322
|
}
|
|
240
323
|
function extractMeasurementsFromMetadata(metadata) {
|
|
241
324
|
if (!metadata || typeof metadata !== "object") {
|
|
242
|
-
return {
|
|
325
|
+
return {
|
|
326
|
+
metadata: null,
|
|
327
|
+
dimensions: null,
|
|
328
|
+
weightValue: null,
|
|
329
|
+
weightUnit: null
|
|
330
|
+
};
|
|
243
331
|
}
|
|
244
332
|
const clone = { ...metadata };
|
|
245
333
|
const dimensions = normalizeDimensionsInput(clone.dimensions);
|
|
@@ -260,7 +348,10 @@ function ensureSchemaName(name, fallback) {
|
|
|
260
348
|
return "Product option schema";
|
|
261
349
|
}
|
|
262
350
|
async function assignOptionSchemaTemplate(em, product, schema, preferredName) {
|
|
263
|
-
const resolvedName = ensureSchemaName(
|
|
351
|
+
const resolvedName = ensureSchemaName(
|
|
352
|
+
schema.name,
|
|
353
|
+
preferredName ?? product.title
|
|
354
|
+
);
|
|
264
355
|
const templateCode = resolveOptionSchemaCode({
|
|
265
356
|
name: schema.name ?? resolvedName,
|
|
266
357
|
fallback: `${resolvedName}-${product.id}`,
|
|
@@ -430,15 +521,14 @@ async function syncCategoryAssignments(em, product, categoryIds) {
|
|
|
430
521
|
}
|
|
431
522
|
return;
|
|
432
523
|
}
|
|
433
|
-
const categories = await em.find(
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
524
|
+
const categories = await em.find(CatalogProductCategory, {
|
|
525
|
+
id: { $in: normalized },
|
|
526
|
+
organizationId: product.organizationId,
|
|
527
|
+
tenantId: product.tenantId
|
|
528
|
+
});
|
|
529
|
+
const categoryMap = new Map(
|
|
530
|
+
categories.map((category) => [category.id, category])
|
|
440
531
|
);
|
|
441
|
-
const categoryMap = new Map(categories.map((category) => [category.id, category]));
|
|
442
532
|
const claimed = /* @__PURE__ */ new Set();
|
|
443
533
|
normalized.forEach((categoryId, index) => {
|
|
444
534
|
const category = categoryMap.get(categoryId);
|
|
@@ -495,14 +585,11 @@ async function syncProductTags(em, product, tags) {
|
|
|
495
585
|
}
|
|
496
586
|
return;
|
|
497
587
|
}
|
|
498
|
-
const existingTags = await em.find(
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
slug: { $in: slugs }
|
|
504
|
-
}
|
|
505
|
-
);
|
|
588
|
+
const existingTags = await em.find(CatalogProductTag, {
|
|
589
|
+
organizationId: product.organizationId,
|
|
590
|
+
tenantId: product.tenantId,
|
|
591
|
+
slug: { $in: slugs }
|
|
592
|
+
});
|
|
506
593
|
const tagsBySlug = new Map(existingTags.map((tag) => [tag.slug, tag]));
|
|
507
594
|
for (const slug of slugs) {
|
|
508
595
|
if (tagsBySlug.has(slug)) continue;
|
|
@@ -567,7 +654,9 @@ async function deleteProductVariantsAndRelatedData(opts) {
|
|
|
567
654
|
);
|
|
568
655
|
const variantIds = variants.map((variant) => variant.id);
|
|
569
656
|
if (variantIds.length) {
|
|
570
|
-
await em.nativeDelete(CatalogProductPrice, {
|
|
657
|
+
await em.nativeDelete(CatalogProductPrice, {
|
|
658
|
+
variant: { $in: variantIds }
|
|
659
|
+
});
|
|
571
660
|
}
|
|
572
661
|
for (const variant of variants) {
|
|
573
662
|
em.remove(variant);
|
|
@@ -633,7 +722,13 @@ async function loadProductSnapshot(em, id) {
|
|
|
633
722
|
{ populate: ["tag"] },
|
|
634
723
|
{ tenantId: record.tenantId, organizationId: record.organizationId }
|
|
635
724
|
),
|
|
636
|
-
|
|
725
|
+
findWithDecryption(
|
|
726
|
+
em,
|
|
727
|
+
CatalogProductCategoryAssignment,
|
|
728
|
+
{ product: record.id },
|
|
729
|
+
{ populate: ["category"] },
|
|
730
|
+
{ tenantId: record.tenantId, organizationId: record.organizationId }
|
|
731
|
+
)
|
|
637
732
|
]);
|
|
638
733
|
const tags = tagAssignments.map((assignment) => {
|
|
639
734
|
const tag = typeof assignment.tag === "string" ? null : assignment.tag ?? null;
|
|
@@ -652,7 +747,9 @@ async function loadProductSnapshot(em, id) {
|
|
|
652
747
|
});
|
|
653
748
|
const optionSchemaTemplate = record.optionSchemaTemplate;
|
|
654
749
|
const optionTemplateId = typeof optionSchemaTemplate === "string" ? optionSchemaTemplate : optionSchemaTemplate?.id ?? null;
|
|
655
|
-
const measurements = extractMeasurementsFromMetadata(
|
|
750
|
+
const measurements = extractMeasurementsFromMetadata(
|
|
751
|
+
record.metadata ? cloneJson(record.metadata) : null
|
|
752
|
+
);
|
|
656
753
|
const dimensions = record.dimensions && Object.keys(record.dimensions).length ? cloneJson(record.dimensions) : measurements.dimensions ? cloneJson(measurements.dimensions) : null;
|
|
657
754
|
const weightValue = record.weightValue ?? (measurements.weightValue !== null ? toNumericString(measurements.weightValue) : null);
|
|
658
755
|
const weightUnit = record.weightUnit ?? measurements.weightUnit ?? null;
|
|
@@ -672,6 +769,13 @@ async function loadProductSnapshot(em, id) {
|
|
|
672
769
|
statusEntryId: record.statusEntryId ?? null,
|
|
673
770
|
primaryCurrencyCode: record.primaryCurrencyCode ?? null,
|
|
674
771
|
defaultUnit: record.defaultUnit ?? null,
|
|
772
|
+
defaultSalesUnit: record.defaultSalesUnit ?? null,
|
|
773
|
+
defaultSalesUnitQuantity: record.defaultSalesUnitQuantity ?? "1",
|
|
774
|
+
uomRoundingScale: record.uomRoundingScale ?? 4,
|
|
775
|
+
uomRoundingMode: record.uomRoundingMode ?? "half_up",
|
|
776
|
+
unitPriceEnabled: record.unitPriceEnabled ?? false,
|
|
777
|
+
unitPriceReferenceUnit: record.unitPriceReferenceUnit ?? null,
|
|
778
|
+
unitPriceBaseQuantity: record.unitPriceBaseQuantity ?? null,
|
|
675
779
|
defaultMediaId: record.defaultMediaId ?? null,
|
|
676
780
|
defaultMediaUrl: record.defaultMediaUrl ?? null,
|
|
677
781
|
weightValue,
|
|
@@ -704,6 +808,13 @@ function applyProductSnapshot(em, record, snapshot) {
|
|
|
704
808
|
record.statusEntryId = snapshot.statusEntryId ?? null;
|
|
705
809
|
record.primaryCurrencyCode = snapshot.primaryCurrencyCode ?? null;
|
|
706
810
|
record.defaultUnit = snapshot.defaultUnit ?? null;
|
|
811
|
+
record.defaultSalesUnit = snapshot.defaultSalesUnit ?? null;
|
|
812
|
+
record.defaultSalesUnitQuantity = snapshot.defaultSalesUnitQuantity ?? "1";
|
|
813
|
+
record.uomRoundingScale = snapshot.uomRoundingScale;
|
|
814
|
+
record.uomRoundingMode = snapshot.uomRoundingMode;
|
|
815
|
+
record.unitPriceEnabled = snapshot.unitPriceEnabled;
|
|
816
|
+
record.unitPriceReferenceUnit = snapshot.unitPriceReferenceUnit ?? null;
|
|
817
|
+
record.unitPriceBaseQuantity = snapshot.unitPriceBaseQuantity ?? null;
|
|
707
818
|
record.defaultMediaId = snapshot.defaultMediaId ?? null;
|
|
708
819
|
record.defaultMediaUrl = snapshot.defaultMediaUrl ?? null;
|
|
709
820
|
record.weightValue = snapshot.weightValue ?? null;
|
|
@@ -720,10 +831,14 @@ function applyProductSnapshot(em, record, snapshot) {
|
|
|
720
831
|
const createProductCommand = {
|
|
721
832
|
id: "catalog.products.create",
|
|
722
833
|
async execute(rawInput, ctx) {
|
|
723
|
-
const { parsed, custom } = parseWithCustomFields(
|
|
834
|
+
const { parsed, custom } = parseWithCustomFields(
|
|
835
|
+
productCreateSchema,
|
|
836
|
+
rawInput
|
|
837
|
+
);
|
|
724
838
|
ensureTenantScope(ctx, parsed.tenantId);
|
|
725
839
|
ensureOrganizationScope(ctx, parsed.organizationId);
|
|
726
840
|
const em = ctx.container.resolve("em").fork();
|
|
841
|
+
const { translate } = await resolveTranslations();
|
|
727
842
|
const now = /* @__PURE__ */ new Date();
|
|
728
843
|
const { taxRateId, taxRate } = await resolveScopedTaxRate(
|
|
729
844
|
em,
|
|
@@ -738,6 +853,14 @@ const createProductCommand = {
|
|
|
738
853
|
const weightValue = parsed.weightValue !== void 0 ? toNumericString(parsed.weightValue) : measurements.weightValue !== null ? toNumericString(measurements.weightValue) : null;
|
|
739
854
|
const weightUnit = parsed.weightUnit !== void 0 ? parsed.weightUnit ?? null : measurements.weightUnit ?? null;
|
|
740
855
|
const metadata = measurements.metadata ? cloneJson(measurements.metadata) : null;
|
|
856
|
+
const unitPriceInput = resolveUnitPriceInput(parsed);
|
|
857
|
+
const unitPriceEnabled = unitPriceInput.enabled ?? false;
|
|
858
|
+
const resolvedUnits = await resolveProductUnitDefaults(em, {
|
|
859
|
+
organizationId: parsed.organizationId,
|
|
860
|
+
tenantId: parsed.tenantId,
|
|
861
|
+
defaultUnit: parsed.defaultUnit ?? null,
|
|
862
|
+
defaultSalesUnit: parsed.defaultSalesUnit ?? parsed.defaultUnit ?? null
|
|
863
|
+
});
|
|
741
864
|
const productId = randomUUID();
|
|
742
865
|
const record = em.create(CatalogProduct, {
|
|
743
866
|
id: productId,
|
|
@@ -753,7 +876,14 @@ const createProductCommand = {
|
|
|
753
876
|
productType: parsed.productType ?? "simple",
|
|
754
877
|
statusEntryId: parsed.statusEntryId ?? null,
|
|
755
878
|
primaryCurrencyCode: parsed.primaryCurrencyCode ?? null,
|
|
756
|
-
defaultUnit:
|
|
879
|
+
defaultUnit: resolvedUnits.defaultUnit,
|
|
880
|
+
defaultSalesUnit: resolvedUnits.defaultSalesUnit ?? resolvedUnits.defaultUnit,
|
|
881
|
+
defaultSalesUnitQuantity: toNumericString(parsed.defaultSalesUnitQuantity ?? 1) ?? "1",
|
|
882
|
+
uomRoundingScale: parsed.uomRoundingScale ?? 4,
|
|
883
|
+
uomRoundingMode: parsed.uomRoundingMode ?? "half_up",
|
|
884
|
+
unitPriceEnabled,
|
|
885
|
+
unitPriceReferenceUnit: unitPriceEnabled ? unitPriceInput.referenceUnit ?? null : null,
|
|
886
|
+
unitPriceBaseQuantity: unitPriceEnabled ? unitPriceInput.baseQuantity ?? null : null,
|
|
757
887
|
defaultMediaId: parsed.defaultMediaId ?? null,
|
|
758
888
|
defaultMediaUrl: parsed.defaultMediaUrl ?? null,
|
|
759
889
|
weightValue,
|
|
@@ -771,9 +901,13 @@ const createProductCommand = {
|
|
|
771
901
|
optionSchemaTemplate = await requireOptionSchemaTemplate(
|
|
772
902
|
em,
|
|
773
903
|
parsed.optionSchemaId,
|
|
774
|
-
"Option schema not found"
|
|
904
|
+
translate("catalog.errors.optionSchemaNotFound", "Option schema not found")
|
|
905
|
+
);
|
|
906
|
+
ensureSameScope(
|
|
907
|
+
optionSchemaTemplate,
|
|
908
|
+
parsed.organizationId,
|
|
909
|
+
parsed.tenantId
|
|
775
910
|
);
|
|
776
|
-
ensureSameScope(optionSchemaTemplate, parsed.organizationId, parsed.tenantId);
|
|
777
911
|
record.optionSchemaTemplate = optionSchemaTemplate;
|
|
778
912
|
} else if (optionSchemaDefinition) {
|
|
779
913
|
optionSchemaTemplate = await assignOptionSchemaTemplate(
|
|
@@ -822,7 +956,10 @@ const createProductCommand = {
|
|
|
822
956
|
if (!after) return null;
|
|
823
957
|
const { translate } = await resolveTranslations();
|
|
824
958
|
return {
|
|
825
|
-
actionLabel: translate(
|
|
959
|
+
actionLabel: translate(
|
|
960
|
+
"catalog.audit.products.create",
|
|
961
|
+
"Create catalog product"
|
|
962
|
+
),
|
|
826
963
|
resourceKind: "catalog.product",
|
|
827
964
|
resourceId: result.productId,
|
|
828
965
|
tenantId: after.tenantId,
|
|
@@ -840,14 +977,17 @@ const createProductCommand = {
|
|
|
840
977
|
const after = payload?.after;
|
|
841
978
|
if (!after) return;
|
|
842
979
|
const em = ctx.container.resolve("em").fork();
|
|
843
|
-
const record = await em
|
|
980
|
+
const record = await findOneWithDecryption(em, CatalogProduct, { id: after.id });
|
|
844
981
|
if (!record) return;
|
|
845
982
|
ensureTenantScope(ctx, record.tenantId);
|
|
846
983
|
ensureOrganizationScope(ctx, record.organizationId);
|
|
847
984
|
em.remove(record);
|
|
848
985
|
await em.flush();
|
|
849
986
|
const dataEngine = ctx.container.resolve("dataEngine");
|
|
850
|
-
const resetValues = buildCustomFieldResetMap(
|
|
987
|
+
const resetValues = buildCustomFieldResetMap(
|
|
988
|
+
void 0,
|
|
989
|
+
after.custom ?? void 0
|
|
990
|
+
);
|
|
851
991
|
if (Object.keys(resetValues).length) {
|
|
852
992
|
await setCustomFieldsIfAny({
|
|
853
993
|
dataEngine,
|
|
@@ -878,35 +1018,105 @@ const updateProductCommand = {
|
|
|
878
1018
|
return snapshot ? { before: snapshot } : {};
|
|
879
1019
|
},
|
|
880
1020
|
async execute(rawInput, ctx) {
|
|
881
|
-
const { parsed, custom } = parseWithCustomFields(
|
|
1021
|
+
const { parsed, custom } = parseWithCustomFields(
|
|
1022
|
+
productUpdateSchema,
|
|
1023
|
+
rawInput
|
|
1024
|
+
);
|
|
1025
|
+
const rawPayload = rawInput && typeof rawInput === "object" ? rawInput : null;
|
|
1026
|
+
const hasDefaultUnit = Boolean(
|
|
1027
|
+
rawPayload && Object.prototype.hasOwnProperty.call(rawPayload, "defaultUnit")
|
|
1028
|
+
);
|
|
1029
|
+
const hasDefaultSalesUnit = Boolean(
|
|
1030
|
+
rawPayload && Object.prototype.hasOwnProperty.call(rawPayload, "defaultSalesUnit")
|
|
1031
|
+
);
|
|
1032
|
+
const requestedDefaultUnit = hasDefaultUnit ? rawPayload?.defaultUnit : parsed.defaultUnit;
|
|
1033
|
+
const requestedDefaultSalesUnit = hasDefaultSalesUnit ? rawPayload?.defaultSalesUnit : parsed.defaultSalesUnit;
|
|
882
1034
|
const em = ctx.container.resolve("em").fork();
|
|
883
|
-
const
|
|
884
|
-
|
|
1035
|
+
const { translate } = await resolveTranslations();
|
|
1036
|
+
const record = await findOneWithDecryption(em, CatalogProduct, {
|
|
1037
|
+
id: parsed.id,
|
|
1038
|
+
deletedAt: null
|
|
1039
|
+
});
|
|
1040
|
+
if (!record)
|
|
1041
|
+
throw new CrudHttpError(404, {
|
|
1042
|
+
error: translate("catalog.errors.productNotFound", "Catalog product not found")
|
|
1043
|
+
});
|
|
885
1044
|
const organizationId = parsed.organizationId ?? record.organizationId;
|
|
886
1045
|
const tenantId = parsed.tenantId ?? record.tenantId;
|
|
887
1046
|
ensureTenantScope(ctx, tenantId);
|
|
888
1047
|
ensureOrganizationScope(ctx, organizationId);
|
|
889
1048
|
ensureSameScope(record, organizationId, tenantId);
|
|
890
1049
|
const dataEngine = ctx.container.resolve("dataEngine");
|
|
1050
|
+
const lookupEm = em.fork();
|
|
1051
|
+
const taxRateProvided = parsed.taxRateId !== void 0 || parsed.taxRate !== void 0;
|
|
1052
|
+
const resolvedTaxRate = taxRateProvided ? await resolveScopedTaxRate(
|
|
1053
|
+
lookupEm,
|
|
1054
|
+
parsed.taxRateId ?? null,
|
|
1055
|
+
parsed.taxRate,
|
|
1056
|
+
organizationId,
|
|
1057
|
+
tenantId
|
|
1058
|
+
) : null;
|
|
891
1059
|
record.organizationId = organizationId;
|
|
892
1060
|
record.tenantId = tenantId;
|
|
893
|
-
const taxRateProvided = parsed.taxRateId !== void 0 || parsed.taxRate !== void 0;
|
|
894
|
-
const resolvedTaxRate = taxRateProvided ? await resolveScopedTaxRate(em, parsed.taxRateId ?? null, parsed.taxRate, organizationId, tenantId) : null;
|
|
895
1061
|
if (parsed.title !== void 0) record.title = parsed.title;
|
|
896
|
-
if (parsed.subtitle !== void 0)
|
|
897
|
-
|
|
1062
|
+
if (parsed.subtitle !== void 0)
|
|
1063
|
+
record.subtitle = parsed.subtitle ?? null;
|
|
1064
|
+
if (parsed.description !== void 0)
|
|
1065
|
+
record.description = parsed.description ?? null;
|
|
898
1066
|
if (parsed.sku !== void 0) record.sku = parsed.sku ?? null;
|
|
899
1067
|
if (parsed.handle !== void 0) record.handle = parsed.handle ?? null;
|
|
900
1068
|
if (taxRateProvided) {
|
|
901
1069
|
record.taxRateId = resolvedTaxRate?.taxRateId ?? null;
|
|
902
1070
|
record.taxRate = resolvedTaxRate?.taxRate ?? null;
|
|
903
1071
|
}
|
|
904
|
-
if (parsed.productType !== void 0)
|
|
905
|
-
|
|
1072
|
+
if (parsed.productType !== void 0)
|
|
1073
|
+
record.productType = parsed.productType;
|
|
1074
|
+
if (parsed.statusEntryId !== void 0)
|
|
1075
|
+
record.statusEntryId = parsed.statusEntryId ?? null;
|
|
906
1076
|
if (parsed.primaryCurrencyCode !== void 0) {
|
|
907
1077
|
record.primaryCurrencyCode = parsed.primaryCurrencyCode ?? null;
|
|
908
1078
|
}
|
|
909
|
-
|
|
1079
|
+
const uomDefaultsTouched = hasDefaultUnit || hasDefaultSalesUnit || parsed.defaultUnit !== void 0 || parsed.defaultSalesUnit !== void 0 || parsed.organizationId !== void 0 || parsed.tenantId !== void 0;
|
|
1080
|
+
if (uomDefaultsTouched) {
|
|
1081
|
+
const resolvedUnits = await resolveProductUnitDefaults(lookupEm, {
|
|
1082
|
+
organizationId,
|
|
1083
|
+
tenantId,
|
|
1084
|
+
defaultUnit: hasDefaultUnit ? requestedDefaultUnit : parsed.defaultUnit !== void 0 ? parsed.defaultUnit : record.defaultUnit,
|
|
1085
|
+
defaultSalesUnit: hasDefaultSalesUnit ? requestedDefaultSalesUnit : parsed.defaultSalesUnit !== void 0 ? parsed.defaultSalesUnit : record.defaultSalesUnit
|
|
1086
|
+
});
|
|
1087
|
+
await ensureBaseUnitCanBeRemoved(lookupEm, {
|
|
1088
|
+
productId: record.id,
|
|
1089
|
+
organizationId,
|
|
1090
|
+
tenantId,
|
|
1091
|
+
defaultUnit: resolvedUnits.defaultUnit,
|
|
1092
|
+
defaultSalesUnit: resolvedUnits.defaultSalesUnit
|
|
1093
|
+
});
|
|
1094
|
+
record.defaultUnit = resolvedUnits.defaultUnit;
|
|
1095
|
+
record.defaultSalesUnit = resolvedUnits.defaultSalesUnit;
|
|
1096
|
+
}
|
|
1097
|
+
if (parsed.defaultSalesUnitQuantity !== void 0) {
|
|
1098
|
+
record.defaultSalesUnitQuantity = toNumericString(parsed.defaultSalesUnitQuantity) ?? "1";
|
|
1099
|
+
}
|
|
1100
|
+
if (parsed.uomRoundingScale !== void 0) {
|
|
1101
|
+
record.uomRoundingScale = parsed.uomRoundingScale;
|
|
1102
|
+
}
|
|
1103
|
+
if (parsed.uomRoundingMode !== void 0) {
|
|
1104
|
+
record.uomRoundingMode = parsed.uomRoundingMode;
|
|
1105
|
+
}
|
|
1106
|
+
const unitPriceInput = resolveUnitPriceInput(parsed);
|
|
1107
|
+
if (unitPriceInput.enabledProvided) {
|
|
1108
|
+
record.unitPriceEnabled = unitPriceInput.enabled ?? false;
|
|
1109
|
+
if (!record.unitPriceEnabled) {
|
|
1110
|
+
record.unitPriceReferenceUnit = null;
|
|
1111
|
+
record.unitPriceBaseQuantity = null;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
if (unitPriceInput.referenceProvided && record.unitPriceEnabled) {
|
|
1115
|
+
record.unitPriceReferenceUnit = unitPriceInput.referenceUnit ?? null;
|
|
1116
|
+
}
|
|
1117
|
+
if (unitPriceInput.baseProvided && record.unitPriceEnabled) {
|
|
1118
|
+
record.unitPriceBaseQuantity = unitPriceInput.baseQuantity ?? null;
|
|
1119
|
+
}
|
|
910
1120
|
if (parsed.defaultMediaId !== void 0) {
|
|
911
1121
|
record.defaultMediaId = parsed.defaultMediaId ?? null;
|
|
912
1122
|
}
|
|
@@ -935,9 +1145,9 @@ const updateProductCommand = {
|
|
|
935
1145
|
record.optionSchemaTemplate = null;
|
|
936
1146
|
} else {
|
|
937
1147
|
const optionTemplate = await requireOptionSchemaTemplate(
|
|
938
|
-
|
|
1148
|
+
lookupEm,
|
|
939
1149
|
parsed.optionSchemaId,
|
|
940
|
-
"Option schema not found"
|
|
1150
|
+
translate("catalog.errors.optionSchemaNotFound", "Option schema not found")
|
|
941
1151
|
);
|
|
942
1152
|
ensureSameScope(optionTemplate, organizationId, tenantId);
|
|
943
1153
|
record.optionSchemaTemplate = optionTemplate;
|
|
@@ -954,7 +1164,8 @@ const updateProductCommand = {
|
|
|
954
1164
|
if (parsed.customFieldsetCode !== void 0) {
|
|
955
1165
|
record.customFieldsetCode = parsed.customFieldsetCode ?? null;
|
|
956
1166
|
}
|
|
957
|
-
if (parsed.isConfigurable !== void 0)
|
|
1167
|
+
if (parsed.isConfigurable !== void 0)
|
|
1168
|
+
record.isConfigurable = parsed.isConfigurable;
|
|
958
1169
|
if (parsed.isActive !== void 0) record.isActive = parsed.isActive;
|
|
959
1170
|
try {
|
|
960
1171
|
await em.flush();
|
|
@@ -996,11 +1207,28 @@ const updateProductCommand = {
|
|
|
996
1207
|
if (!before || !after) return null;
|
|
997
1208
|
const { translate } = await resolveTranslations();
|
|
998
1209
|
return {
|
|
999
|
-
actionLabel: translate(
|
|
1210
|
+
actionLabel: translate(
|
|
1211
|
+
"catalog.audit.products.update",
|
|
1212
|
+
"Update catalog product"
|
|
1213
|
+
),
|
|
1000
1214
|
resourceKind: "catalog.product",
|
|
1001
1215
|
resourceId: before.id,
|
|
1002
1216
|
tenantId: before.tenantId,
|
|
1003
1217
|
organizationId: before.organizationId,
|
|
1218
|
+
changes: buildChanges(before, after, [
|
|
1219
|
+
"title",
|
|
1220
|
+
"sku",
|
|
1221
|
+
"productType",
|
|
1222
|
+
"defaultUnit",
|
|
1223
|
+
"defaultSalesUnit",
|
|
1224
|
+
"defaultSalesUnitQuantity",
|
|
1225
|
+
"uomRoundingScale",
|
|
1226
|
+
"uomRoundingMode",
|
|
1227
|
+
"unitPriceEnabled",
|
|
1228
|
+
"unitPriceReferenceUnit",
|
|
1229
|
+
"unitPriceBaseQuantity",
|
|
1230
|
+
"isActive"
|
|
1231
|
+
]),
|
|
1004
1232
|
snapshotBefore: before,
|
|
1005
1233
|
snapshotAfter: after,
|
|
1006
1234
|
payload: {
|
|
@@ -1016,7 +1244,7 @@ const updateProductCommand = {
|
|
|
1016
1244
|
const before = payload?.before;
|
|
1017
1245
|
if (!before) return;
|
|
1018
1246
|
const em = ctx.container.resolve("em").fork();
|
|
1019
|
-
let record = await em
|
|
1247
|
+
let record = await findOneWithDecryption(em, CatalogProduct, { id: before.id });
|
|
1020
1248
|
if (!record) {
|
|
1021
1249
|
record = em.create(CatalogProduct, {
|
|
1022
1250
|
id: before.id,
|
|
@@ -1032,6 +1260,13 @@ const updateProductCommand = {
|
|
|
1032
1260
|
statusEntryId: before.statusEntryId ?? null,
|
|
1033
1261
|
primaryCurrencyCode: before.primaryCurrencyCode ?? null,
|
|
1034
1262
|
defaultUnit: before.defaultUnit ?? null,
|
|
1263
|
+
defaultSalesUnit: before.defaultSalesUnit ?? null,
|
|
1264
|
+
defaultSalesUnitQuantity: before.defaultSalesUnitQuantity ?? "1",
|
|
1265
|
+
uomRoundingScale: before.uomRoundingScale,
|
|
1266
|
+
uomRoundingMode: before.uomRoundingMode,
|
|
1267
|
+
unitPriceEnabled: before.unitPriceEnabled,
|
|
1268
|
+
unitPriceReferenceUnit: before.unitPriceReferenceUnit ?? null,
|
|
1269
|
+
unitPriceBaseQuantity: before.unitPriceBaseQuantity ?? null,
|
|
1035
1270
|
weightValue: before.weightValue ?? null,
|
|
1036
1271
|
weightUnit: before.weightUnit ?? null,
|
|
1037
1272
|
dimensions: before.dimensions ? cloneJson(before.dimensions) : null,
|
|
@@ -1050,12 +1285,19 @@ const updateProductCommand = {
|
|
|
1050
1285
|
ensureOrganizationScope(ctx, before.organizationId);
|
|
1051
1286
|
applyProductSnapshot(em, record, before);
|
|
1052
1287
|
await em.flush();
|
|
1053
|
-
|
|
1054
|
-
await
|
|
1055
|
-
|
|
1056
|
-
|
|
1288
|
+
const relationEm = em.fork();
|
|
1289
|
+
const relationRecord = await findOneWithDecryption(relationEm, CatalogProduct, { id: before.id });
|
|
1290
|
+
if (relationRecord) {
|
|
1291
|
+
await restoreOffersFromSnapshot(relationEm, relationRecord, before.offers);
|
|
1292
|
+
await syncCategoryAssignments(relationEm, relationRecord, before.categoryIds);
|
|
1293
|
+
await syncProductTags(relationEm, relationRecord, before.tags);
|
|
1294
|
+
await relationEm.flush();
|
|
1295
|
+
}
|
|
1057
1296
|
const dataEngine = ctx.container.resolve("dataEngine");
|
|
1058
|
-
const resetValues = buildCustomFieldResetMap(
|
|
1297
|
+
const resetValues = buildCustomFieldResetMap(
|
|
1298
|
+
before.custom ?? void 0,
|
|
1299
|
+
payload?.after?.custom ?? void 0
|
|
1300
|
+
);
|
|
1059
1301
|
if (Object.keys(resetValues).length) {
|
|
1060
1302
|
await setCustomFieldsIfAny({
|
|
1061
1303
|
dataEngine,
|
|
@@ -1094,15 +1336,31 @@ const deleteProductCommand = {
|
|
|
1094
1336
|
{ id },
|
|
1095
1337
|
{ populate: ["optionSchemaTemplate"] }
|
|
1096
1338
|
);
|
|
1097
|
-
if (!record)
|
|
1339
|
+
if (!record) {
|
|
1340
|
+
const { translate } = await resolveTranslations();
|
|
1341
|
+
throw new CrudHttpError(404, {
|
|
1342
|
+
error: translate(
|
|
1343
|
+
"catalog.products.errors.notFound",
|
|
1344
|
+
"Catalog product not found"
|
|
1345
|
+
)
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1098
1348
|
const baseEm = ctx.container.resolve("em");
|
|
1099
1349
|
const snapshot = await loadProductSnapshot(baseEm, id);
|
|
1100
1350
|
ensureTenantScope(ctx, record.tenantId);
|
|
1101
1351
|
ensureOrganizationScope(ctx, record.organizationId);
|
|
1102
1352
|
const dataEngine = ctx.container.resolve("dataEngine");
|
|
1103
|
-
await deleteProductVariantsAndRelatedData({
|
|
1353
|
+
await deleteProductVariantsAndRelatedData({
|
|
1354
|
+
em,
|
|
1355
|
+
product: record,
|
|
1356
|
+
dataEngine,
|
|
1357
|
+
ctx
|
|
1358
|
+
});
|
|
1104
1359
|
await em.nativeDelete(CatalogProductPrice, { product: record.id });
|
|
1105
|
-
const templateToRemove = await resolveOptionSchemaTemplateForRemoval(
|
|
1360
|
+
const templateToRemove = await resolveOptionSchemaTemplateForRemoval(
|
|
1361
|
+
em,
|
|
1362
|
+
record
|
|
1363
|
+
);
|
|
1106
1364
|
if (templateToRemove) {
|
|
1107
1365
|
record.optionSchemaTemplate = null;
|
|
1108
1366
|
em.remove(templateToRemove);
|
|
@@ -1134,7 +1392,10 @@ const deleteProductCommand = {
|
|
|
1134
1392
|
if (!before) return null;
|
|
1135
1393
|
const { translate } = await resolveTranslations();
|
|
1136
1394
|
return {
|
|
1137
|
-
actionLabel: translate(
|
|
1395
|
+
actionLabel: translate(
|
|
1396
|
+
"catalog.audit.products.delete",
|
|
1397
|
+
"Delete catalog product"
|
|
1398
|
+
),
|
|
1138
1399
|
resourceKind: "catalog.product",
|
|
1139
1400
|
resourceId: before.id,
|
|
1140
1401
|
tenantId: before.tenantId,
|
|
@@ -1152,7 +1413,7 @@ const deleteProductCommand = {
|
|
|
1152
1413
|
const before = payload?.before;
|
|
1153
1414
|
if (!before) return;
|
|
1154
1415
|
const em = ctx.container.resolve("em").fork();
|
|
1155
|
-
let record = await em
|
|
1416
|
+
let record = await findOneWithDecryption(em, CatalogProduct, { id: before.id });
|
|
1156
1417
|
if (!record) {
|
|
1157
1418
|
record = em.create(CatalogProduct, {
|
|
1158
1419
|
id: before.id,
|
|
@@ -1168,6 +1429,13 @@ const deleteProductCommand = {
|
|
|
1168
1429
|
statusEntryId: before.statusEntryId ?? null,
|
|
1169
1430
|
primaryCurrencyCode: before.primaryCurrencyCode ?? null,
|
|
1170
1431
|
defaultUnit: before.defaultUnit ?? null,
|
|
1432
|
+
defaultSalesUnit: before.defaultSalesUnit ?? null,
|
|
1433
|
+
defaultSalesUnitQuantity: before.defaultSalesUnitQuantity ?? "1",
|
|
1434
|
+
uomRoundingScale: before.uomRoundingScale,
|
|
1435
|
+
uomRoundingMode: before.uomRoundingMode,
|
|
1436
|
+
unitPriceEnabled: before.unitPriceEnabled,
|
|
1437
|
+
unitPriceReferenceUnit: before.unitPriceReferenceUnit ?? null,
|
|
1438
|
+
unitPriceBaseQuantity: before.unitPriceBaseQuantity ?? null,
|
|
1171
1439
|
weightValue: before.weightValue ?? null,
|
|
1172
1440
|
weightUnit: before.weightUnit ?? null,
|
|
1173
1441
|
dimensions: before.dimensions ? cloneJson(before.dimensions) : null,
|
|
@@ -1185,10 +1453,15 @@ const deleteProductCommand = {
|
|
|
1185
1453
|
ensureTenantScope(ctx, before.tenantId);
|
|
1186
1454
|
ensureOrganizationScope(ctx, before.organizationId);
|
|
1187
1455
|
applyProductSnapshot(em, record, before);
|
|
1188
|
-
await restoreOffersFromSnapshot(em, record, before.offers);
|
|
1189
|
-
await syncCategoryAssignments(em, record, before.categoryIds);
|
|
1190
|
-
await syncProductTags(em, record, before.tags);
|
|
1191
1456
|
await em.flush();
|
|
1457
|
+
const relationEm = em.fork();
|
|
1458
|
+
const relationRecord = await findOneWithDecryption(relationEm, CatalogProduct, { id: before.id });
|
|
1459
|
+
if (relationRecord) {
|
|
1460
|
+
await restoreOffersFromSnapshot(relationEm, relationRecord, before.offers);
|
|
1461
|
+
await syncCategoryAssignments(relationEm, relationRecord, before.categoryIds);
|
|
1462
|
+
await syncProductTags(relationEm, relationRecord, before.tags);
|
|
1463
|
+
await relationEm.flush();
|
|
1464
|
+
}
|
|
1192
1465
|
const dataEngine = ctx.container.resolve("dataEngine");
|
|
1193
1466
|
if (before.custom && Object.keys(before.custom).length) {
|
|
1194
1467
|
await setCustomFieldsIfAny({
|
|
@@ -1212,15 +1485,14 @@ registerCommand(updateProductCommand);
|
|
|
1212
1485
|
registerCommand(deleteProductCommand);
|
|
1213
1486
|
function resolveProductUniqueConstraint(error) {
|
|
1214
1487
|
if (!(error instanceof UniqueConstraintViolationException)) return null;
|
|
1215
|
-
const constraint =
|
|
1488
|
+
const constraint = getErrorConstraint(error);
|
|
1216
1489
|
if (constraint === "catalog_products_handle_scope_unique") return "handle";
|
|
1217
1490
|
if (constraint === "catalog_products_sku_scope_unique") return "sku";
|
|
1218
|
-
const message =
|
|
1219
|
-
|
|
1220
|
-
if (normalized.includes("catalog_products_handle_scope_unique") || normalized.includes(" handle")) {
|
|
1491
|
+
const message = getErrorMessage(error).toLowerCase();
|
|
1492
|
+
if (message.includes("catalog_products_handle_scope_unique") || message.includes(" handle")) {
|
|
1221
1493
|
return "handle";
|
|
1222
1494
|
}
|
|
1223
|
-
if (
|
|
1495
|
+
if (message.includes("catalog_products_sku_scope_unique") || message.includes(" sku")) {
|
|
1224
1496
|
return "sku";
|
|
1225
1497
|
}
|
|
1226
1498
|
return null;
|
|
@@ -1233,20 +1505,30 @@ async function rethrowProductUniqueConstraint(error) {
|
|
|
1233
1505
|
}
|
|
1234
1506
|
async function throwDuplicateHandleError() {
|
|
1235
1507
|
const { translate } = await resolveTranslations();
|
|
1236
|
-
const message = translate(
|
|
1508
|
+
const message = translate(
|
|
1509
|
+
"catalog.products.errors.handleExists",
|
|
1510
|
+
"Handle already in use."
|
|
1511
|
+
);
|
|
1237
1512
|
throw new CrudHttpError(400, {
|
|
1238
1513
|
error: message,
|
|
1239
1514
|
fieldErrors: { handle: message },
|
|
1240
|
-
details: [
|
|
1515
|
+
details: [
|
|
1516
|
+
{ path: ["handle"], message, code: "duplicate", origin: "validation" }
|
|
1517
|
+
]
|
|
1241
1518
|
});
|
|
1242
1519
|
}
|
|
1243
1520
|
async function throwDuplicateSkuError() {
|
|
1244
1521
|
const { translate } = await resolveTranslations();
|
|
1245
|
-
const message = translate(
|
|
1522
|
+
const message = translate(
|
|
1523
|
+
"catalog.products.errors.skuExists",
|
|
1524
|
+
"SKU already in use."
|
|
1525
|
+
);
|
|
1246
1526
|
throw new CrudHttpError(400, {
|
|
1247
1527
|
error: message,
|
|
1248
1528
|
fieldErrors: { sku: message },
|
|
1249
|
-
details: [
|
|
1529
|
+
details: [
|
|
1530
|
+
{ path: ["sku"], message, code: "duplicate", origin: "validation" }
|
|
1531
|
+
]
|
|
1250
1532
|
});
|
|
1251
1533
|
}
|
|
1252
1534
|
//# sourceMappingURL=products.js.map
|