@medusajs/product 3.0.0-snapshot-20251208164410 → 3.0.0-snapshot-20251211173847
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/migrations/Migration20250910154539.d.ts.map +1 -1
- package/dist/migrations/Migration20250910154539.js +0 -1
- package/dist/migrations/Migration20250910154539.js.map +1 -1
- package/dist/models/index.d.ts +0 -2
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/index.js +1 -5
- package/dist/models/index.js.map +1 -1
- package/dist/models/product-category.d.ts +6 -155
- package/dist/models/product-category.d.ts.map +1 -1
- package/dist/models/product-collection.d.ts +6 -155
- package/dist/models/product-collection.d.ts.map +1 -1
- package/dist/models/product-image.d.ts +14 -418
- package/dist/models/product-image.d.ts.map +1 -1
- package/dist/models/product-option-value.d.ts +12 -369
- package/dist/models/product-option-value.d.ts.map +1 -1
- package/dist/models/product-option-value.js +3 -14
- package/dist/models/product-option-value.js.map +1 -1
- package/dist/models/product-option.d.ts +6 -233
- package/dist/models/product-option.d.ts.map +1 -1
- package/dist/models/product-option.js +12 -4
- package/dist/models/product-option.js.map +1 -1
- package/dist/models/product-tag.d.ts +6 -155
- package/dist/models/product-tag.d.ts.map +1 -1
- package/dist/models/product-type.d.ts +6 -155
- package/dist/models/product-type.d.ts.map +1 -1
- package/dist/models/product-variant-product-image.d.ts +26 -780
- package/dist/models/product-variant-product-image.d.ts.map +1 -1
- package/dist/models/product-variant.d.ts +12 -362
- package/dist/models/product-variant.d.ts.map +1 -1
- package/dist/models/product.d.ts +6 -155
- package/dist/models/product.d.ts.map +1 -1
- package/dist/models/product.js +2 -7
- package/dist/models/product.js.map +1 -1
- package/dist/repositories/product-category.d.ts +15 -621
- package/dist/repositories/product-category.d.ts.map +1 -1
- package/dist/repositories/product.d.ts +6 -171
- package/dist/repositories/product.d.ts.map +1 -1
- package/dist/repositories/product.js +5 -50
- package/dist/repositories/product.js.map +1 -1
- package/dist/services/product-module-service.d.ts +2 -35
- package/dist/services/product-module-service.d.ts.map +1 -1
- package/dist/services/product-module-service.js +76 -660
- package/dist/services/product-module-service.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/dist/migrations/Migration20251022153442.d.ts +0 -6
- package/dist/migrations/Migration20251022153442.d.ts.map +0 -1
- package/dist/migrations/Migration20251022153442.js +0 -104
- package/dist/migrations/Migration20251022153442.js.map +0 -1
- package/dist/migrations/Migration20251029150809.d.ts +0 -6
- package/dist/migrations/Migration20251029150809.d.ts.map +0 -1
- package/dist/migrations/Migration20251029150809.js +0 -14
- package/dist/migrations/Migration20251029150809.js.map +0 -1
- package/dist/migrations/Migration20251110180907.d.ts +0 -6
- package/dist/migrations/Migration20251110180907.d.ts.map +0 -1
- package/dist/migrations/Migration20251110180907.js +0 -21
- package/dist/migrations/Migration20251110180907.js.map +0 -1
- package/dist/migrations/Migration20251113183352.d.ts +0 -6
- package/dist/migrations/Migration20251113183352.d.ts.map +0 -1
- package/dist/migrations/Migration20251113183352.js +0 -32
- package/dist/migrations/Migration20251113183352.js.map +0 -1
- package/dist/models/product-product-option-value.d.ts +0 -2213
- package/dist/models/product-product-option-value.d.ts.map +0 -1
- package/dist/models/product-product-option-value.js +0 -19
- package/dist/models/product-product-option-value.js.map +0 -1
- package/dist/models/product-product-option.d.ts +0 -1272
- package/dist/models/product-product-option.d.ts.map +0 -1
- package/dist/models/product-product-option.js +0 -24
- package/dist/models/product-product-option.js.map +0 -1
|
@@ -16,8 +16,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
16
16
|
const types_1 = require("@medusajs/framework/types");
|
|
17
17
|
const _models_1 = require("../models");
|
|
18
18
|
const utils_1 = require("@medusajs/framework/utils");
|
|
19
|
-
const joiner_config_1 = require("./../joiner-config");
|
|
20
19
|
const events_1 = require("../utils/events");
|
|
20
|
+
const joiner_config_1 = require("./../joiner-config");
|
|
21
21
|
class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
22
22
|
Product: _models_1.Product,
|
|
23
23
|
ProductCategory: _models_1.ProductCategory,
|
|
@@ -29,7 +29,7 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
29
29
|
ProductVariant: _models_1.ProductVariant,
|
|
30
30
|
ProductImage: _models_1.ProductImage,
|
|
31
31
|
}) {
|
|
32
|
-
constructor({ baseRepository, productRepository, productService, productVariantService, productTagService, productCategoryService, productCollectionService, productImageService, productTypeService, productOptionService,
|
|
32
|
+
constructor({ baseRepository, productRepository, productService, productVariantService, productTagService, productCategoryService, productCollectionService, productImageService, productTypeService, productOptionService, productOptionValueService, productVariantProductImageService, [utils_1.Modules.EVENT_BUS]: eventBusModuleService, }, moduleDeclaration) {
|
|
33
33
|
// @ts-ignore
|
|
34
34
|
// eslint-disable-next-line prefer-rest-params
|
|
35
35
|
super(...arguments);
|
|
@@ -44,8 +44,6 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
44
44
|
this.productImageService_ = productImageService;
|
|
45
45
|
this.productTypeService_ = productTypeService;
|
|
46
46
|
this.productOptionService_ = productOptionService;
|
|
47
|
-
this.productProductOptionService_ = productProductOptionService;
|
|
48
|
-
this.productProductOptionValueService_ = productProductOptionValueService;
|
|
49
47
|
this.productOptionValueService_ = productOptionValueService;
|
|
50
48
|
this.productVariantProductImageService_ = productVariantProductImageService;
|
|
51
49
|
this.eventBusModuleService_ = eventBusModuleService;
|
|
@@ -57,16 +55,10 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
57
55
|
async retrieveProduct(productId, config, sharedContext) {
|
|
58
56
|
const relationsSet = new Set(config?.relations ?? []);
|
|
59
57
|
const shouldLoadVariantImages = relationsSet.has("variants.images");
|
|
60
|
-
const shouldFilterOptionValues = relationsSet.has("options.values");
|
|
61
58
|
if (shouldLoadVariantImages) {
|
|
62
59
|
relationsSet.add("variants");
|
|
63
60
|
relationsSet.add("images");
|
|
64
61
|
}
|
|
65
|
-
if (shouldFilterOptionValues) {
|
|
66
|
-
relationsSet.add("options");
|
|
67
|
-
relationsSet.add("product_options");
|
|
68
|
-
relationsSet.add("product_options.values");
|
|
69
|
-
}
|
|
70
62
|
const product = await this.productService_.retrieve(productId, this.getProductFindConfig_({
|
|
71
63
|
...config,
|
|
72
64
|
relations: Array.from(relationsSet),
|
|
@@ -74,9 +66,6 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
74
66
|
if (shouldLoadVariantImages && product.variants && product.images) {
|
|
75
67
|
await this.buildVariantImagesFromProduct(product.variants, product.images, sharedContext);
|
|
76
68
|
}
|
|
77
|
-
if (shouldFilterOptionValues) {
|
|
78
|
-
this.filterOptionValuesByProduct(product);
|
|
79
|
-
}
|
|
80
69
|
return this.baseRepository_.serialize(product);
|
|
81
70
|
}
|
|
82
71
|
// @ts-ignore
|
|
@@ -87,11 +76,6 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
87
76
|
relationsSet.add("variants");
|
|
88
77
|
relationsSet.add("images");
|
|
89
78
|
}
|
|
90
|
-
const shouldFilterOptionValues = relationsSet.has("options.values");
|
|
91
|
-
if (shouldFilterOptionValues) {
|
|
92
|
-
relationsSet.add("product_options");
|
|
93
|
-
relationsSet.add("product_options.values");
|
|
94
|
-
}
|
|
95
79
|
const products = await this.productService_.list(filters, this.getProductFindConfig_({
|
|
96
80
|
...config,
|
|
97
81
|
relations: Array.from(relationsSet),
|
|
@@ -103,15 +87,11 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
103
87
|
}
|
|
104
88
|
}
|
|
105
89
|
}
|
|
106
|
-
if (shouldFilterOptionValues) {
|
|
107
|
-
this.filterOptionValuesByProducts(products);
|
|
108
|
-
}
|
|
109
90
|
return this.baseRepository_.serialize(products);
|
|
110
91
|
}
|
|
111
92
|
// @ts-ignore
|
|
112
93
|
async listAndCountProducts(filters, config, sharedContext) {
|
|
113
94
|
const shouldLoadVariantImages = config?.relations?.includes("variants.images");
|
|
114
|
-
const shouldFilterOptionValues = config?.relations?.includes("options.values");
|
|
115
95
|
// Ensure we load necessary relations
|
|
116
96
|
const relations = [...(config?.relations || [])];
|
|
117
97
|
if (shouldLoadVariantImages) {
|
|
@@ -122,14 +102,6 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
122
102
|
relations.push("images");
|
|
123
103
|
}
|
|
124
104
|
}
|
|
125
|
-
if (shouldFilterOptionValues) {
|
|
126
|
-
if (!relations.includes("product_options")) {
|
|
127
|
-
relations.push("product_options");
|
|
128
|
-
}
|
|
129
|
-
if (!relations.includes("product_options.values")) {
|
|
130
|
-
relations.push("product_options.values");
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
105
|
const [products, count] = await this.productService_.listAndCount(filters, this.getProductFindConfig_({ ...config, relations }), sharedContext);
|
|
134
106
|
if (shouldLoadVariantImages) {
|
|
135
107
|
for (const product of products) {
|
|
@@ -138,9 +110,6 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
138
110
|
}
|
|
139
111
|
}
|
|
140
112
|
}
|
|
141
|
-
if (shouldFilterOptionValues) {
|
|
142
|
-
this.filterOptionValuesByProducts(products);
|
|
143
|
-
}
|
|
144
113
|
const serializedProducts = await this.baseRepository_.serialize(products);
|
|
145
114
|
return [serializedProducts, count];
|
|
146
115
|
}
|
|
@@ -173,11 +142,9 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
173
142
|
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "Unable to create variants without specifying a product_id");
|
|
174
143
|
}
|
|
175
144
|
const productOptions = await this.productOptionService_.list({
|
|
176
|
-
|
|
177
|
-
id: [...new Set(data.map((v) => v.product_id))],
|
|
178
|
-
},
|
|
145
|
+
product_id: [...new Set(data.map((v) => v.product_id))],
|
|
179
146
|
}, {
|
|
180
|
-
relations: ["values"
|
|
147
|
+
relations: ["values"],
|
|
181
148
|
}, sharedContext);
|
|
182
149
|
const variants = await this.productVariantService_.list({
|
|
183
150
|
product_id: [...new Set(data.map((v) => v.product_id))],
|
|
@@ -237,10 +204,8 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
237
204
|
product_id: v.product_id,
|
|
238
205
|
}));
|
|
239
206
|
const productOptions = await this.productOptionService_.list({
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
},
|
|
243
|
-
}, { relations: ["values", "products"] }, sharedContext);
|
|
207
|
+
product_id: Array.from(new Set(variantsWithProductId.map((v) => v.product_id))),
|
|
208
|
+
}, { relations: ["values"] }, sharedContext);
|
|
244
209
|
const productVariantsWithOptions = ProductModuleService.assignOptionsToVariants(variantsWithProductId, productOptions);
|
|
245
210
|
if (data.some((d) => !!d.options)) {
|
|
246
211
|
ProductModuleService.checkIfVariantWithOptionsAlreadyExists(productVariantsWithOptions, allVariants);
|
|
@@ -348,25 +313,18 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
348
313
|
return Array.isArray(data) ? createdOptions : createdOptions[0];
|
|
349
314
|
}
|
|
350
315
|
async createOptions_(data, sharedContext = {}) {
|
|
316
|
+
if (data.some((v) => !v.product_id)) {
|
|
317
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "Tried to create options without specifying a product_id");
|
|
318
|
+
}
|
|
351
319
|
const normalizedInput = data.map((opt) => {
|
|
352
|
-
Object.keys(opt.ranks ?? []).forEach((value) => {
|
|
353
|
-
if (!opt.values.includes(value)) {
|
|
354
|
-
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Value "${value}" is assigned a rank but is not defined in the list of values.`);
|
|
355
|
-
}
|
|
356
|
-
});
|
|
357
320
|
return {
|
|
358
321
|
...opt,
|
|
359
322
|
values: opt.values?.map((v) => {
|
|
360
|
-
|
|
361
|
-
const valueObj = (0, utils_1.isString)(v) ? { value: v } : v;
|
|
362
|
-
const rank = opt.ranks && (0, utils_1.isString)(v)
|
|
363
|
-
? opt.ranks[v]
|
|
364
|
-
: opt.ranks?.[valueObj.value];
|
|
365
|
-
return rank !== undefined ? { ...valueObj, rank } : valueObj;
|
|
323
|
+
return typeof v === "string" ? { value: v } : v;
|
|
366
324
|
}),
|
|
367
325
|
};
|
|
368
326
|
});
|
|
369
|
-
return this.productOptionService_.create(normalizedInput, sharedContext);
|
|
327
|
+
return await this.productOptionService_.create(normalizedInput, sharedContext);
|
|
370
328
|
}
|
|
371
329
|
async upsertProductOptions(data, sharedContext = {}) {
|
|
372
330
|
const input = Array.isArray(data) ? data : [data];
|
|
@@ -413,255 +371,35 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
413
371
|
}
|
|
414
372
|
// Data normalization
|
|
415
373
|
const normalizedInput = data.map((opt) => {
|
|
416
|
-
const
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
Object.keys(opt.ranks).forEach((value) => {
|
|
421
|
-
if (!validValues.includes(value)) {
|
|
422
|
-
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Value "${value}" is assigned a rank but is not defined in the list of values.`);
|
|
423
|
-
}
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
let normalizedValues;
|
|
427
|
-
if (opt.values) {
|
|
428
|
-
// If new values are provided → normalize and apply ranks
|
|
429
|
-
normalizedValues = opt.values.map((v) => {
|
|
430
|
-
const valueObj = (0, utils_1.isString)(v) ? { value: v } : v;
|
|
431
|
-
const rank = opt.ranks && (0, utils_1.isString)(v)
|
|
432
|
-
? opt.ranks[v]
|
|
433
|
-
: opt.ranks?.[valueObj.value];
|
|
434
|
-
const rankedValue = rank !== undefined ? { ...valueObj, rank } : valueObj;
|
|
435
|
-
if ("id" in rankedValue) {
|
|
436
|
-
return rankedValue;
|
|
437
|
-
}
|
|
438
|
-
const dbVal = dbValues.find((dbVal) => dbVal.value === rankedValue.value);
|
|
439
|
-
if (!dbVal) {
|
|
440
|
-
return rankedValue;
|
|
441
|
-
}
|
|
442
|
-
return {
|
|
443
|
-
id: dbVal.id,
|
|
444
|
-
...rankedValue,
|
|
445
|
-
};
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
else if (opt.ranks) {
|
|
449
|
-
// If only ranks were provided → update existing DB values with ranks
|
|
450
|
-
normalizedValues = dbValues.map((dbVal) => {
|
|
451
|
-
const rank = opt.ranks[dbVal.value];
|
|
452
|
-
return rank !== undefined
|
|
453
|
-
? { id: dbVal.id, value: dbVal.value, rank }
|
|
454
|
-
: { id: dbVal.id, value: dbVal.value };
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
const { ranks, ...cleanOpt } = opt;
|
|
374
|
+
const dbValues = dbOptions.find(({ id }) => id === opt.id)?.values || [];
|
|
375
|
+
const normalizedValues = opt.values?.map((v) => {
|
|
376
|
+
return typeof v === "string" ? { value: v } : v;
|
|
377
|
+
});
|
|
458
378
|
return {
|
|
459
|
-
...
|
|
460
|
-
...(normalizedValues
|
|
379
|
+
...opt,
|
|
380
|
+
...(normalizedValues
|
|
381
|
+
? {
|
|
382
|
+
// Oftentimes the options are only passed by value without an id, even if they exist in the DB
|
|
383
|
+
values: normalizedValues.map((normVal) => {
|
|
384
|
+
if ("id" in normVal) {
|
|
385
|
+
return normVal;
|
|
386
|
+
}
|
|
387
|
+
const dbVal = dbValues.find((dbVal) => dbVal.value === normVal.value);
|
|
388
|
+
if (!dbVal) {
|
|
389
|
+
return normVal;
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
id: dbVal.id,
|
|
393
|
+
value: normVal.value,
|
|
394
|
+
};
|
|
395
|
+
}),
|
|
396
|
+
}
|
|
397
|
+
: {}),
|
|
461
398
|
};
|
|
462
399
|
});
|
|
463
400
|
const { entities: productOptions } = await this.productOptionService_.upsertWithReplace(normalizedInput, { relations: ["values"] }, sharedContext);
|
|
464
401
|
return productOptions;
|
|
465
402
|
}
|
|
466
|
-
async addProductOptionToProduct(data, sharedContext = {}) {
|
|
467
|
-
const productOptionProducts = await this.addProductOptionToProduct_(data, sharedContext);
|
|
468
|
-
return productOptionProducts;
|
|
469
|
-
}
|
|
470
|
-
async addProductOptionToProduct_(data, sharedContext = {}) {
|
|
471
|
-
const pairs = Array.isArray(data) ? data : [data];
|
|
472
|
-
const existingProductOptions = await this.productProductOptionService_.list({
|
|
473
|
-
$or: pairs.map((pair) => ({
|
|
474
|
-
product_id: pair.product_id,
|
|
475
|
-
product_option_id: pair.product_option_id,
|
|
476
|
-
})),
|
|
477
|
-
}, { relations: ["values"] }, sharedContext);
|
|
478
|
-
// Validate value removals when product_option_value_ids are provided - this sets new values for the product <> options meaning some values may be removed thus we need removal here as well
|
|
479
|
-
const validationPairs = [];
|
|
480
|
-
for (const pair of pairs) {
|
|
481
|
-
if (pair.product_option_value_ids) {
|
|
482
|
-
const existingProductOption = existingProductOptions.find((epo) => epo.product_id === pair.product_id &&
|
|
483
|
-
epo.product_option_id === pair.product_option_id);
|
|
484
|
-
if (existingProductOption) {
|
|
485
|
-
const currentValues = Array.isArray(existingProductOption.values)
|
|
486
|
-
? existingProductOption.values
|
|
487
|
-
: existingProductOption.values?.toArray?.() ?? [];
|
|
488
|
-
const currentValueIds = new Set(currentValues.map((v) => v.id));
|
|
489
|
-
const newValueIds = new Set(pair.product_option_value_ids);
|
|
490
|
-
// Find values being removed
|
|
491
|
-
const removedValueIds = Array.from(currentValueIds).filter((id) => !newValueIds.has(id));
|
|
492
|
-
if (removedValueIds.length > 0) {
|
|
493
|
-
validationPairs.push({
|
|
494
|
-
productId: pair.product_id,
|
|
495
|
-
optionId: pair.product_option_id,
|
|
496
|
-
valueIdsToCheck: removedValueIds,
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
if (validationPairs.length > 0) {
|
|
503
|
-
await this.validateOptionRemoval_(validationPairs, sharedContext);
|
|
504
|
-
}
|
|
505
|
-
// Separate pairs: those that need PPO creation vs those that just need value updates
|
|
506
|
-
const pairsNeedingPPOCreation = [];
|
|
507
|
-
for (const pair of pairs) {
|
|
508
|
-
const hasExistingPPO = existingProductOptions.some((epo) => epo.product_id === pair.product_id &&
|
|
509
|
-
epo.product_option_id === pair.product_option_id);
|
|
510
|
-
// Only create PPO if it doesn't exist
|
|
511
|
-
if (!hasExistingPPO) {
|
|
512
|
-
pairsNeedingPPOCreation.push(pair);
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
let createdPPOs = [];
|
|
516
|
-
if (pairsNeedingPPOCreation.length > 0) {
|
|
517
|
-
const productProductOptions = await this.productProductOptionService_.create(pairsNeedingPPOCreation, sharedContext);
|
|
518
|
-
createdPPOs = (Array.isArray(productProductOptions)
|
|
519
|
-
? productProductOptions
|
|
520
|
-
: [productProductOptions]);
|
|
521
|
-
}
|
|
522
|
-
// Map all PPOs (existing and newly created) to their pairs
|
|
523
|
-
const ppoMap = new Map();
|
|
524
|
-
// First, add existing PPOs (these take precedence for value updates)
|
|
525
|
-
for (const existingPPO of existingProductOptions) {
|
|
526
|
-
const key = `${existingPPO.product_id}_${existingPPO.product_option_id}`;
|
|
527
|
-
ppoMap.set(key, existingPPO);
|
|
528
|
-
}
|
|
529
|
-
// Then add newly created PPOs (only if they don't already exist in the map)
|
|
530
|
-
for (const ppo of createdPPOs) {
|
|
531
|
-
const key = `${ppo.product_id}_${ppo.product_option_id}`;
|
|
532
|
-
if (!ppoMap.has(key)) {
|
|
533
|
-
ppoMap.set(key, ppo);
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
const uniqueOptionIds = [...new Set(pairs.map((p) => p.product_option_id))];
|
|
537
|
-
const options = await this.productOptionService_.list({ id: uniqueOptionIds }, { relations: ["values"] }, sharedContext);
|
|
538
|
-
const optionValuesMap = new Map(options.map((opt) => [opt.id, opt.values || []]));
|
|
539
|
-
const valuePairsToCreate = [];
|
|
540
|
-
for (const pair of pairs) {
|
|
541
|
-
const key = `${pair.product_id}_${pair.product_option_id}`;
|
|
542
|
-
const ppo = ppoMap.get(key);
|
|
543
|
-
if (!ppo)
|
|
544
|
-
continue;
|
|
545
|
-
const originalPair = pair;
|
|
546
|
-
if (originalPair) {
|
|
547
|
-
const allValues = optionValuesMap.get(originalPair.product_option_id) || [];
|
|
548
|
-
// If specific value IDs were provided, use only those; otherwise use all values
|
|
549
|
-
const valuesToLink = originalPair.product_option_value_ids
|
|
550
|
-
? allValues.filter((v) => originalPair.product_option_value_ids.includes(v.id))
|
|
551
|
-
: allValues;
|
|
552
|
-
// If updating with specific value_ids, remove old values that aren't in the new list
|
|
553
|
-
if (originalPair.product_option_value_ids) {
|
|
554
|
-
const existingProductOption = existingProductOptions.find((epo) => epo.product_id === originalPair.product_id &&
|
|
555
|
-
epo.product_option_id === originalPair.product_option_id);
|
|
556
|
-
if (existingProductOption) {
|
|
557
|
-
const currentValues = Array.isArray(existingProductOption.values)
|
|
558
|
-
? existingProductOption.values
|
|
559
|
-
: existingProductOption.values?.toArray?.() ?? [];
|
|
560
|
-
const currentValueIds = new Set(currentValues.map((v) => v.id));
|
|
561
|
-
const newValueIds = new Set(originalPair.product_option_value_ids);
|
|
562
|
-
// Find values to remove
|
|
563
|
-
const valuesToRemove = Array.from(currentValueIds).filter((id) => !newValueIds.has(id));
|
|
564
|
-
// Delete removed value links
|
|
565
|
-
if (valuesToRemove.length > 0) {
|
|
566
|
-
await this.productProductOptionValueService_.delete({
|
|
567
|
-
product_product_option_id: existingProductOption.id,
|
|
568
|
-
product_option_value_id: valuesToRemove,
|
|
569
|
-
}, sharedContext);
|
|
570
|
-
// Flush to ensure deletion is persisted
|
|
571
|
-
await sharedContext.transactionManager?.flush?.();
|
|
572
|
-
}
|
|
573
|
-
// refetch PPOs after deletion
|
|
574
|
-
const [reloadedPPO] = await this.productProductOptionService_.list({
|
|
575
|
-
id: existingProductOption.id,
|
|
576
|
-
}, { relations: ["values"] }, sharedContext);
|
|
577
|
-
const reloadedValues = reloadedPPO
|
|
578
|
-
? Array.isArray(reloadedPPO.values)
|
|
579
|
-
? reloadedPPO.values
|
|
580
|
-
: reloadedPPO.values?.toArray?.() ?? []
|
|
581
|
-
: [];
|
|
582
|
-
const reloadedValueIds = new Set(reloadedValues.map((v) => v.id));
|
|
583
|
-
// Only create links for values that don't already exist (check against reloaded values)
|
|
584
|
-
const existingValueIdsSet = reloadedValueIds;
|
|
585
|
-
for (const value of valuesToLink) {
|
|
586
|
-
if (!existingValueIdsSet.has(value.id)) {
|
|
587
|
-
valuePairsToCreate.push({
|
|
588
|
-
product_product_option_id: existingProductOption.id,
|
|
589
|
-
product_option_value_id: value.id,
|
|
590
|
-
});
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
else {
|
|
595
|
-
// No existing link, create all value links
|
|
596
|
-
for (const value of valuesToLink) {
|
|
597
|
-
valuePairsToCreate.push({
|
|
598
|
-
product_product_option_id: ppo.id,
|
|
599
|
-
product_option_value_id: value.id,
|
|
600
|
-
});
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
else {
|
|
605
|
-
// No specific value_ids, create all value links (only if PPO is newly created)
|
|
606
|
-
if (!existingProductOptions.find((epo) => epo.product_id === pair.product_id &&
|
|
607
|
-
epo.product_option_id === pair.product_option_id)) {
|
|
608
|
-
for (const value of valuesToLink) {
|
|
609
|
-
valuePairsToCreate.push({
|
|
610
|
-
product_product_option_id: ppo.id,
|
|
611
|
-
product_option_value_id: value.id,
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
if (valuePairsToCreate.length > 0) {
|
|
619
|
-
await this.productProductOptionValueService_.create(valuePairsToCreate, sharedContext);
|
|
620
|
-
}
|
|
621
|
-
// Get all PPOs (existing + created) for return value
|
|
622
|
-
const allPPOsForReturn = [];
|
|
623
|
-
for (const pair of pairs) {
|
|
624
|
-
const key = `${pair.product_id}_${pair.product_option_id}`;
|
|
625
|
-
const ppo = ppoMap.get(key);
|
|
626
|
-
if (ppo) {
|
|
627
|
-
allPPOsForReturn.push(ppo);
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
if (Array.isArray(data)) {
|
|
631
|
-
return allPPOsForReturn.map((ppo) => ({ id: ppo.id }));
|
|
632
|
-
}
|
|
633
|
-
return { id: allPPOsForReturn[0]?.id || createdPPOs[0]?.id };
|
|
634
|
-
}
|
|
635
|
-
async removeProductOptionFromProduct(data, sharedContext = {}) {
|
|
636
|
-
await this.removeProductOptionFromProduct_(data, sharedContext);
|
|
637
|
-
}
|
|
638
|
-
async removeProductOptionFromProduct_(data, sharedContext = {}) {
|
|
639
|
-
const pairs = Array.isArray(data) ? data : [data];
|
|
640
|
-
const productOptionsProducts = await this.productProductOptionService_.list({
|
|
641
|
-
$or: pairs,
|
|
642
|
-
}, { relations: [] }, sharedContext);
|
|
643
|
-
// Validate that no variants are using the options before removal
|
|
644
|
-
const validationPairs = productOptionsProducts
|
|
645
|
-
.map((productOptionProduct) => {
|
|
646
|
-
const productId = productOptionProduct.product_id;
|
|
647
|
-
const optionId = productOptionProduct.product_option_id;
|
|
648
|
-
if (productId && optionId) {
|
|
649
|
-
return {
|
|
650
|
-
productId,
|
|
651
|
-
optionId,
|
|
652
|
-
// Check all values of the option
|
|
653
|
-
};
|
|
654
|
-
}
|
|
655
|
-
return null;
|
|
656
|
-
})
|
|
657
|
-
.filter((p) => p !== null);
|
|
658
|
-
if (validationPairs.length > 0) {
|
|
659
|
-
await this.validateOptionRemoval_(validationPairs, sharedContext);
|
|
660
|
-
}
|
|
661
|
-
const productOptionsProductIds = productOptionsProducts.map(({ id }) => id);
|
|
662
|
-
await this.productProductOptionValueService_.delete(productOptionsProductIds.map((id) => ({ product_product_option_id: id })), sharedContext);
|
|
663
|
-
await this.productProductOptionService_.delete(productOptionsProductIds, sharedContext);
|
|
664
|
-
}
|
|
665
403
|
// @ts-expect-error
|
|
666
404
|
async createProductCollections(data, sharedContext = {}) {
|
|
667
405
|
const input = Array.isArray(data) ? data : [data];
|
|
@@ -886,41 +624,7 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
886
624
|
return (0, utils_1.isString)(idOrSelector) ? updatedProducts[0] : updatedProducts;
|
|
887
625
|
}
|
|
888
626
|
async createProducts_(data, sharedContext = {}) {
|
|
889
|
-
const
|
|
890
|
-
.flatMap((p) => p.options ?? [])
|
|
891
|
-
.filter((o) => "id" in o)
|
|
892
|
-
.map((o) => o.id);
|
|
893
|
-
let existingOptions = [];
|
|
894
|
-
if (existingOptionIds.length > 0) {
|
|
895
|
-
existingOptions = await this.productOptionService_.list({ id: existingOptionIds }, { relations: ["values"] }, sharedContext);
|
|
896
|
-
const fetchedIds = new Set(existingOptions.map((opt) => opt.id));
|
|
897
|
-
const missingIds = existingOptionIds.filter((id) => !fetchedIds.has(id));
|
|
898
|
-
if (missingIds.length) {
|
|
899
|
-
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Some product options were not found: [${missingIds.join(", ")}]`);
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
const existingOptionsMap = new Map(existingOptions.map((opt) => [opt.id, opt]));
|
|
903
|
-
const hydratedData = data.map((product) => {
|
|
904
|
-
if (!product.options?.length)
|
|
905
|
-
return product;
|
|
906
|
-
const hydratedOptions = product.options.map((option) => {
|
|
907
|
-
if ("id" in option) {
|
|
908
|
-
const dbOption = existingOptionsMap.get(option.id);
|
|
909
|
-
if (!dbOption) {
|
|
910
|
-
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Product option with id ${option.id} not found.`);
|
|
911
|
-
}
|
|
912
|
-
return {
|
|
913
|
-
id: dbOption.id,
|
|
914
|
-
title: dbOption.title,
|
|
915
|
-
values: dbOption.values?.map((v) => ({ value: v.value })),
|
|
916
|
-
value_ids: option.value_ids,
|
|
917
|
-
};
|
|
918
|
-
}
|
|
919
|
-
return option;
|
|
920
|
-
});
|
|
921
|
-
return { ...product, options: hydratedOptions };
|
|
922
|
-
});
|
|
923
|
-
const normalizedProducts = (await this.normalizeCreateProductInput(hydratedData, sharedContext));
|
|
627
|
+
const normalizedProducts = await this.normalizeCreateProductInput(data, sharedContext);
|
|
924
628
|
for (const product of normalizedProducts) {
|
|
925
629
|
this.validateProductCreatePayload(product);
|
|
926
630
|
}
|
|
@@ -934,7 +638,6 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
934
638
|
}, {}, sharedContext);
|
|
935
639
|
}
|
|
936
640
|
const existingTagsMap = new Map(existingTags.map((tag) => [tag.id, tag]));
|
|
937
|
-
const productOptionsToCreate = new Map();
|
|
938
641
|
const productsToCreate = normalizedProducts.map((product) => {
|
|
939
642
|
const productId = (0, utils_1.generateEntityId)(product.id, "prod");
|
|
940
643
|
product.id = productId;
|
|
@@ -942,12 +645,6 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
942
645
|
;
|
|
943
646
|
product.categories = product.categories.map((category) => category.id);
|
|
944
647
|
}
|
|
945
|
-
if (product.options?.length) {
|
|
946
|
-
const newOptions = product.options.filter((o) => !("id" in o));
|
|
947
|
-
if (newOptions.length) {
|
|
948
|
-
productOptionsToCreate.set(productId, newOptions);
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
648
|
if (product.variants?.length) {
|
|
952
649
|
const normalizedVariants = product.variants.map((variant) => {
|
|
953
650
|
const variantId = (0, utils_1.generateEntityId)(variant.id, "variant");
|
|
@@ -973,79 +670,10 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
973
670
|
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Tag with id ${tag.id} not found. Please create the tag before associating it with the product.`);
|
|
974
671
|
});
|
|
975
672
|
}
|
|
976
|
-
delete product.options;
|
|
977
673
|
return product;
|
|
978
674
|
});
|
|
979
|
-
const
|
|
980
|
-
|
|
981
|
-
for (const [productId, options] of productOptionsToCreate.entries()) {
|
|
982
|
-
const optionIds = [];
|
|
983
|
-
for (const option of options) {
|
|
984
|
-
const optionId = (0, utils_1.generateEntityId)(undefined, "opt");
|
|
985
|
-
optionIds.push(optionId);
|
|
986
|
-
allOptionsWithIds.push({
|
|
987
|
-
...option,
|
|
988
|
-
id: optionId,
|
|
989
|
-
});
|
|
990
|
-
}
|
|
991
|
-
productToOptionIdsMap.set(productId, optionIds);
|
|
992
|
-
}
|
|
993
|
-
const [createdProducts] = await Promise.all([
|
|
994
|
-
this.productService_.create(productsToCreate, sharedContext),
|
|
995
|
-
allOptionsWithIds.length > 0
|
|
996
|
-
? this.createOptions_(allOptionsWithIds, sharedContext)
|
|
997
|
-
: Promise.resolve([]),
|
|
998
|
-
]);
|
|
999
|
-
const linkPairs = [];
|
|
1000
|
-
for (const product of createdProducts) {
|
|
1001
|
-
const hydratedProduct = hydratedData.find((p) => p.title === product.title);
|
|
1002
|
-
const existingOptions = [];
|
|
1003
|
-
if (hydratedProduct?.options?.length) {
|
|
1004
|
-
for (const option of hydratedProduct.options) {
|
|
1005
|
-
if ("id" in option) {
|
|
1006
|
-
existingOptions.push({
|
|
1007
|
-
id: option.id,
|
|
1008
|
-
value_ids: option.value_ids,
|
|
1009
|
-
});
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
const newOptionIds = productToOptionIdsMap.get(product.id) ?? [];
|
|
1014
|
-
const newOptions = newOptionIds.map((id) => ({ id }));
|
|
1015
|
-
const allOptions = [...existingOptions, ...newOptions];
|
|
1016
|
-
for (const option of allOptions) {
|
|
1017
|
-
const pair = {
|
|
1018
|
-
product_id: product.id,
|
|
1019
|
-
product_option_id: option.id,
|
|
1020
|
-
product_option_value_ids: option.value_ids
|
|
1021
|
-
? option.value_ids
|
|
1022
|
-
: undefined,
|
|
1023
|
-
};
|
|
1024
|
-
linkPairs.push(pair);
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
if (linkPairs.length > 0) {
|
|
1028
|
-
await this.addProductOptionToProduct_(linkPairs, sharedContext);
|
|
1029
|
-
}
|
|
1030
|
-
await sharedContext.transactionManager.flush();
|
|
1031
|
-
const productIds = createdProducts.map((p) => p.id);
|
|
1032
|
-
const productsWithOptions = await this.productService_.list({ id: productIds }, {
|
|
1033
|
-
relations: [
|
|
1034
|
-
"options",
|
|
1035
|
-
"options.values",
|
|
1036
|
-
"options.products",
|
|
1037
|
-
"product_options",
|
|
1038
|
-
"product_options.values",
|
|
1039
|
-
"variants",
|
|
1040
|
-
"images",
|
|
1041
|
-
"tags",
|
|
1042
|
-
],
|
|
1043
|
-
}, sharedContext);
|
|
1044
|
-
// Filter option values to only include those associated with each product
|
|
1045
|
-
this.filterOptionValuesByProducts(productsWithOptions);
|
|
1046
|
-
const productIdOrder = new Map(productIds.map((id, index) => [id, index]));
|
|
1047
|
-
const orderedProductsWithOptions = [...productsWithOptions].sort((a, b) => (productIdOrder.get(a.id) ?? 0) - (productIdOrder.get(b.id) ?? 0));
|
|
1048
|
-
return orderedProductsWithOptions;
|
|
675
|
+
const createdProducts = await this.productService_.create(productsToCreate, sharedContext);
|
|
676
|
+
return createdProducts;
|
|
1049
677
|
}
|
|
1050
678
|
async updateProducts_(data, sharedContext = {}) {
|
|
1051
679
|
// We have to do that manually because this method is bypassing the product service and goes
|
|
@@ -1058,73 +686,23 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
1058
686
|
.getEventManager()
|
|
1059
687
|
.registerSubscriber(new subscriber(sharedContext));
|
|
1060
688
|
}
|
|
1061
|
-
const
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
? this.productOptionService_.list({ id: allOptionIds }, {
|
|
1070
|
-
relations: ["values", "products"],
|
|
1071
|
-
}, sharedContext)
|
|
1072
|
-
: Promise.resolve([]),
|
|
1073
|
-
]);
|
|
1074
|
-
if (allOptionIds.length && existingOptions.length !== allOptionIds.length) {
|
|
1075
|
-
const found = new Set(existingOptions.map((opt) => opt.id));
|
|
1076
|
-
const missing = allOptionIds.filter((id) => !found.has(id));
|
|
1077
|
-
if (missing.length) {
|
|
1078
|
-
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Some product options were not found: [${missing.join(", ")}]`);
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
const linkPairs = [];
|
|
1082
|
-
const unlinkPairs = [];
|
|
1083
|
-
for (const product of data) {
|
|
1084
|
-
if (!product.option_ids) {
|
|
1085
|
-
continue;
|
|
1086
|
-
}
|
|
1087
|
-
const newOptionIds = new Set(product.option_ids);
|
|
1088
|
-
const existingOptionIds = new Set(originalProducts
|
|
1089
|
-
.find((p) => p.id === product.id)
|
|
1090
|
-
?.options?.map((o) => o.id) ?? []);
|
|
1091
|
-
for (const optionId of newOptionIds) {
|
|
1092
|
-
if (!existingOptionIds.has(optionId)) {
|
|
1093
|
-
linkPairs.push({
|
|
1094
|
-
product_id: product.id,
|
|
1095
|
-
product_option_id: optionId,
|
|
1096
|
-
});
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
for (const optionId of existingOptionIds) {
|
|
1100
|
-
if (!newOptionIds.has(optionId)) {
|
|
1101
|
-
unlinkPairs.push({
|
|
1102
|
-
product_id: product.id,
|
|
1103
|
-
product_option_id: optionId,
|
|
1104
|
-
});
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
delete product.option_ids;
|
|
1108
|
-
}
|
|
1109
|
-
await Promise.all([
|
|
1110
|
-
linkPairs.length &&
|
|
1111
|
-
this.addProductOptionToProduct_(linkPairs, sharedContext),
|
|
1112
|
-
unlinkPairs.length &&
|
|
1113
|
-
this.removeProductOptionFromProduct_(unlinkPairs, sharedContext),
|
|
1114
|
-
]);
|
|
1115
|
-
await sharedContext.transactionManager.flush();
|
|
1116
|
-
const normalizedProducts = (await this.normalizeUpdateProductInput(data));
|
|
689
|
+
const productIds = data.map((d) => d.id).filter(Boolean);
|
|
690
|
+
const originalProducts = await this.productService_.list({
|
|
691
|
+
id: productIds,
|
|
692
|
+
}, {
|
|
693
|
+
relations: ["options", "options.values", "tags"],
|
|
694
|
+
take: productIds.length,
|
|
695
|
+
}, sharedContext);
|
|
696
|
+
const normalizedProducts = await this.normalizeUpdateProductInput(data, originalProducts);
|
|
1117
697
|
for (const product of normalizedProducts) {
|
|
1118
698
|
this.validateProductUpdatePayload(product);
|
|
1119
699
|
}
|
|
1120
700
|
const updatedProducts = await this.productRepository_.deepUpdate(normalizedProducts, ProductModuleService.validateVariantOptions, sharedContext);
|
|
1121
|
-
// Filter option values to only include those associated with each product
|
|
1122
|
-
this.filterOptionValuesByProducts(updatedProducts);
|
|
1123
701
|
return updatedProducts;
|
|
1124
702
|
}
|
|
1125
703
|
// @ts-expect-error
|
|
1126
704
|
async updateProductOptionValues(idOrSelector, data, sharedContext = {}) {
|
|
1127
|
-
// TODO: There is a
|
|
705
|
+
// TODO: There is a missmatch in the API which lead to function with different number of
|
|
1128
706
|
// arguments. Therefore, applying the MedusaContext() decorator to the function will not work
|
|
1129
707
|
// because the context arg index will differ from method to method.
|
|
1130
708
|
sharedContext.messageAggregator ??= new utils_1.MessageAggregator();
|
|
@@ -1199,22 +777,7 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
1199
777
|
async normalizeCreateProductInput(products, sharedContext = {}) {
|
|
1200
778
|
const products_ = Array.isArray(products) ? products : [products];
|
|
1201
779
|
const normalizedProducts = (await this.normalizeUpdateProductInput(products_));
|
|
1202
|
-
for
|
|
1203
|
-
if (productData.options?.length) {
|
|
1204
|
-
;
|
|
1205
|
-
productData.options = productData.options?.map((option) => {
|
|
1206
|
-
return {
|
|
1207
|
-
title: option.title,
|
|
1208
|
-
values: option.values?.map((value) => {
|
|
1209
|
-
return {
|
|
1210
|
-
value: value,
|
|
1211
|
-
};
|
|
1212
|
-
}),
|
|
1213
|
-
is_exclusive: option.is_exclusive ?? true, // Always default to true for options created from product creation
|
|
1214
|
-
...(option.id ? { id: option.id } : {}),
|
|
1215
|
-
};
|
|
1216
|
-
});
|
|
1217
|
-
}
|
|
780
|
+
for (const productData of normalizedProducts) {
|
|
1218
781
|
if (!productData.handle && productData.title) {
|
|
1219
782
|
productData.handle = (0, utils_1.toHandle)(productData.title);
|
|
1220
783
|
}
|
|
@@ -1257,12 +820,39 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
1257
820
|
*/
|
|
1258
821
|
async normalizeUpdateProductInput(products, originalProducts) {
|
|
1259
822
|
const products_ = Array.isArray(products) ? products : [products];
|
|
823
|
+
const productsIds = products_.map((p) => p.id).filter(Boolean);
|
|
824
|
+
let dbOptions = [];
|
|
825
|
+
if (productsIds.length) {
|
|
826
|
+
// Re map options to handle non serialized data as well
|
|
827
|
+
dbOptions =
|
|
828
|
+
originalProducts
|
|
829
|
+
?.flatMap((originalProduct) => originalProduct.options.map((option) => option))
|
|
830
|
+
.filter(Boolean) ?? [];
|
|
831
|
+
}
|
|
1260
832
|
const normalizedProducts = [];
|
|
1261
833
|
for (const product of products_) {
|
|
1262
834
|
const productData = { ...product };
|
|
1263
835
|
if (productData.is_giftcard) {
|
|
1264
836
|
productData.discountable = false;
|
|
1265
837
|
}
|
|
838
|
+
if (productData.options?.length) {
|
|
839
|
+
;
|
|
840
|
+
productData.options = productData.options?.map((option) => {
|
|
841
|
+
const dbOption = dbOptions.find((o) => (o.title === option.title || o.id === option.id) &&
|
|
842
|
+
o.product_id === productData.id);
|
|
843
|
+
return {
|
|
844
|
+
title: option.title,
|
|
845
|
+
values: option.values?.map((value) => {
|
|
846
|
+
const dbValue = dbOption?.values?.find((val) => val.value === value);
|
|
847
|
+
return {
|
|
848
|
+
value: value,
|
|
849
|
+
...(dbValue ? { id: dbValue.id } : {}),
|
|
850
|
+
};
|
|
851
|
+
}),
|
|
852
|
+
...(dbOption ? { id: dbOption.id } : {}),
|
|
853
|
+
};
|
|
854
|
+
});
|
|
855
|
+
}
|
|
1266
856
|
if (productData.tag_ids) {
|
|
1267
857
|
;
|
|
1268
858
|
productData.tags = productData.tag_ids.map((cid) => ({
|
|
@@ -1301,10 +891,7 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
1301
891
|
const variantsWithOptions = ProductModuleService.assignOptionsToVariants(variants.map((v) => ({
|
|
1302
892
|
...v,
|
|
1303
893
|
// adding product_id to the variant to make it valid for the assignOptionsToVariants function
|
|
1304
|
-
|
|
1305
|
-
...(options.length && options[0].products?.length
|
|
1306
|
-
? { product_id: options[0].products[0].id }
|
|
1307
|
-
: {}),
|
|
894
|
+
...(options.length ? { product_id: options[0].product_id } : {}),
|
|
1308
895
|
})), options);
|
|
1309
896
|
ProductModuleService.checkIfVariantsHaveUniqueOptionsCombinations(variantsWithOptions);
|
|
1310
897
|
}
|
|
@@ -1314,13 +901,7 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
1314
901
|
}
|
|
1315
902
|
const variantsWithOptions = variants.map((variant) => {
|
|
1316
903
|
const numOfProvidedVariantOptionValues = Object.keys(variant.options || {}).length;
|
|
1317
|
-
const productsOptions = options.filter((o) =>
|
|
1318
|
-
// products could be a Collection object or array, normalize to array
|
|
1319
|
-
const productsArray = Array.isArray(o.products)
|
|
1320
|
-
? o.products
|
|
1321
|
-
: o.products?.toArray?.() ?? [];
|
|
1322
|
-
return productsArray.some((p) => p.id === variant.product_id);
|
|
1323
|
-
});
|
|
904
|
+
const productsOptions = options.filter((o) => o.product_id === variant.product_id);
|
|
1324
905
|
if (numOfProvidedVariantOptionValues &&
|
|
1325
906
|
productsOptions.length !== numOfProvidedVariantOptionValues) {
|
|
1326
907
|
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Product has ${productsOptions.length} option values but there were ${numOfProvidedVariantOptionValues} provided option values for the variant: ${variant.title}.`);
|
|
@@ -1519,134 +1100,6 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
1519
1100
|
}
|
|
1520
1101
|
return result;
|
|
1521
1102
|
}
|
|
1522
|
-
/**
|
|
1523
|
-
* Validates that no variants are using the specified option or option values
|
|
1524
|
-
* before they are removed from a product.
|
|
1525
|
-
*
|
|
1526
|
-
* @param pairs - Array of validation pairs: { productId, optionId, valueIdsToCheck? }
|
|
1527
|
-
* @param sharedContext - The shared context
|
|
1528
|
-
* @throws MedusaError if any variants are using the option/values
|
|
1529
|
-
*/
|
|
1530
|
-
async validateOptionRemoval_(pairs, sharedContext = {}) {
|
|
1531
|
-
if (pairs.length === 0) {
|
|
1532
|
-
return;
|
|
1533
|
-
}
|
|
1534
|
-
// Filter pairs that need validation (for option removal, check if option is linked)
|
|
1535
|
-
const pairsToValidate = [];
|
|
1536
|
-
// For option removals (no valueIdsToCheck), check if options are linked to products
|
|
1537
|
-
const optionRemovalPairs = pairs.filter((p) => !p.valueIdsToCheck);
|
|
1538
|
-
if (optionRemovalPairs.length > 0) {
|
|
1539
|
-
const existingProductOptions = await this.productProductOptionService_.list({
|
|
1540
|
-
$or: optionRemovalPairs.map((p) => ({
|
|
1541
|
-
product_id: p.productId,
|
|
1542
|
-
product_option_id: p.optionId,
|
|
1543
|
-
})),
|
|
1544
|
-
}, {}, sharedContext);
|
|
1545
|
-
const existingPairsSet = new Set(existingProductOptions.map((epo) => `${epo.product_id}_${epo.product_option_id}`));
|
|
1546
|
-
// Only validate pairs that are actually linked
|
|
1547
|
-
for (const pair of optionRemovalPairs) {
|
|
1548
|
-
const key = `${pair.productId}_${pair.optionId}`;
|
|
1549
|
-
if (existingPairsSet.has(key)) {
|
|
1550
|
-
pairsToValidate.push(pair);
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
|
-
}
|
|
1554
|
-
// For value removals (with valueIdsToCheck), always validate
|
|
1555
|
-
const valueRemovalPairs = pairs.filter((p) => p.valueIdsToCheck);
|
|
1556
|
-
pairsToValidate.push(...valueRemovalPairs);
|
|
1557
|
-
if (pairsToValidate.length === 0) {
|
|
1558
|
-
return; // Nothing to validate
|
|
1559
|
-
}
|
|
1560
|
-
// Get all unique option IDs to fetch options with their values
|
|
1561
|
-
const uniqueOptionIds = [...new Set(pairsToValidate.map((p) => p.optionId))];
|
|
1562
|
-
const options = await this.productOptionService_.list({ id: uniqueOptionIds }, { relations: ["values"] }, sharedContext);
|
|
1563
|
-
const optionsMap = new Map(options.map((opt) => [opt.id, opt]));
|
|
1564
|
-
const bulkValidationPairs = [];
|
|
1565
|
-
for (const pair of pairsToValidate) {
|
|
1566
|
-
const option = optionsMap.get(pair.optionId);
|
|
1567
|
-
if (!option) {
|
|
1568
|
-
continue; // Option doesn't exist, skip
|
|
1569
|
-
}
|
|
1570
|
-
// if no subset is provided we check the whole option values
|
|
1571
|
-
const valueIdsToValidate = pair.valueIdsToCheck
|
|
1572
|
-
? pair.valueIdsToCheck
|
|
1573
|
-
: (option.values || []).map((v) => v.id);
|
|
1574
|
-
if (valueIdsToValidate.length === 0) {
|
|
1575
|
-
continue; // No values to check
|
|
1576
|
-
}
|
|
1577
|
-
bulkValidationPairs.push({
|
|
1578
|
-
productId: pair.productId,
|
|
1579
|
-
optionValueIds: valueIdsToValidate,
|
|
1580
|
-
pair,
|
|
1581
|
-
option,
|
|
1582
|
-
});
|
|
1583
|
-
}
|
|
1584
|
-
if (bulkValidationPairs.length > 0) {
|
|
1585
|
-
const conflictingVariantsMap = await this.productRepository_.checkVariantsUsingOptionValues(bulkValidationPairs.map((p) => ({
|
|
1586
|
-
productId: p.productId,
|
|
1587
|
-
optionValueIds: p.optionValueIds,
|
|
1588
|
-
})), sharedContext);
|
|
1589
|
-
for (const bulkPair of bulkValidationPairs) {
|
|
1590
|
-
const allConflictingVariants = [];
|
|
1591
|
-
for (const valueId of bulkPair.optionValueIds) {
|
|
1592
|
-
const key = `${bulkPair.productId}_${valueId}`;
|
|
1593
|
-
const variants = conflictingVariantsMap.get(key) || [];
|
|
1594
|
-
// Deduplicate variants by variant_id
|
|
1595
|
-
for (const variant of variants) {
|
|
1596
|
-
if (!allConflictingVariants.some((v) => v.variant_id === variant.variant_id)) {
|
|
1597
|
-
allConflictingVariants.push({
|
|
1598
|
-
variant_id: variant.variant_id,
|
|
1599
|
-
title: variant.title,
|
|
1600
|
-
});
|
|
1601
|
-
}
|
|
1602
|
-
}
|
|
1603
|
-
}
|
|
1604
|
-
if (allConflictingVariants.length > 0) {
|
|
1605
|
-
const variantNames = allConflictingVariants.map((v) => v.title || v.variant_id);
|
|
1606
|
-
if (bulkPair.pair.valueIdsToCheck) {
|
|
1607
|
-
// Specific values being removed
|
|
1608
|
-
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Cannot unassign option values from product because the following variant(s) are using it: ${variantNames.join(", ")}`);
|
|
1609
|
-
}
|
|
1610
|
-
else {
|
|
1611
|
-
// Entire option being removed
|
|
1612
|
-
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Cannot unassign product option from product which has variants for that option`);
|
|
1613
|
-
}
|
|
1614
|
-
}
|
|
1615
|
-
}
|
|
1616
|
-
}
|
|
1617
|
-
}
|
|
1618
|
-
filterOptionValuesByProducts(products) {
|
|
1619
|
-
for (const product of products) {
|
|
1620
|
-
this.filterOptionValuesByProduct(product);
|
|
1621
|
-
}
|
|
1622
|
-
}
|
|
1623
|
-
filterOptionValuesByProduct(product) {
|
|
1624
|
-
if (!product.options || !product.product_options) {
|
|
1625
|
-
return;
|
|
1626
|
-
}
|
|
1627
|
-
const productOptions = Array.isArray(product.product_options)
|
|
1628
|
-
? product.product_options
|
|
1629
|
-
: product.product_options?.toArray?.() ?? [];
|
|
1630
|
-
// Build a Set of value IDs that are actually associated with this product
|
|
1631
|
-
const allowedValueIds = new Set();
|
|
1632
|
-
for (const productOption of productOptions) {
|
|
1633
|
-
const values = Array.isArray(productOption.values)
|
|
1634
|
-
? productOption.values
|
|
1635
|
-
: productOption.values?.toArray?.() ?? [];
|
|
1636
|
-
for (const value of values) {
|
|
1637
|
-
allowedValueIds.add(value.id);
|
|
1638
|
-
}
|
|
1639
|
-
}
|
|
1640
|
-
// Filter the values in each option to only include allowed ones
|
|
1641
|
-
if (product.options) {
|
|
1642
|
-
for (const option of product.options) {
|
|
1643
|
-
if (option.values) {
|
|
1644
|
-
option.values = option.values.filter((value) => allowedValueIds.has(value.id));
|
|
1645
|
-
}
|
|
1646
|
-
}
|
|
1647
|
-
}
|
|
1648
|
-
delete product.product_options;
|
|
1649
|
-
}
|
|
1650
1103
|
async buildVariantImagesFromProduct(variants, productImages, sharedContext = {}) {
|
|
1651
1104
|
// Create a clean map of images without problematic collections
|
|
1652
1105
|
const imagesMap = new Map();
|
|
@@ -1860,36 +1313,6 @@ __decorate([
|
|
|
1860
1313
|
__metadata("design:paramtypes", [Array, Object]),
|
|
1861
1314
|
__metadata("design:returntype", Promise)
|
|
1862
1315
|
], ProductModuleService.prototype, "updateOptions_", null);
|
|
1863
|
-
__decorate([
|
|
1864
|
-
(0, utils_1.InjectManager)(),
|
|
1865
|
-
(0, utils_1.EmitEvents)(),
|
|
1866
|
-
__param(1, (0, utils_1.MedusaContext)()),
|
|
1867
|
-
__metadata("design:type", Function),
|
|
1868
|
-
__metadata("design:paramtypes", [Object, Object]),
|
|
1869
|
-
__metadata("design:returntype", Promise)
|
|
1870
|
-
], ProductModuleService.prototype, "addProductOptionToProduct", null);
|
|
1871
|
-
__decorate([
|
|
1872
|
-
(0, utils_1.InjectTransactionManager)(),
|
|
1873
|
-
__param(1, (0, utils_1.MedusaContext)()),
|
|
1874
|
-
__metadata("design:type", Function),
|
|
1875
|
-
__metadata("design:paramtypes", [Object, Object]),
|
|
1876
|
-
__metadata("design:returntype", Promise)
|
|
1877
|
-
], ProductModuleService.prototype, "addProductOptionToProduct_", null);
|
|
1878
|
-
__decorate([
|
|
1879
|
-
(0, utils_1.InjectManager)(),
|
|
1880
|
-
(0, utils_1.EmitEvents)(),
|
|
1881
|
-
__param(1, (0, utils_1.MedusaContext)()),
|
|
1882
|
-
__metadata("design:type", Function),
|
|
1883
|
-
__metadata("design:paramtypes", [Object, Object]),
|
|
1884
|
-
__metadata("design:returntype", Promise)
|
|
1885
|
-
], ProductModuleService.prototype, "removeProductOptionFromProduct", null);
|
|
1886
|
-
__decorate([
|
|
1887
|
-
(0, utils_1.InjectTransactionManager)(),
|
|
1888
|
-
__param(1, (0, utils_1.MedusaContext)()),
|
|
1889
|
-
__metadata("design:type", Function),
|
|
1890
|
-
__metadata("design:paramtypes", [Object, Object]),
|
|
1891
|
-
__metadata("design:returntype", Promise)
|
|
1892
|
-
], ProductModuleService.prototype, "removeProductOptionFromProduct_", null);
|
|
1893
1316
|
__decorate([
|
|
1894
1317
|
(0, utils_1.InjectManager)(),
|
|
1895
1318
|
(0, utils_1.EmitEvents)()
|
|
@@ -2107,11 +1530,4 @@ __decorate([
|
|
|
2107
1530
|
__metadata("design:paramtypes", [Array, Object]),
|
|
2108
1531
|
__metadata("design:returntype", Promise)
|
|
2109
1532
|
], ProductModuleService.prototype, "getVariantImages", null);
|
|
2110
|
-
__decorate([
|
|
2111
|
-
(0, utils_1.InjectTransactionManager)(),
|
|
2112
|
-
__param(1, (0, utils_1.MedusaContext)()),
|
|
2113
|
-
__metadata("design:type", Function),
|
|
2114
|
-
__metadata("design:paramtypes", [Array, Object]),
|
|
2115
|
-
__metadata("design:returntype", Promise)
|
|
2116
|
-
], ProductModuleService.prototype, "validateOptionRemoval_", null);
|
|
2117
1533
|
//# sourceMappingURL=product-module-service.js.map
|