@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.
Files changed (163) hide show
  1. package/dist/generated/entities/catalog_product/index.js +16 -0
  2. package/dist/generated/entities/catalog_product/index.js.map +2 -2
  3. package/dist/generated/entities/catalog_product_unit_conversion/index.js +27 -0
  4. package/dist/generated/entities/catalog_product_unit_conversion/index.js.map +7 -0
  5. package/dist/generated/entities/sales_credit_memo_line/index.js +7 -1
  6. package/dist/generated/entities/sales_credit_memo_line/index.js.map +2 -2
  7. package/dist/generated/entities/sales_invoice_line/index.js +7 -1
  8. package/dist/generated/entities/sales_invoice_line/index.js.map +2 -2
  9. package/dist/generated/entities/sales_order_line/index.js +6 -0
  10. package/dist/generated/entities/sales_order_line/index.js.map +2 -2
  11. package/dist/generated/entities/sales_quote_line/index.js +6 -0
  12. package/dist/generated/entities/sales_quote_line/index.js.map +2 -2
  13. package/dist/generated/entities.ids.generated.js +1 -0
  14. package/dist/generated/entities.ids.generated.js.map +2 -2
  15. package/dist/generated/entity-fields-registry.js +2 -0
  16. package/dist/generated/entity-fields-registry.js.map +2 -2
  17. package/dist/modules/catalog/api/prices/route.js +123 -8
  18. package/dist/modules/catalog/api/prices/route.js.map +2 -2
  19. package/dist/modules/catalog/api/product-unit-conversions/route.js +194 -0
  20. package/dist/modules/catalog/api/product-unit-conversions/route.js.map +7 -0
  21. package/dist/modules/catalog/api/products/route.js +351 -201
  22. package/dist/modules/catalog/api/products/route.js.map +2 -2
  23. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +1267 -497
  24. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  25. package/dist/modules/catalog/backend/catalog/products/create/page.js +733 -210
  26. package/dist/modules/catalog/backend/catalog/products/create/page.js.map +2 -2
  27. package/dist/modules/catalog/commands/index.js +1 -0
  28. package/dist/modules/catalog/commands/index.js.map +2 -2
  29. package/dist/modules/catalog/commands/productUnitConversions.js +503 -0
  30. package/dist/modules/catalog/commands/productUnitConversions.js.map +7 -0
  31. package/dist/modules/catalog/commands/products.js +355 -73
  32. package/dist/modules/catalog/commands/products.js.map +2 -2
  33. package/dist/modules/catalog/commands/shared.js +18 -4
  34. package/dist/modules/catalog/commands/shared.js.map +2 -2
  35. package/dist/modules/catalog/components/products/ProductUomSection.js +591 -0
  36. package/dist/modules/catalog/components/products/ProductUomSection.js.map +7 -0
  37. package/dist/modules/catalog/components/products/productForm.js +66 -5
  38. package/dist/modules/catalog/components/products/productForm.js.map +2 -2
  39. package/dist/modules/catalog/components/products/productFormUtils.js +68 -0
  40. package/dist/modules/catalog/components/products/productFormUtils.js.map +7 -0
  41. package/dist/modules/catalog/data/entities.js +86 -0
  42. package/dist/modules/catalog/data/entities.js.map +2 -2
  43. package/dist/modules/catalog/data/validators.js +65 -3
  44. package/dist/modules/catalog/data/validators.js.map +2 -2
  45. package/dist/modules/catalog/events.js +3 -0
  46. package/dist/modules/catalog/events.js.map +2 -2
  47. package/dist/modules/catalog/lib/unitCodes.js +7 -0
  48. package/dist/modules/catalog/lib/unitCodes.js.map +7 -0
  49. package/dist/modules/catalog/lib/unitResolution.js +53 -0
  50. package/dist/modules/catalog/lib/unitResolution.js.map +7 -0
  51. package/dist/modules/catalog/migrations/Migration20260218225422.js +19 -0
  52. package/dist/modules/catalog/migrations/Migration20260218225422.js.map +7 -0
  53. package/dist/modules/catalog/migrations/Migration20260219084500.js +27 -0
  54. package/dist/modules/catalog/migrations/Migration20260219084500.js.map +7 -0
  55. package/dist/modules/catalog/search.js +69 -1
  56. package/dist/modules/catalog/search.js.map +2 -2
  57. package/dist/modules/catalog/seed/examples.js +91 -42
  58. package/dist/modules/catalog/seed/examples.js.map +2 -2
  59. package/dist/modules/dashboards/seed/analytics.js +3 -0
  60. package/dist/modules/dashboards/seed/analytics.js.map +2 -2
  61. package/dist/modules/sales/api/order-lines/route.js +98 -15
  62. package/dist/modules/sales/api/order-lines/route.js.map +2 -2
  63. package/dist/modules/sales/api/quote-lines/route.js +101 -14
  64. package/dist/modules/sales/api/quote-lines/route.js.map +2 -2
  65. package/dist/modules/sales/api/quotes/public/[token]/route.js +87 -12
  66. package/dist/modules/sales/api/quotes/public/[token]/route.js.map +2 -2
  67. package/dist/modules/sales/commands/documents.js +1424 -260
  68. package/dist/modules/sales/commands/documents.js.map +3 -3
  69. package/dist/modules/sales/commands/shared.js +6 -2
  70. package/dist/modules/sales/commands/shared.js.map +2 -2
  71. package/dist/modules/sales/components/documents/ItemsSection.js +216 -86
  72. package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
  73. package/dist/modules/sales/components/documents/LineItemDialog.js +913 -241
  74. package/dist/modules/sales/components/documents/LineItemDialog.js.map +3 -3
  75. package/dist/modules/sales/components/documents/ShipmentsSection.js +15 -3
  76. package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
  77. package/dist/modules/sales/data/entities.js +59 -3
  78. package/dist/modules/sales/data/entities.js.map +2 -2
  79. package/dist/modules/sales/data/validators.js +35 -0
  80. package/dist/modules/sales/data/validators.js.map +2 -2
  81. package/dist/modules/sales/frontend/quote/[token]/page.js +15 -1
  82. package/dist/modules/sales/frontend/quote/[token]/page.js.map +2 -2
  83. package/dist/modules/sales/migrations/Migration20260218225423.js +31 -0
  84. package/dist/modules/sales/migrations/Migration20260218225423.js.map +7 -0
  85. package/dist/modules/sales/migrations/Migration20260219084501.js +71 -0
  86. package/dist/modules/sales/migrations/Migration20260219084501.js.map +7 -0
  87. package/dist/modules/sales/search.js +28 -0
  88. package/dist/modules/sales/search.js.map +2 -2
  89. package/dist/modules/sales/seed/examples.js +14 -1
  90. package/dist/modules/sales/seed/examples.js.map +2 -2
  91. package/dist/modules/sales/widgets/injection/document-history/widget.client.js +1 -1
  92. package/dist/modules/sales/widgets/injection/document-history/widget.client.js.map +2 -2
  93. package/generated/entities/catalog_product/index.ts +8 -0
  94. package/generated/entities/catalog_product_unit_conversion/index.ts +12 -0
  95. package/generated/entities/sales_credit_memo_line/index.ts +3 -0
  96. package/generated/entities/sales_invoice_line/index.ts +3 -0
  97. package/generated/entities/sales_order_line/index.ts +3 -0
  98. package/generated/entities/sales_quote_line/index.ts +3 -0
  99. package/generated/entities.ids.generated.ts +1 -0
  100. package/generated/entity-fields-registry.ts +2 -0
  101. package/package.json +2 -2
  102. package/src/modules/auth/i18n/de.json +1 -1
  103. package/src/modules/auth/i18n/en.json +1 -1
  104. package/src/modules/auth/i18n/es.json +1 -1
  105. package/src/modules/auth/i18n/pl.json +1 -1
  106. package/src/modules/catalog/api/prices/route.ts +213 -81
  107. package/src/modules/catalog/api/product-unit-conversions/route.ts +195 -0
  108. package/src/modules/catalog/api/products/route.ts +638 -402
  109. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +2085 -1072
  110. package/src/modules/catalog/backend/catalog/products/create/page.tsx +1288 -593
  111. package/src/modules/catalog/commands/index.ts +1 -0
  112. package/src/modules/catalog/commands/productUnitConversions.ts +626 -0
  113. package/src/modules/catalog/commands/products.ts +1151 -693
  114. package/src/modules/catalog/commands/shared.ts +19 -5
  115. package/src/modules/catalog/components/products/ProductUomSection.tsx +745 -0
  116. package/src/modules/catalog/components/products/productForm.ts +369 -256
  117. package/src/modules/catalog/components/products/productFormUtils.ts +82 -0
  118. package/src/modules/catalog/data/entities.ts +82 -1
  119. package/src/modules/catalog/data/validators.ts +118 -34
  120. package/src/modules/catalog/events.ts +3 -0
  121. package/src/modules/catalog/i18n/de.json +56 -0
  122. package/src/modules/catalog/i18n/en.json +56 -0
  123. package/src/modules/catalog/i18n/es.json +56 -0
  124. package/src/modules/catalog/i18n/pl.json +56 -0
  125. package/src/modules/catalog/lib/unitCodes.ts +1 -0
  126. package/src/modules/catalog/lib/unitResolution.ts +62 -0
  127. package/src/modules/catalog/migrations/.snapshot-open-mercato.json +245 -0
  128. package/src/modules/catalog/migrations/Migration20260218225422.ts +21 -0
  129. package/src/modules/catalog/migrations/Migration20260219084500.ts +26 -0
  130. package/src/modules/catalog/search.ts +73 -1
  131. package/src/modules/catalog/seed/examples.ts +552 -479
  132. package/src/modules/dashboards/i18n/de.json +1 -1
  133. package/src/modules/dashboards/i18n/en.json +1 -1
  134. package/src/modules/dashboards/i18n/es.json +1 -1
  135. package/src/modules/dashboards/i18n/pl.json +1 -1
  136. package/src/modules/dashboards/seed/analytics.ts +3 -0
  137. package/src/modules/sales/api/order-lines/route.ts +158 -68
  138. package/src/modules/sales/api/quote-lines/route.ts +161 -67
  139. package/src/modules/sales/api/quotes/public/[token]/route.ts +122 -36
  140. package/src/modules/sales/commands/documents.ts +4250 -2424
  141. package/src/modules/sales/commands/shared.ts +7 -2
  142. package/src/modules/sales/components/documents/ItemsSection.tsx +580 -310
  143. package/src/modules/sales/components/documents/LineItemDialog.tsx +1988 -833
  144. package/src/modules/sales/components/documents/ShipmentsSection.tsx +17 -3
  145. package/src/modules/sales/components/documents/lineItemTypes.ts +6 -0
  146. package/src/modules/sales/data/entities.ts +53 -0
  147. package/src/modules/sales/data/validators.ts +36 -0
  148. package/src/modules/sales/frontend/quote/[token]/page.tsx +25 -1
  149. package/src/modules/sales/i18n/de.json +23 -3
  150. package/src/modules/sales/i18n/en.json +23 -3
  151. package/src/modules/sales/i18n/es.json +23 -3
  152. package/src/modules/sales/i18n/pl.json +23 -3
  153. package/src/modules/sales/lib/types.ts +30 -0
  154. package/src/modules/sales/migrations/.snapshot-open-mercato.json +172 -0
  155. package/src/modules/sales/migrations/Migration20260218225423.ts +37 -0
  156. package/src/modules/sales/migrations/Migration20260219084501.ts +73 -0
  157. package/src/modules/sales/search.ts +28 -0
  158. package/src/modules/sales/seed/examples.ts +20 -1
  159. package/src/modules/sales/widgets/injection/document-history/widget.client.tsx +1 -1
  160. package/src/modules/workflows/i18n/de.json +4 -4
  161. package/src/modules/workflows/i18n/en.json +4 -4
  162. package/src/modules/workflows/i18n/es.json +4 -4
  163. 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 { loadCustomFieldSnapshot, buildCustomFieldResetMap } from "@open-mercato/shared/lib/commands/customFieldSnapshots";
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 { findWithDecryption, findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
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.findOne(SalesTaxRate, {
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
- throw new CrudHttpError(400, { error: "Tax class not found" });
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) return null;
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 label = typeof value.label === "string" && value.label.trim().length ? value.label.trim() : null;
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((choice) => !!choice) : [];
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 { metadata: null, dimensions: null, weightValue: null, weightUnit: null };
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(schema.name, preferredName ?? product.title);
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
- CatalogProductCategory,
435
- {
436
- id: { $in: normalized },
437
- organizationId: product.organizationId,
438
- tenantId: product.tenantId
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
- CatalogProductTag,
500
- {
501
- organizationId: product.organizationId,
502
- tenantId: product.tenantId,
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, { variant: { $in: variantIds } });
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
- em.find(CatalogProductCategoryAssignment, { product: record.id }, { populate: ["category"] })
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(record.metadata ? cloneJson(record.metadata) : null);
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(productCreateSchema, rawInput);
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: parsed.defaultUnit ?? null,
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("catalog.audit.products.create", "Create catalog product"),
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.findOne(CatalogProduct, { id: after.id });
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(void 0, after.custom ?? void 0);
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(productUpdateSchema, rawInput);
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 record = await em.findOne(CatalogProduct, { id: parsed.id, deletedAt: null });
884
- if (!record) throw new CrudHttpError(404, { error: "Catalog product not found" });
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) record.subtitle = parsed.subtitle ?? null;
897
- if (parsed.description !== void 0) record.description = parsed.description ?? null;
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) record.productType = parsed.productType;
905
- if (parsed.statusEntryId !== void 0) record.statusEntryId = parsed.statusEntryId ?? null;
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
- if (parsed.defaultUnit !== void 0) record.defaultUnit = parsed.defaultUnit ?? null;
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
- em,
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) record.isConfigurable = parsed.isConfigurable;
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("catalog.audit.products.update", "Update catalog product"),
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.findOne(CatalogProduct, { id: before.id });
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
- await restoreOffersFromSnapshot(em, record, before.offers);
1054
- await syncCategoryAssignments(em, record, before.categoryIds);
1055
- await syncProductTags(em, record, before.tags);
1056
- await em.flush();
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(before.custom ?? void 0, payload?.after?.custom ?? void 0);
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) throw new CrudHttpError(404, { error: "Catalog product not found" });
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({ em, product: record, dataEngine, ctx });
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(em, record);
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("catalog.audit.products.delete", "Delete catalog product"),
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.findOne(CatalogProduct, { id: before.id });
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 = typeof error.constraint === "string" ? error.constraint : null;
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 = typeof error.message === "string" ? error.message : "";
1219
- const normalized = message ? message.toLowerCase() : "";
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 (normalized.includes("catalog_products_sku_scope_unique") || normalized.includes(" sku")) {
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("catalog.products.errors.handleExists", "Handle already in use.");
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: [{ path: ["handle"], message, code: "duplicate", origin: "validation" }]
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("catalog.products.errors.skuExists", "SKU already in use.");
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: [{ path: ["sku"], message, code: "duplicate", origin: "validation" }]
1529
+ details: [
1530
+ { path: ["sku"], message, code: "duplicate", origin: "validation" }
1531
+ ]
1250
1532
  });
1251
1533
  }
1252
1534
  //# sourceMappingURL=products.js.map