Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for setting sales channel when creating a product #6986

Merged
merged 1 commit into from
Apr 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 23 additions & 58 deletions integration-tests/api/__tests__/admin/product.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const {
createVariantPriceSet,
} = require("../../../modules/helpers/create-variant-price-set")
const { PriceListStatus, PriceListType } = require("@medusajs/types")
const { ContainerRegistrationKeys } = require("@medusajs/utils")

let {
ProductOptionValue,
Expand Down Expand Up @@ -85,12 +84,9 @@ medusaIntegrationTestRunner({
let publishedCollection

let baseType

let baseRegion

let pricingService
let scService
let remoteLink
let container

beforeAll(() => {
Expand Down Expand Up @@ -219,20 +215,10 @@ medusaIntegrationTestRunner({
await api.delete(`/admin/products/${deletedProduct.id}`, adminHeaders)

pricingService = container.resolve(ModuleRegistrationName.PRICING)
scService = container.resolve(ModuleRegistrationName.SALES_CHANNEL)
remoteLink = container.resolve(ContainerRegistrationKeys.REMOTE_LINK)
})

describe("/admin/products", () => {
describe("GET /admin/products", () => {
beforeEach(async () => {
await simpleSalesChannelFactory(dbConnection, {
name: "Default channel",
id: "default-channel",
is_default: true,
})
})

it("returns a list of products with all statuses when no status or invalid status is provided", async () => {
const res = await api
.get("/admin/products", adminHeaders)
Expand Down Expand Up @@ -1057,7 +1043,7 @@ medusaIntegrationTestRunner({
})

it("should return products filtered by sales_channel_id", async () => {
const { salesChannel } = await breaking(
const [productId, salesChannelId] = await breaking(
async () => {
const salesChannel = await simpleSalesChannelFactory(
dbConnection,
Expand All @@ -1068,38 +1054,42 @@ medusaIntegrationTestRunner({
}
)

return { salesChannel }
return [baseProduct.id, salesChannel.id]
},
async () => {
const salesChannel = await scService.create({
name: "Test channel",
description: "Lorem Ipsum",
})

await remoteLink.create({
[Modules.PRODUCT]: {
product_id: baseProduct.id,
},
[Modules.SALES_CHANNEL]: {
sales_channel_id: salesChannel.id,
},
})
const salesChannel = await simpleSalesChannelFactory(
dbConnection,
{
name: "test name",
description: "test description",
}
)

return { salesChannel }
// Currently the product update doesn't support managing sales channels
const newProduct = (
await api.post(
"/admin/products",
getProductFixture({
title: "Test saleschannel",
sales_channels: [{ id: salesChannel.id }],
}),
adminHeaders
)
).data.product
return [newProduct.id, salesChannel.id]
}
)

const res = await api.get(
`/admin/products?sales_channel_id[]=${salesChannel.id}`,
`/admin/products?sales_channel_id[]=${salesChannelId}`,
adminHeaders
)

expect(res.status).toEqual(200)
expect(res.data.products.length).toEqual(1)
expect(res.data.products).toEqual([
expect.objectContaining({
id: baseProduct.id,
status: "draft",
id: productId,
}),
])
})
Expand Down Expand Up @@ -1216,14 +1206,6 @@ medusaIntegrationTestRunner({
})

describe("POST /admin/products", () => {
beforeEach(async () => {
await simpleSalesChannelFactory(dbConnection, {
name: "Default channel",
id: "default-channel",
is_default: true,
})
})

it("creates a product", async () => {
const response = await api
.post(
Expand Down Expand Up @@ -1957,15 +1939,6 @@ medusaIntegrationTestRunner({
})

describe("updates a variant's default prices (ignores prices associated with a Price List)", () => {
beforeEach(async () => {
// await priceListSeeder(dbConnection)
await simpleSalesChannelFactory(dbConnection, {
name: "Default channel",
id: "default-channel",
is_default: true,
})
})

it("successfully updates a variant's default prices by changing an existing price (currency_code)", async () => {
const data = {
prices: [
Expand Down Expand Up @@ -2843,14 +2816,6 @@ medusaIntegrationTestRunner({

// TODO: Discuss how this should be handled
describe.skip("GET /admin/products/tag-usage", () => {
beforeEach(async () => {
await simpleSalesChannelFactory(dbConnection, {
name: "Default channel",
id: "default-channel",
is_default: true,
})
})

it("successfully gets the tags usage", async () => {
const res = await api
.get("/admin/products/tag-usage", adminHeaders)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const removeRemoteLinkStep = createStep(
)
await link.delete(grouped)

return new StepResponse(void 0, grouped)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a typing issue where if you return void 0 you cannot do .config on the step to change the name

return new StepResponse(grouped, grouped)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
return new StepResponse(grouped, grouped)
return new StepResponse(grouped)

},
async (removedLinks, { container }) => {
if (!removedLinks) {
Expand Down
1 change: 0 additions & 1 deletion packages/core-flows/src/product/steps/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ export * from "./update-products"
export * from "./delete-products"
export * from "./get-products"
export * from "./create-variant-pricing-link"
export * from "./remove-variant-pricing-link"
export * from "./create-product-options"
export * from "./update-product-options"
export * from "./delete-product-options"
Expand Down

This file was deleted.

42 changes: 22 additions & 20 deletions packages/core-flows/src/product/workflows/create-products.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import {
} from "@medusajs/workflows-sdk"
import { createProductsStep, createVariantPricingLinkStep } from "../steps"
import { createPriceSetsStep } from "../../pricing"
import { associateProductsWithSalesChannelsStep } from "../../sales-channel"

// TODO: We should have separate types here as input, not the module DTO. Eg. the HTTP request that we are handling
// has different data than the DTO, so that needs to be represented differently.
type WorkflowInput = {
products: (Omit<ProductTypes.CreateProductDTO, "variants"> & {
sales_channels?: { id: string }[]
variants?: (ProductTypes.CreateProductVariantDTO & {
prices?: PricingTypes.CreateMoneyAmountDTO[]
})[]
Expand All @@ -24,17 +26,34 @@ export const createProductsWorkflow = createWorkflow(
input: WorkflowData<WorkflowInput>
): WorkflowData<ProductTypes.ProductDTO[]> => {
// Passing prices to the product module will fail, we want to keep them for after the product is created.
const productWithoutPrices = transform({ input }, (data) =>
const productWithoutExternalRelations = transform({ input }, (data) =>
data.input.products.map((p) => ({
...p,
sales_channels: undefined,
variants: p.variants?.map((v) => ({
...v,
prices: undefined,
})),
}))
)

const createdProducts = createProductsStep(productWithoutPrices)
const createdProducts = createProductsStep(productWithoutExternalRelations)

const salesChannelLinks = transform({ input, createdProducts }, (data) => {
return data.createdProducts
.map((createdProduct, i) => {
const inputProduct = data.input.products[i]
return (
inputProduct.sales_channels?.map((salesChannel) => ({
sales_channel_id: salesChannel.id,
product_id: createdProduct.id,
})) ?? []
)
})
.flat()
})

associateProductsWithSalesChannelsStep({ links: salesChannelLinks })

// Note: We rely on the same order of input and output when creating products here, ensure this always holds true
const variantsWithAssociatedPrices = transform(
Expand Down Expand Up @@ -83,23 +102,6 @@ export const createProductsWorkflow = createWorkflow(

createVariantPricingLinkStep(variantAndPriceSetLinks)

// TODO: Should we just refetch the products here?
return transform(
{
createdProducts,
variantAndPriceSets,
},
(data) => {
return data.createdProducts.map((product) => ({
...product,
variants: product.variants?.map((variant) => ({
...variant,
price_set: data.variantAndPriceSets.find(
(v) => v.variant.id === variant.id
)?.price_set,
})),
}))
}
)
return createdProducts
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we refetch after a workflow, I think it's better to not bother and reconstruct the data for now

}
)
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import {
deleteProductVariantsStep,
removeVariantPricingLinkStep,
} from "../steps"
import { Modules } from "@medusajs/modules-sdk"
import { deleteProductVariantsStep } from "../steps"
import { removeRemoteLinkStep } from "../../common"

type WorkflowInput = { ids: string[] }

export const deleteProductVariantsWorkflowId = "delete-product-variants"
export const deleteProductVariantsWorkflow = createWorkflow(
deleteProductVariantsWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<void> => {
removeVariantPricingLinkStep({ variant_ids: input.ids })
removeRemoteLinkStep({
[Modules.PRODUCT]: { variant_id: input.ids },
}).config({ name: "remove-variant-link-step" })

return deleteProductVariantsStep(input.ids)
}
)
17 changes: 11 additions & 6 deletions packages/core-flows/src/product/workflows/delete-products.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ import {
createWorkflow,
transform,
} from "@medusajs/workflows-sdk"
import {
deleteProductsStep,
getProductsStep,
removeVariantPricingLinkStep,
} from "../steps"
import { Modules } from "@medusajs/modules-sdk"
import { deleteProductsStep, getProductsStep } from "../steps"
import { removeRemoteLinkStep } from "../../common"

type WorkflowInput = { ids: string[] }

Expand All @@ -22,7 +20,14 @@ export const deleteProductsWorkflow = createWorkflow(
.map((variant) => variant.id)
})

removeVariantPricingLinkStep({ variant_ids: variantsToBeDeleted })
removeRemoteLinkStep({
[Modules.PRODUCT]: { variant_id: variantsToBeDeleted },
}).config({ name: "remove-variant-link-step" })

removeRemoteLinkStep({
[Modules.PRODUCT]: { product_id: input.ids },
}).config({ name: "remove-product-link-step" })

return deleteProductsStep(input.ids)
}
)
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const updateProductsWorkflow = createWorkflow(
input: WorkflowData<WorkflowInput>
): WorkflowData<ProductTypes.ProductDTO[]> => {
// TODO: Delete price sets for removed variants
// TODO Update sales channel links
return updateProductsStep(input)
}
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
links: {
sales_channel_id: string
product_ids: string[]
product_id: string
}[]
}

Expand All @@ -14,25 +14,23 @@ export const associateProductsWithSalesChannelsStepId =
export const associateProductsWithSalesChannelsStep = createStep(
associateProductsWithSalesChannelsStepId,
async (input: StepInput, { container }) => {
const remoteLink = container.resolve(ContainerRegistrationKeys.REMOTE_LINK)
if (!input.links.length) {
return new StepResponse([], [])
}

const links = input.links
.map((link) => {
return link.product_ids.map((id) => {
return {
[Modules.PRODUCT]: {
product_id: id,
},
[Modules.SALES_CHANNEL]: {
sales_channel_id: link.sales_channel_id,
},
}
})
})
.flat()
const remoteLink = container.resolve(ContainerRegistrationKeys.REMOTE_LINK)
const links = input.links.map((link) => {
return {
[Modules.PRODUCT]: {
product_id: link.product_id,
},
[Modules.SALES_CHANNEL]: {
sales_channel_id: link.sales_channel_id,
},
}
})

const createdLinks = await remoteLink.create(links)

return new StepResponse(createdLinks, links)
},
async (links, { container }) => {
Expand Down
Loading
Loading