feat(medusa): variant creation with prices in productservice.create (#5410)

This commit is contained in:
Philip Korsholm
2023-10-30 17:22:57 +01:00
committed by GitHub
parent 397da6c2ba
commit 4d52082bf0
4 changed files with 207 additions and 30 deletions

View File

@@ -0,0 +1,5 @@
---
"@medusajs/medusa": patch
---
feat(medusa): variant creation with prices in productservice.create

View 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" }),
],
}),
],
})
)
})
})
})

View File

@@ -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",
},
],

View File

@@ -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"],
})