feat(medusa): variant creation with prices in productservice.create (#5410)
This commit is contained in:
1 parent
397da6c2ba
commit
4d52082bf0
4 files changed
+207
-30
No files matched your search
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(medusa): variant creation with prices in productservice.create
|
||||||
@@ -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",
|
title: "Suit",
|
||||||
options: [],
|
options: [],
|
||||||
collection: { id: IdMap.getId("cat"), title: "Suits" },
|
collection: { id: IdMap.getId("cat"), title: "Suits" },
|
||||||
variants: product.variants,
|
variants: product.variants ?? [],
|
||||||
}),
|
}),
|
||||||
findOneWithRelations: () => ({
|
findOneWithRelations: () => ({
|
||||||
id: IdMap.getId("ironman"),
|
id: IdMap.getId("ironman"),
|
||||||
@@ -117,6 +117,13 @@ describe("ProductService", () => {
|
|||||||
Promise.resolve({ id: IdMap.getId("cat"), title: "Suits" }),
|
Promise.resolve({ id: IdMap.getId("cat"), title: "Suits" }),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const productVariantService = {
|
||||||
|
withTransaction: function () {
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
create: (id, data) => Promise.resolve(data),
|
||||||
|
}
|
||||||
|
|
||||||
const productService = new ProductService({
|
const productService = new ProductService({
|
||||||
manager: MockManager,
|
manager: MockManager,
|
||||||
productRepository,
|
productRepository,
|
||||||
@@ -124,6 +131,7 @@ describe("ProductService", () => {
|
|||||||
productCollectionService,
|
productCollectionService,
|
||||||
productTagRepository,
|
productTagRepository,
|
||||||
productTypeRepository,
|
productTypeRepository,
|
||||||
|
productVariantService,
|
||||||
featureFlagRouter: new FlagRouter({}),
|
featureFlagRouter: new FlagRouter({}),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -131,7 +139,7 @@ describe("ProductService", () => {
|
|||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("successfully create a product", async () => {
|
it("should successfully create a product", async () => {
|
||||||
await productService.create({
|
await productService.create({
|
||||||
title: "Suit",
|
title: "Suit",
|
||||||
options: [],
|
options: [],
|
||||||
@@ -158,16 +166,6 @@ describe("ProductService", () => {
|
|||||||
expect(productRepository.create).toHaveBeenCalledTimes(1)
|
expect(productRepository.create).toHaveBeenCalledTimes(1)
|
||||||
expect(productRepository.create).toHaveBeenCalledWith({
|
expect(productRepository.create).toHaveBeenCalledWith({
|
||||||
title: "Suit",
|
title: "Suit",
|
||||||
variants: [
|
|
||||||
{
|
|
||||||
id: "test1",
|
|
||||||
title: "green",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "test2",
|
|
||||||
title: "blue",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(productTagRepository.upsertTags).toHaveBeenCalledTimes(1)
|
expect(productTagRepository.upsertTags).toHaveBeenCalledTimes(1)
|
||||||
@@ -197,10 +195,12 @@ describe("ProductService", () => {
|
|||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
id: "test1",
|
id: "test1",
|
||||||
|
options: [],
|
||||||
title: "green",
|
title: "green",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "test2",
|
id: "test2",
|
||||||
|
options: [],
|
||||||
title: "blue",
|
title: "blue",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
buildRelations,
|
buildRelations,
|
||||||
buildSelects, FlagRouter, objectToStringPath
|
buildSelects,
|
||||||
|
FlagRouter,
|
||||||
|
objectToStringPath,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
import { isDefined, MedusaError } from "medusa-core-utils"
|
||||||
import { EntityManager, In } from "typeorm"
|
import { EntityManager, In } from "typeorm"
|
||||||
@@ -8,19 +10,19 @@ import { ProductVariantService, SearchService } from "."
|
|||||||
import { TransactionBaseService } from "../interfaces"
|
import { TransactionBaseService } from "../interfaces"
|
||||||
import SalesChannelFeatureFlag from "../loaders/feature-flags/sales-channels"
|
import SalesChannelFeatureFlag from "../loaders/feature-flags/sales-channels"
|
||||||
import {
|
import {
|
||||||
Product,
|
Product,
|
||||||
ProductCategory,
|
ProductCategory,
|
||||||
ProductOption,
|
ProductOption,
|
||||||
ProductTag,
|
ProductTag,
|
||||||
ProductType,
|
ProductType,
|
||||||
ProductVariant,
|
ProductVariant,
|
||||||
SalesChannel,
|
SalesChannel,
|
||||||
ShippingProfile,
|
ShippingProfile,
|
||||||
} from "../models"
|
} from "../models"
|
||||||
import { ImageRepository } from "../repositories/image"
|
import { ImageRepository } from "../repositories/image"
|
||||||
import {
|
import {
|
||||||
FindWithoutRelationsOptions,
|
FindWithoutRelationsOptions,
|
||||||
ProductRepository,
|
ProductRepository,
|
||||||
} from "../repositories/product"
|
} from "../repositories/product"
|
||||||
import { ProductCategoryRepository } from "../repositories/product-category"
|
import { ProductCategoryRepository } from "../repositories/product-category"
|
||||||
import { ProductOptionRepository } from "../repositories/product-option"
|
import { ProductOptionRepository } from "../repositories/product-option"
|
||||||
@@ -29,15 +31,16 @@ import { ProductTypeRepository } from "../repositories/product-type"
|
|||||||
import { ProductVariantRepository } from "../repositories/product-variant"
|
import { ProductVariantRepository } from "../repositories/product-variant"
|
||||||
import { Selector } from "../types/common"
|
import { Selector } from "../types/common"
|
||||||
import {
|
import {
|
||||||
CreateProductInput,
|
CreateProductInput,
|
||||||
FilterableProductProps,
|
FilterableProductProps,
|
||||||
FindProductConfig,
|
FindProductConfig,
|
||||||
ProductOptionInput,
|
ProductOptionInput,
|
||||||
ProductSelector,
|
ProductSelector,
|
||||||
UpdateProductInput,
|
UpdateProductInput,
|
||||||
} from "../types/product"
|
} from "../types/product"
|
||||||
import { buildQuery, isString, setMetadata } from "../utils"
|
import { buildQuery, isString, setMetadata } from "../utils"
|
||||||
import EventBusService from "./event-bus"
|
import EventBusService from "./event-bus"
|
||||||
|
import { CreateProductVariantInput } from "../types/product-variant"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
@@ -429,6 +432,7 @@ class ProductService extends TransactionBaseService {
|
|||||||
tags,
|
tags,
|
||||||
type,
|
type,
|
||||||
images,
|
images,
|
||||||
|
variants,
|
||||||
sales_channels: salesChannels,
|
sales_channels: salesChannels,
|
||||||
categories: categories,
|
categories: categories,
|
||||||
...rest
|
...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, {
|
const result = await this.retrieve(product.id, {
|
||||||
relations: ["options"],
|
relations: ["options"],
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in new issue
Block a user