Skip to content

Commit

Permalink
feat(medusa): variant creation with prices in productservice.create (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
pKorsholm authored Oct 30, 2023
1 parent 397da6c commit 4d52082
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 30 deletions.
5 changes: 5 additions & 0 deletions .changeset/small-eggs-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@medusajs/medusa": patch
---

feat(medusa): variant creation with prices in productservice.create
147 changes: 147 additions & 0 deletions integration-tests/plugins/__tests__/services/product.ts
Original file line number Diff line number Diff line change
@@ -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" }),
],
}),
],
})
)
})
})
})
24 changes: 12 additions & 12 deletions packages/medusa/src/services/__tests__/product.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down Expand Up @@ -117,21 +117,29 @@ 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,
eventBusService,
productCollectionService,
productTagRepository,
productTypeRepository,
productVariantService,
featureFlagRouter: new FlagRouter({}),
})

beforeEach(() => {
jest.clearAllMocks()
})

it("successfully create a product", async () => {
it("should successfully create a product", async () => {
await productService.create({
title: "Suit",
options: [],
Expand All @@ -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)
Expand Down Expand Up @@ -197,10 +195,12 @@ describe("ProductService", () => {
variants: [
{
id: "test1",
options: [],
title: "green",
},
{
id: "test2",
options: [],
title: "blue",
},
],
Expand Down
61 changes: 43 additions & 18 deletions packages/medusa/src/services/product.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import {
buildRelations,
buildSelects, FlagRouter, objectToStringPath
buildRelations,
buildSelects,
FlagRouter,
objectToStringPath,
} from "@medusajs/utils"
import { isDefined, MedusaError } from "medusa-core-utils"
import { EntityManager, In } from "typeorm"
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"
Expand All @@ -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
Expand Down Expand Up @@ -429,6 +432,7 @@ class ProductService extends TransactionBaseService {
tags,
type,
images,
variants,
sales_channels: salesChannels,
categories: categories,
...rest
Expand Down Expand Up @@ -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"],
})
Expand Down

0 comments on commit 4d52082

Please sign in to comment.