feat(medusa): variant creation with prices in productservice.create (#5410)
This commit is contained in:
5
.changeset/small-eggs-search.md
Normal file
5
.changeset/small-eggs-search.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@medusajs/medusa": patch
|
||||
---
|
||||
|
||||
feat(medusa): variant creation with prices in productservice.create
|
||||
147
integration-tests/plugins/__tests__/services/product.ts
Normal file
147
integration-tests/plugins/__tests__/services/product.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import path from "path"
|
||||
import { initDb, useDb } from "../../../environment-helpers/use-db"
|
||||
import { bootstrapApp } from "../../../environment-helpers/bootstrap-app"
|
||||
import { setPort } from "../../../environment-helpers/use-api"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
describe("product", () => {
|
||||
let dbConnection
|
||||
let medusaContainer
|
||||
let productService
|
||||
|
||||
let express
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||
dbConnection = await initDb({ cwd } as any)
|
||||
const { container, port, app } = await bootstrapApp({ cwd })
|
||||
|
||||
setPort(port)
|
||||
express = app.listen(port, () => {
|
||||
process.send!(port)
|
||||
})
|
||||
|
||||
medusaContainer = container
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
|
||||
express.close()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
const db = useDb()
|
||||
await db.teardown()
|
||||
})
|
||||
|
||||
describe("product service", () => {
|
||||
it("should create variant prices correctly in service creation", async () => {
|
||||
productService = medusaContainer.resolve("productService")
|
||||
|
||||
const payload = {
|
||||
title: "test-product",
|
||||
handle: "test-product",
|
||||
options: [{ title: "test-option" }],
|
||||
variants: [
|
||||
{
|
||||
title: "test-variant",
|
||||
inventory_quantity: 10,
|
||||
sku: "test",
|
||||
options: [{ value: "large", title: "test-option" }],
|
||||
prices: [{ amount: "100", currency_code: "usd" }],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const { id } = await productService.create(payload)
|
||||
|
||||
const result = await productService.retrieve(id, {
|
||||
relations: ["variants", "variants.prices", "variants.options"],
|
||||
})
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
variants: [
|
||||
expect.objectContaining({
|
||||
options: [expect.objectContaining({ value: "large" })],
|
||||
prices: [
|
||||
expect.objectContaining({ amount: 100, currency_code: "usd" }),
|
||||
],
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should fail to create a variant without options on for a product with options", async () => {
|
||||
const payload = {
|
||||
title: "test-product",
|
||||
handle: "test-product",
|
||||
options: [{ title: "test-option" }],
|
||||
variants: [
|
||||
{
|
||||
title: "test-variant",
|
||||
inventory_quantity: 10,
|
||||
sku: "test",
|
||||
prices: [{ amount: "100", currency_code: "usd" }],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let error
|
||||
|
||||
try {
|
||||
await productService.create(payload)
|
||||
} catch (err) {
|
||||
error = err
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(
|
||||
"Product options length does not match variant options length. Product has 1 and variant has 0."
|
||||
)
|
||||
})
|
||||
|
||||
it("should create a product and variant without options", async () => {
|
||||
const payload = {
|
||||
title: "test-product",
|
||||
handle: "test-product",
|
||||
variants: [
|
||||
{
|
||||
title: "test-variant",
|
||||
inventory_quantity: 10,
|
||||
sku: "test",
|
||||
prices: [{ amount: "100", currency_code: "usd" }],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const { id } = await productService.create(payload)
|
||||
|
||||
const result = await productService.retrieve(id, {
|
||||
relations: [
|
||||
"options",
|
||||
"variants",
|
||||
"variants.prices",
|
||||
"variants.options",
|
||||
],
|
||||
})
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
options: [],
|
||||
variants: [
|
||||
expect.objectContaining({
|
||||
prices: [
|
||||
expect.objectContaining({ amount: 100, currency_code: "usd" }),
|
||||
],
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -78,7 +78,7 @@ describe("ProductService", () => {
|
||||
title: "Suit",
|
||||
options: [],
|
||||
collection: { id: IdMap.getId("cat"), title: "Suits" },
|
||||
variants: product.variants,
|
||||
variants: product.variants ?? [],
|
||||
}),
|
||||
findOneWithRelations: () => ({
|
||||
id: IdMap.getId("ironman"),
|
||||
@@ -117,6 +117,13 @@ describe("ProductService", () => {
|
||||
Promise.resolve({ id: IdMap.getId("cat"), title: "Suits" }),
|
||||
}
|
||||
|
||||
const productVariantService = {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
create: (id, data) => Promise.resolve(data),
|
||||
}
|
||||
|
||||
const productService = new ProductService({
|
||||
manager: MockManager,
|
||||
productRepository,
|
||||
@@ -124,6 +131,7 @@ describe("ProductService", () => {
|
||||
productCollectionService,
|
||||
productTagRepository,
|
||||
productTypeRepository,
|
||||
productVariantService,
|
||||
featureFlagRouter: new FlagRouter({}),
|
||||
})
|
||||
|
||||
@@ -131,7 +139,7 @@ describe("ProductService", () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("successfully create a product", async () => {
|
||||
it("should successfully create a product", async () => {
|
||||
await productService.create({
|
||||
title: "Suit",
|
||||
options: [],
|
||||
@@ -158,16 +166,6 @@ describe("ProductService", () => {
|
||||
expect(productRepository.create).toHaveBeenCalledTimes(1)
|
||||
expect(productRepository.create).toHaveBeenCalledWith({
|
||||
title: "Suit",
|
||||
variants: [
|
||||
{
|
||||
id: "test1",
|
||||
title: "green",
|
||||
},
|
||||
{
|
||||
id: "test2",
|
||||
title: "blue",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
expect(productTagRepository.upsertTags).toHaveBeenCalledTimes(1)
|
||||
@@ -197,10 +195,12 @@ describe("ProductService", () => {
|
||||
variants: [
|
||||
{
|
||||
id: "test1",
|
||||
options: [],
|
||||
title: "green",
|
||||
},
|
||||
{
|
||||
id: "test2",
|
||||
options: [],
|
||||
title: "blue",
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import {
|
||||
buildRelations,
|
||||
buildSelects, FlagRouter, objectToStringPath
|
||||
buildRelations,
|
||||
buildSelects,
|
||||
FlagRouter,
|
||||
objectToStringPath,
|
||||
} from "@medusajs/utils"
|
||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
||||
import { EntityManager, In } from "typeorm"
|
||||
@@ -8,19 +10,19 @@ import { ProductVariantService, SearchService } from "."
|
||||
import { TransactionBaseService } from "../interfaces"
|
||||
import SalesChannelFeatureFlag from "../loaders/feature-flags/sales-channels"
|
||||
import {
|
||||
Product,
|
||||
ProductCategory,
|
||||
ProductOption,
|
||||
ProductTag,
|
||||
ProductType,
|
||||
ProductVariant,
|
||||
SalesChannel,
|
||||
ShippingProfile,
|
||||
Product,
|
||||
ProductCategory,
|
||||
ProductOption,
|
||||
ProductTag,
|
||||
ProductType,
|
||||
ProductVariant,
|
||||
SalesChannel,
|
||||
ShippingProfile,
|
||||
} from "../models"
|
||||
import { ImageRepository } from "../repositories/image"
|
||||
import {
|
||||
FindWithoutRelationsOptions,
|
||||
ProductRepository,
|
||||
FindWithoutRelationsOptions,
|
||||
ProductRepository,
|
||||
} from "../repositories/product"
|
||||
import { ProductCategoryRepository } from "../repositories/product-category"
|
||||
import { ProductOptionRepository } from "../repositories/product-option"
|
||||
@@ -29,15 +31,16 @@ import { ProductTypeRepository } from "../repositories/product-type"
|
||||
import { ProductVariantRepository } from "../repositories/product-variant"
|
||||
import { Selector } from "../types/common"
|
||||
import {
|
||||
CreateProductInput,
|
||||
FilterableProductProps,
|
||||
FindProductConfig,
|
||||
ProductOptionInput,
|
||||
ProductSelector,
|
||||
UpdateProductInput,
|
||||
CreateProductInput,
|
||||
FilterableProductProps,
|
||||
FindProductConfig,
|
||||
ProductOptionInput,
|
||||
ProductSelector,
|
||||
UpdateProductInput,
|
||||
} from "../types/product"
|
||||
import { buildQuery, isString, setMetadata } from "../utils"
|
||||
import EventBusService from "./event-bus"
|
||||
import { CreateProductVariantInput } from "../types/product-variant"
|
||||
|
||||
type InjectedDependencies = {
|
||||
manager: EntityManager
|
||||
@@ -429,6 +432,7 @@ class ProductService extends TransactionBaseService {
|
||||
tags,
|
||||
type,
|
||||
images,
|
||||
variants,
|
||||
sales_channels: salesChannels,
|
||||
categories: categories,
|
||||
...rest
|
||||
@@ -501,6 +505,27 @@ class ProductService extends TransactionBaseService {
|
||||
})
|
||||
)
|
||||
|
||||
if (variants) {
|
||||
const toCreate = variants.map((variant) => {
|
||||
return {
|
||||
...variant,
|
||||
options:
|
||||
variant.options?.map((option, index) => {
|
||||
return {
|
||||
option_id: product.options[index].id,
|
||||
...option,
|
||||
}
|
||||
}) ?? [],
|
||||
}
|
||||
})
|
||||
product.variants = await this.productVariantService_
|
||||
.withTransaction(manager)
|
||||
.create(
|
||||
product.id,
|
||||
toCreate as unknown as CreateProductVariantInput[]
|
||||
)
|
||||
}
|
||||
|
||||
const result = await this.retrieve(product.id, {
|
||||
relations: ["options"],
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user