@medusajs/product 3.0.0-snapshot-20251104011621 → 3.0.0-snapshot-20251106143524
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/Migration20251022153442.d.ts +6 -0
- package/dist/migrations/Migration20251022153442.d.ts.map +1 -0
- package/dist/migrations/Migration20251022153442.js +104 -0
- package/dist/migrations/Migration20251022153442.js.map +1 -0
- package/dist/migrations/Migration20251029150809.d.ts +6 -0
- package/dist/migrations/Migration20251029150809.d.ts.map +1 -0
- package/dist/migrations/Migration20251029150809.js +14 -0
- package/dist/migrations/Migration20251029150809.js.map +1 -0
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/index.js +3 -1
- package/dist/models/index.js.map +1 -1
- package/dist/models/product-category.d.ts +14 -6
- package/dist/models/product-category.d.ts.map +1 -1
- package/dist/models/product-collection.d.ts +14 -6
- package/dist/models/product-collection.d.ts.map +1 -1
- package/dist/models/product-image.d.ts +26 -14
- package/dist/models/product-image.d.ts.map +1 -1
- package/dist/models/product-option-value.d.ts +18 -12
- package/dist/models/product-option-value.d.ts.map +1 -1
- package/dist/models/product-option-value.js +1 -0
- package/dist/models/product-option-value.js.map +1 -1
- package/dist/models/product-option.d.ts +10 -6
- package/dist/models/product-option.d.ts.map +1 -1
- package/dist/models/product-option.js +4 -12
- package/dist/models/product-option.js.map +1 -1
- package/dist/models/product-product-option.d.ts +668 -0
- package/dist/models/product-product-option.d.ts.map +1 -0
- package/dist/models/product-product-option.js +19 -0
- package/dist/models/product-product-option.js.map +1 -0
- package/dist/models/product-tag.d.ts +14 -6
- package/dist/models/product-tag.d.ts.map +1 -1
- package/dist/models/product-type.d.ts +14 -6
- package/dist/models/product-type.d.ts.map +1 -1
- package/dist/models/product-variant-product-image.d.ts +47 -26
- package/dist/models/product-variant-product-image.d.ts.map +1 -1
- package/dist/models/product-variant.d.ts +21 -12
- package/dist/models/product-variant.d.ts.map +1 -1
- package/dist/models/product.d.ts +14 -6
- package/dist/models/product.d.ts.map +1 -1
- package/dist/models/product.js +5 -3
- package/dist/models/product.js.map +1 -1
- package/dist/repositories/product-category.d.ts +36 -18
- package/dist/repositories/product-category.d.ts.map +1 -1
- package/dist/repositories/product.d.ts +14 -6
- package/dist/repositories/product.d.ts.map +1 -1
- package/dist/repositories/product.js +2 -5
- package/dist/repositories/product.js.map +1 -1
- package/dist/services/product-module-service.d.ts +20 -4
- package/dist/services/product-module-service.d.ts.map +1 -1
- package/dist/services/product-module-service.js +312 -80
- package/dist/services/product-module-service.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
|
@@ -11,7 +11,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
11
11
|
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
12
|
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
13
|
};
|
|
14
|
-
var _a;
|
|
14
|
+
var _a, _b;
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
16
|
const types_1 = require("@medusajs/framework/types");
|
|
17
17
|
const _models_1 = require("../models");
|
|
@@ -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, productOptionValueService, productVariantProductImageService, [utils_1.Modules.EVENT_BUS]: eventBusModuleService, }, moduleDeclaration) {
|
|
32
|
+
constructor({ baseRepository, productRepository, productService, productVariantService, productTagService, productCategoryService, productCollectionService, productImageService, productTypeService, productOptionService, productProductOptionService, 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,6 +44,7 @@ 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;
|
|
47
48
|
this.productOptionValueService_ = productOptionValueService;
|
|
48
49
|
this.productVariantProductImageService_ = productVariantProductImageService;
|
|
49
50
|
this.eventBusModuleService_ = eventBusModuleService;
|
|
@@ -142,9 +143,11 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
142
143
|
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "Unable to create variants without specifying a product_id");
|
|
143
144
|
}
|
|
144
145
|
const productOptions = await this.productOptionService_.list({
|
|
145
|
-
|
|
146
|
+
products: {
|
|
147
|
+
id: [...new Set(data.map((v) => v.product_id))],
|
|
148
|
+
},
|
|
146
149
|
}, {
|
|
147
|
-
relations: ["values"],
|
|
150
|
+
relations: ["values", "products"],
|
|
148
151
|
}, sharedContext);
|
|
149
152
|
const variants = await this.productVariantService_.list({
|
|
150
153
|
product_id: [...new Set(data.map((v) => v.product_id))],
|
|
@@ -204,8 +207,10 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
204
207
|
product_id: v.product_id,
|
|
205
208
|
}));
|
|
206
209
|
const productOptions = await this.productOptionService_.list({
|
|
207
|
-
|
|
208
|
-
|
|
210
|
+
products: {
|
|
211
|
+
id: Array.from(new Set(variantsWithProductId.map((v) => v.product_id))),
|
|
212
|
+
},
|
|
213
|
+
}, { relations: ["values", "products"] }, sharedContext);
|
|
209
214
|
const productVariantsWithOptions = ProductModuleService.assignOptionsToVariants(variantsWithProductId, productOptions);
|
|
210
215
|
if (data.some((d) => !!d.options)) {
|
|
211
216
|
ProductModuleService.checkIfVariantWithOptionsAlreadyExists(productVariantsWithOptions, allVariants);
|
|
@@ -313,18 +318,25 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
313
318
|
return Array.isArray(data) ? createdOptions : createdOptions[0];
|
|
314
319
|
}
|
|
315
320
|
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
|
-
}
|
|
319
321
|
const normalizedInput = data.map((opt) => {
|
|
322
|
+
Object.keys(opt.ranks ?? []).forEach((value) => {
|
|
323
|
+
if (!opt.values.includes(value)) {
|
|
324
|
+
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.`);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
320
327
|
return {
|
|
321
328
|
...opt,
|
|
322
329
|
values: opt.values?.map((v) => {
|
|
323
|
-
|
|
330
|
+
// Normalize each value into an object and attach rank if available
|
|
331
|
+
const valueObj = (0, utils_1.isString)(v) ? { value: v } : v;
|
|
332
|
+
const rank = opt.ranks && (0, utils_1.isString)(v)
|
|
333
|
+
? opt.ranks[v]
|
|
334
|
+
: opt.ranks?.[valueObj.value];
|
|
335
|
+
return rank !== undefined ? { ...valueObj, rank } : valueObj;
|
|
324
336
|
}),
|
|
325
337
|
};
|
|
326
338
|
});
|
|
327
|
-
return
|
|
339
|
+
return this.productOptionService_.create(normalizedInput, sharedContext);
|
|
328
340
|
}
|
|
329
341
|
async upsertProductOptions(data, sharedContext = {}) {
|
|
330
342
|
const input = Array.isArray(data) ? data : [data];
|
|
@@ -371,35 +383,77 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
371
383
|
}
|
|
372
384
|
// Data normalization
|
|
373
385
|
const normalizedInput = data.map((opt) => {
|
|
374
|
-
const
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
-
}),
|
|
386
|
+
const dbOption = dbOptions.find(({ id }) => id === opt.id);
|
|
387
|
+
const dbValues = dbOption?.values || [];
|
|
388
|
+
if (opt.ranks) {
|
|
389
|
+
const validValues = opt.values ?? dbValues.map((v) => v.value);
|
|
390
|
+
Object.keys(opt.ranks).forEach((value) => {
|
|
391
|
+
if (!validValues.includes(value)) {
|
|
392
|
+
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.`);
|
|
396
393
|
}
|
|
397
|
-
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
let normalizedValues;
|
|
397
|
+
if (opt.values) {
|
|
398
|
+
// If new values are provided → normalize and apply ranks
|
|
399
|
+
normalizedValues = opt.values.map((v) => {
|
|
400
|
+
const valueObj = (0, utils_1.isString)(v) ? { value: v } : v;
|
|
401
|
+
const rank = opt.ranks && (0, utils_1.isString)(v)
|
|
402
|
+
? opt.ranks[v]
|
|
403
|
+
: opt.ranks?.[valueObj.value];
|
|
404
|
+
const rankedValue = rank !== undefined ? { ...valueObj, rank } : valueObj;
|
|
405
|
+
if ("id" in rankedValue) {
|
|
406
|
+
return rankedValue;
|
|
407
|
+
}
|
|
408
|
+
const dbVal = dbValues.find((dbVal) => dbVal.value === rankedValue.value);
|
|
409
|
+
if (!dbVal) {
|
|
410
|
+
return rankedValue;
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
id: dbVal.id,
|
|
414
|
+
...rankedValue,
|
|
415
|
+
};
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
else if (opt.ranks) {
|
|
419
|
+
// If only ranks were provided → update existing DB values with ranks
|
|
420
|
+
normalizedValues = dbValues.map((dbVal) => {
|
|
421
|
+
const rank = opt.ranks[dbVal.value];
|
|
422
|
+
return rank !== undefined
|
|
423
|
+
? { id: dbVal.id, value: dbVal.value, rank }
|
|
424
|
+
: { id: dbVal.id, value: dbVal.value };
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
const { ranks, ...cleanOpt } = opt;
|
|
428
|
+
return {
|
|
429
|
+
...cleanOpt,
|
|
430
|
+
...(normalizedValues ? { values: normalizedValues } : {}),
|
|
398
431
|
};
|
|
399
432
|
});
|
|
400
433
|
const { entities: productOptions } = await this.productOptionService_.upsertWithReplace(normalizedInput, { relations: ["values"] }, sharedContext);
|
|
401
434
|
return productOptions;
|
|
402
435
|
}
|
|
436
|
+
async addProductOptionToProduct(data, sharedContext = {}) {
|
|
437
|
+
const productOptionProducts = await this.addProductOptionToProduct_(data, sharedContext);
|
|
438
|
+
return productOptionProducts;
|
|
439
|
+
}
|
|
440
|
+
async addProductOptionToProduct_(data, sharedContext = {}) {
|
|
441
|
+
const productOptionProducts = await this.productProductOptionService_.create(data, sharedContext);
|
|
442
|
+
if (Array.isArray(data)) {
|
|
443
|
+
return productOptionProducts.map((ppo) => ({ id: ppo.id }));
|
|
444
|
+
}
|
|
445
|
+
return { id: productOptionProducts.id };
|
|
446
|
+
}
|
|
447
|
+
async removeProductOptionFromProduct(data, sharedContext = {}) {
|
|
448
|
+
await this.removeProductOptionFromProduct_(data, sharedContext);
|
|
449
|
+
}
|
|
450
|
+
async removeProductOptionFromProduct_(data, sharedContext = {}) {
|
|
451
|
+
const pairs = Array.isArray(data) ? data : [data];
|
|
452
|
+
const productOptionsProducts = await this.productProductOptionService_.list({
|
|
453
|
+
$or: pairs,
|
|
454
|
+
});
|
|
455
|
+
await this.productProductOptionService_.delete(productOptionsProducts.map(({ id }) => id), sharedContext);
|
|
456
|
+
}
|
|
403
457
|
// @ts-expect-error
|
|
404
458
|
async createProductCollections(data, sharedContext = {}) {
|
|
405
459
|
const input = Array.isArray(data) ? data : [data];
|
|
@@ -624,7 +678,40 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
624
678
|
return (0, utils_1.isString)(idOrSelector) ? updatedProducts[0] : updatedProducts;
|
|
625
679
|
}
|
|
626
680
|
async createProducts_(data, sharedContext = {}) {
|
|
627
|
-
const
|
|
681
|
+
const existingOptionIds = data
|
|
682
|
+
.flatMap((p) => p.options ?? [])
|
|
683
|
+
.filter((o) => "id" in o)
|
|
684
|
+
.map((o) => o.id);
|
|
685
|
+
let existingOptions = [];
|
|
686
|
+
if (existingOptionIds.length > 0) {
|
|
687
|
+
existingOptions = await this.productOptionService_.list({ id: existingOptionIds }, { relations: ["values"] }, sharedContext);
|
|
688
|
+
const fetchedIds = new Set(existingOptions.map((opt) => opt.id));
|
|
689
|
+
const missingIds = existingOptionIds.filter((id) => !fetchedIds.has(id));
|
|
690
|
+
if (missingIds.length) {
|
|
691
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Some product options were not found: [${missingIds.join(", ")}]`);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
const existingOptionsMap = new Map(existingOptions.map((opt) => [opt.id, opt]));
|
|
695
|
+
const hydratedData = data.map((product) => {
|
|
696
|
+
if (!product.options?.length)
|
|
697
|
+
return product;
|
|
698
|
+
const hydratedOptions = product.options.map((option) => {
|
|
699
|
+
if ("id" in option) {
|
|
700
|
+
const dbOption = existingOptionsMap.get(option.id);
|
|
701
|
+
if (!dbOption) {
|
|
702
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Product option with id ${option.id} not found.`);
|
|
703
|
+
}
|
|
704
|
+
return {
|
|
705
|
+
id: dbOption.id,
|
|
706
|
+
title: dbOption.title,
|
|
707
|
+
values: dbOption.values?.map((v) => ({ value: v.value })),
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
return option;
|
|
711
|
+
});
|
|
712
|
+
return { ...product, options: hydratedOptions };
|
|
713
|
+
});
|
|
714
|
+
const normalizedProducts = this.normalizeCreateProductInput(hydratedData, sharedContext);
|
|
628
715
|
for (const product of normalizedProducts) {
|
|
629
716
|
this.validateProductCreatePayload(product);
|
|
630
717
|
}
|
|
@@ -638,6 +725,7 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
638
725
|
}, {}, sharedContext);
|
|
639
726
|
}
|
|
640
727
|
const existingTagsMap = new Map(existingTags.map((tag) => [tag.id, tag]));
|
|
728
|
+
const productOptionsToCreate = new Map();
|
|
641
729
|
const productsToCreate = normalizedProducts.map((product) => {
|
|
642
730
|
const productId = (0, utils_1.generateEntityId)(product.id, "prod");
|
|
643
731
|
product.id = productId;
|
|
@@ -645,6 +733,12 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
645
733
|
;
|
|
646
734
|
product.categories = product.categories.map((category) => category.id);
|
|
647
735
|
}
|
|
736
|
+
if (product.options?.length) {
|
|
737
|
+
const newOptions = product.options.filter((o) => !("id" in o));
|
|
738
|
+
if (newOptions.length) {
|
|
739
|
+
productOptionsToCreate.set(productId, newOptions);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
648
742
|
if (product.variants?.length) {
|
|
649
743
|
const normalizedVariants = product.variants.map((variant) => {
|
|
650
744
|
const variantId = (0, utils_1.generateEntityId)(variant.id, "variant");
|
|
@@ -670,10 +764,66 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
670
764
|
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.`);
|
|
671
765
|
});
|
|
672
766
|
}
|
|
767
|
+
delete product.options;
|
|
673
768
|
return product;
|
|
674
769
|
});
|
|
675
|
-
const
|
|
676
|
-
|
|
770
|
+
const productToOptionIdsMap = new Map();
|
|
771
|
+
const allOptionsWithIds = [];
|
|
772
|
+
for (const [productId, options] of productOptionsToCreate.entries()) {
|
|
773
|
+
const optionIds = [];
|
|
774
|
+
for (const option of options) {
|
|
775
|
+
const optionId = (0, utils_1.generateEntityId)(undefined, "opt");
|
|
776
|
+
optionIds.push(optionId);
|
|
777
|
+
allOptionsWithIds.push({
|
|
778
|
+
...option,
|
|
779
|
+
id: optionId,
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
productToOptionIdsMap.set(productId, optionIds);
|
|
783
|
+
}
|
|
784
|
+
const [createdProducts] = await Promise.all([
|
|
785
|
+
this.productService_.create(productsToCreate, sharedContext),
|
|
786
|
+
allOptionsWithIds.length > 0
|
|
787
|
+
? this.createOptions_(allOptionsWithIds, sharedContext)
|
|
788
|
+
: Promise.resolve([]),
|
|
789
|
+
]);
|
|
790
|
+
const linkPairs = [];
|
|
791
|
+
for (const product of createdProducts) {
|
|
792
|
+
const hydratedProduct = hydratedData.find((p) => p.title === product.title);
|
|
793
|
+
const allOptionIds = [];
|
|
794
|
+
if (hydratedProduct?.options?.length) {
|
|
795
|
+
for (const option of hydratedProduct.options) {
|
|
796
|
+
if ("id" in option) {
|
|
797
|
+
allOptionIds.push(option.id);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
const newOptionIds = productToOptionIdsMap.get(product.id) ?? [];
|
|
802
|
+
const optionIds = [...new Set([...allOptionIds, ...newOptionIds])];
|
|
803
|
+
for (const optionId of optionIds) {
|
|
804
|
+
linkPairs.push({
|
|
805
|
+
product_id: product.id,
|
|
806
|
+
product_option_id: optionId,
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
if (linkPairs.length > 0) {
|
|
811
|
+
await this.addProductOptionToProduct_(linkPairs, sharedContext);
|
|
812
|
+
}
|
|
813
|
+
const productIds = createdProducts.map((p) => p.id);
|
|
814
|
+
const productsWithOptions = await this.productService_.list({ id: productIds }, {
|
|
815
|
+
relations: [
|
|
816
|
+
"options",
|
|
817
|
+
"options.values",
|
|
818
|
+
"options.products",
|
|
819
|
+
"variants",
|
|
820
|
+
"images",
|
|
821
|
+
"tags",
|
|
822
|
+
],
|
|
823
|
+
}, sharedContext);
|
|
824
|
+
const productIdOrder = new Map(productIds.map((id, index) => [id, index]));
|
|
825
|
+
const orderedProductsWithOptions = [...productsWithOptions].sort((a, b) => (productIdOrder.get(a.id) ?? 0) - (productIdOrder.get(b.id) ?? 0));
|
|
826
|
+
return orderedProductsWithOptions;
|
|
677
827
|
}
|
|
678
828
|
async updateProducts_(data, sharedContext = {}) {
|
|
679
829
|
// We have to do that manually because this method is bypassing the product service and goes
|
|
@@ -686,12 +836,69 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
686
836
|
.getEventManager()
|
|
687
837
|
.registerSubscriber(new subscriber(sharedContext));
|
|
688
838
|
}
|
|
689
|
-
const
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
839
|
+
const allOptionIds = data
|
|
840
|
+
.flatMap((p) => p.option_ids ?? [])
|
|
841
|
+
.filter((id) => !!id);
|
|
842
|
+
const [originalProducts, existingOptions] = await Promise.all([
|
|
843
|
+
this.productService_.list({ id: data.map((d) => d.id) }, {
|
|
844
|
+
relations: [
|
|
845
|
+
"options",
|
|
846
|
+
"options.values",
|
|
847
|
+
"options.products",
|
|
848
|
+
"variants",
|
|
849
|
+
"images",
|
|
850
|
+
"tags",
|
|
851
|
+
],
|
|
852
|
+
}, sharedContext),
|
|
853
|
+
allOptionIds.length
|
|
854
|
+
? this.productOptionService_.list({ id: allOptionIds }, {
|
|
855
|
+
relations: ["values", "products"],
|
|
856
|
+
}, sharedContext)
|
|
857
|
+
: Promise.resolve([]),
|
|
858
|
+
]);
|
|
859
|
+
if (allOptionIds.length && existingOptions.length !== allOptionIds.length) {
|
|
860
|
+
const found = new Set(existingOptions.map((opt) => opt.id));
|
|
861
|
+
const missing = allOptionIds.filter((id) => !found.has(id));
|
|
862
|
+
if (missing.length) {
|
|
863
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Some product options were not found: [${missing.join(", ")}]`);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
const linkPairs = [];
|
|
867
|
+
const unlinkPairs = [];
|
|
868
|
+
for (const product of data) {
|
|
869
|
+
if (!product.option_ids) {
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
const newOptionIds = new Set(product.option_ids);
|
|
873
|
+
const existingOptionIds = new Set(originalProducts
|
|
874
|
+
.find((p) => p.id === product.id)
|
|
875
|
+
?.options?.map((o) => o.id) ?? []);
|
|
876
|
+
for (const optionId of newOptionIds) {
|
|
877
|
+
if (!existingOptionIds.has(optionId)) {
|
|
878
|
+
linkPairs.push({
|
|
879
|
+
product_id: product.id,
|
|
880
|
+
product_option_id: optionId,
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
for (const optionId of existingOptionIds) {
|
|
885
|
+
if (!newOptionIds.has(optionId)) {
|
|
886
|
+
unlinkPairs.push({
|
|
887
|
+
product_id: product.id,
|
|
888
|
+
product_option_id: optionId,
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
delete product.option_ids;
|
|
893
|
+
}
|
|
894
|
+
await Promise.all([
|
|
895
|
+
linkPairs.length &&
|
|
896
|
+
this.addProductOptionToProduct_(linkPairs, sharedContext),
|
|
897
|
+
unlinkPairs.length &&
|
|
898
|
+
this.removeProductOptionFromProduct_(unlinkPairs, sharedContext),
|
|
899
|
+
]);
|
|
900
|
+
await sharedContext.transactionManager.flush();
|
|
901
|
+
const normalizedProducts = this.normalizeUpdateProductInput(data);
|
|
695
902
|
for (const product of normalizedProducts) {
|
|
696
903
|
this.validateProductUpdatePayload(product);
|
|
697
904
|
}
|
|
@@ -700,7 +907,7 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
700
907
|
}
|
|
701
908
|
// @ts-expect-error
|
|
702
909
|
async updateProductOptionValues(idOrSelector, data, sharedContext = {}) {
|
|
703
|
-
// TODO: There is a
|
|
910
|
+
// TODO: There is a mismatch in the API which lead to function with different number of
|
|
704
911
|
// arguments. Therefore, applying the MedusaContext() decorator to the function will not work
|
|
705
912
|
// because the context arg index will differ from method to method.
|
|
706
913
|
sharedContext.messageAggregator ??= new utils_1.MessageAggregator();
|
|
@@ -772,10 +979,24 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
772
979
|
validateProductUpdatePayload(productData) {
|
|
773
980
|
this.validateProductPayload(productData);
|
|
774
981
|
}
|
|
775
|
-
|
|
982
|
+
normalizeCreateProductInput(products, sharedContext = {}) {
|
|
776
983
|
const products_ = Array.isArray(products) ? products : [products];
|
|
777
|
-
const normalizedProducts =
|
|
984
|
+
const normalizedProducts = this.normalizeUpdateProductInput(products_);
|
|
778
985
|
for (const productData of normalizedProducts) {
|
|
986
|
+
if (productData.options?.length) {
|
|
987
|
+
;
|
|
988
|
+
productData.options = productData.options?.map((option) => {
|
|
989
|
+
return {
|
|
990
|
+
title: option.title,
|
|
991
|
+
values: option.values?.map((value) => {
|
|
992
|
+
return {
|
|
993
|
+
value: value,
|
|
994
|
+
};
|
|
995
|
+
}),
|
|
996
|
+
...(option.id ? { id: option.id } : {}),
|
|
997
|
+
};
|
|
998
|
+
});
|
|
999
|
+
}
|
|
779
1000
|
if (!productData.handle && productData.title) {
|
|
780
1001
|
productData.handle = (0, utils_1.toHandle)(productData.title);
|
|
781
1002
|
}
|
|
@@ -816,46 +1037,18 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
816
1037
|
* @param originalProducts - The original products to use for the normalization (must include options and option values relations)
|
|
817
1038
|
* @returns The normalized products
|
|
818
1039
|
*/
|
|
819
|
-
|
|
1040
|
+
normalizeUpdateProductInput(products) {
|
|
820
1041
|
const products_ = Array.isArray(products) ? products : [products];
|
|
821
|
-
const productsIds = products_.map((p) => p.id).filter(Boolean);
|
|
822
|
-
let dbOptions = [];
|
|
823
|
-
if (productsIds.length) {
|
|
824
|
-
// Re map options to handle non serialized data as well
|
|
825
|
-
dbOptions =
|
|
826
|
-
originalProducts
|
|
827
|
-
?.map((originalProduct) => originalProduct.options.map((option) => option))
|
|
828
|
-
.flat()
|
|
829
|
-
.filter(Boolean) ?? [];
|
|
830
|
-
}
|
|
831
1042
|
const normalizedProducts = [];
|
|
832
1043
|
for (const product of products_) {
|
|
833
1044
|
const productData = { ...product };
|
|
834
1045
|
if (productData.is_giftcard) {
|
|
835
1046
|
productData.discountable = false;
|
|
836
1047
|
}
|
|
837
|
-
if (productData.options?.length) {
|
|
838
|
-
;
|
|
839
|
-
productData.options = productData.options?.map((option) => {
|
|
840
|
-
const dbOption = dbOptions.find((o) => (o.title === option.title || o.id === option.id) &&
|
|
841
|
-
o.product_id === productData.id);
|
|
842
|
-
return {
|
|
843
|
-
title: option.title,
|
|
844
|
-
values: option.values?.map((value) => {
|
|
845
|
-
const dbValue = dbOption?.values?.find((val) => val.value === value);
|
|
846
|
-
return {
|
|
847
|
-
value: value,
|
|
848
|
-
...(dbValue ? { id: dbValue.id } : {}),
|
|
849
|
-
};
|
|
850
|
-
}),
|
|
851
|
-
...(dbOption ? { id: dbOption.id } : {}),
|
|
852
|
-
};
|
|
853
|
-
});
|
|
854
|
-
}
|
|
855
1048
|
if (productData.tag_ids) {
|
|
856
1049
|
;
|
|
857
|
-
productData.tags = productData.tag_ids.map((
|
|
858
|
-
id:
|
|
1050
|
+
productData.tags = productData.tag_ids.map((tid) => ({
|
|
1051
|
+
id: tid,
|
|
859
1052
|
}));
|
|
860
1053
|
delete productData.tag_ids;
|
|
861
1054
|
}
|
|
@@ -890,7 +1083,10 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
890
1083
|
const variantsWithOptions = ProductModuleService.assignOptionsToVariants(variants.map((v) => ({
|
|
891
1084
|
...v,
|
|
892
1085
|
// adding product_id to the variant to make it valid for the assignOptionsToVariants function
|
|
893
|
-
|
|
1086
|
+
// get product_id from the first product in the products array of the first option
|
|
1087
|
+
...(options.length && options[0].products?.length
|
|
1088
|
+
? { product_id: options[0].products[0].id }
|
|
1089
|
+
: {}),
|
|
894
1090
|
})), options);
|
|
895
1091
|
ProductModuleService.checkIfVariantsHaveUniqueOptionsCombinations(variantsWithOptions);
|
|
896
1092
|
}
|
|
@@ -900,7 +1096,13 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
|
|
|
900
1096
|
}
|
|
901
1097
|
const variantsWithOptions = variants.map((variant) => {
|
|
902
1098
|
const numOfProvidedVariantOptionValues = Object.keys(variant.options || {}).length;
|
|
903
|
-
const productsOptions = options.filter((o) =>
|
|
1099
|
+
const productsOptions = options.filter((o) => {
|
|
1100
|
+
// products could be a Collection object or array, normalize to array
|
|
1101
|
+
const productsArray = Array.isArray(o.products)
|
|
1102
|
+
? o.products
|
|
1103
|
+
: o.products?.toArray?.() ?? [];
|
|
1104
|
+
return productsArray.some((p) => p.id === variant.product_id);
|
|
1105
|
+
});
|
|
904
1106
|
if (numOfProvidedVariantOptionValues &&
|
|
905
1107
|
productsOptions.length !== numOfProvidedVariantOptionValues) {
|
|
906
1108
|
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}.`);
|
|
@@ -1312,6 +1514,36 @@ __decorate([
|
|
|
1312
1514
|
__metadata("design:paramtypes", [Array, Object]),
|
|
1313
1515
|
__metadata("design:returntype", Promise)
|
|
1314
1516
|
], ProductModuleService.prototype, "updateOptions_", null);
|
|
1517
|
+
__decorate([
|
|
1518
|
+
(0, utils_1.InjectManager)(),
|
|
1519
|
+
(0, utils_1.EmitEvents)(),
|
|
1520
|
+
__param(1, (0, utils_1.MedusaContext)()),
|
|
1521
|
+
__metadata("design:type", Function),
|
|
1522
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
1523
|
+
__metadata("design:returntype", Promise)
|
|
1524
|
+
], ProductModuleService.prototype, "addProductOptionToProduct", null);
|
|
1525
|
+
__decorate([
|
|
1526
|
+
(0, utils_1.InjectTransactionManager)(),
|
|
1527
|
+
__param(1, (0, utils_1.MedusaContext)()),
|
|
1528
|
+
__metadata("design:type", Function),
|
|
1529
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
1530
|
+
__metadata("design:returntype", Promise)
|
|
1531
|
+
], ProductModuleService.prototype, "addProductOptionToProduct_", null);
|
|
1532
|
+
__decorate([
|
|
1533
|
+
(0, utils_1.InjectManager)(),
|
|
1534
|
+
(0, utils_1.EmitEvents)(),
|
|
1535
|
+
__param(1, (0, utils_1.MedusaContext)()),
|
|
1536
|
+
__metadata("design:type", Function),
|
|
1537
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
1538
|
+
__metadata("design:returntype", Promise)
|
|
1539
|
+
], ProductModuleService.prototype, "removeProductOptionFromProduct", null);
|
|
1540
|
+
__decorate([
|
|
1541
|
+
(0, utils_1.InjectTransactionManager)(),
|
|
1542
|
+
__param(1, (0, utils_1.MedusaContext)()),
|
|
1543
|
+
__metadata("design:type", Function),
|
|
1544
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
1545
|
+
__metadata("design:returntype", Promise)
|
|
1546
|
+
], ProductModuleService.prototype, "removeProductOptionFromProduct_", null);
|
|
1315
1547
|
__decorate([
|
|
1316
1548
|
(0, utils_1.InjectManager)(),
|
|
1317
1549
|
(0, utils_1.EmitEvents)()
|
|
@@ -1456,7 +1688,7 @@ __decorate([
|
|
|
1456
1688
|
__param(1, (0, utils_1.MedusaContext)()),
|
|
1457
1689
|
__metadata("design:type", Function),
|
|
1458
1690
|
__metadata("design:paramtypes", [typeof (_a = typeof T !== "undefined" && T) === "function" ? _a : Object, Object]),
|
|
1459
|
-
__metadata("design:returntype",
|
|
1691
|
+
__metadata("design:returntype", typeof (_b = typeof TOutput !== "undefined" && TOutput) === "function" ? _b : Object)
|
|
1460
1692
|
], ProductModuleService.prototype, "normalizeCreateProductInput", null);
|
|
1461
1693
|
__decorate([
|
|
1462
1694
|
(0, utils_1.InjectManager)()
|