feat: Add products to sales channel (#6725)

Depends on #6722
This commit is contained in:
Oli Juhl
2024-03-19 11:24:37 +01:00
committed by GitHub
parent 62d5803b20
commit 3062605bce
8 changed files with 238 additions and 34 deletions

View File

@@ -2,6 +2,7 @@ const { ModuleRegistrationName } = require("@medusajs/modules-sdk")
const { medusaIntegrationTestRunner } = require("medusa-test-utils")
const { createAdminUser } = require("../../../helpers/create-admin-user")
const { breaking } = require("../../../helpers/breaking")
const { ContainerRegistrationKeys } = require("@medusajs/utils")
const adminReqConfig = {
headers: {
@@ -21,6 +22,8 @@ medusaIntegrationTestRunner({
testSuite: ({ dbConnection, getContainer, api }) => {
let container
let salesChannelService
let productService
let remoteQuery
beforeAll(() => {
;({
@@ -41,6 +44,8 @@ medusaIntegrationTestRunner({
salesChannelService = container.resolve(
ModuleRegistrationName.SALES_CHANNEL
)
productService = container.resolve(ModuleRegistrationName.PRODUCT)
remoteQuery = container.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
})
describe("GET /admin/sales-channels/:id", () => {
@@ -687,29 +692,59 @@ medusaIntegrationTestRunner({
})
describe("POST /admin/sales-channels/:id/products/batch", () => {
let salesChannel
let product
let { salesChannel, product } = {}
beforeEach(async () => {
salesChannel = await simpleSalesChannelFactory(dbConnection, {
name: "test name",
description: "test description",
})
product = await simpleProductFactory(dbConnection, {
id: "product_1",
title: "test title",
})
;({ salesChannel, product } = await breaking(
async () => {
const salesChannel = await simpleSalesChannelFactory(dbConnection, {
name: "test name",
description: "test description",
})
const product = await simpleProductFactory(dbConnection, {
id: "product_1",
title: "test title",
})
return { salesChannel, product }
},
async () => {
const salesChannel = await salesChannelService.create({
name: "test name",
description: "test description",
})
const product = await productService.create({
title: "test title",
})
return { salesChannel, product }
}
))
})
it("should add products to a sales channel", async () => {
const payload = {
product_ids: [{ id: product.id }],
product_ids: breaking(
() => [{ id: product.id }],
() => [product.id]
),
}
const response = await api.post(
`/admin/sales-channels/${salesChannel.id}/products/batch`,
payload,
adminReqConfig
const response = await breaking(
async () => {
return await api.post(
`/admin/sales-channels/${salesChannel.id}/products/batch`,
payload,
adminReqConfig
)
},
async () => {
return await api.post(
`/admin/sales-channels/${salesChannel.id}/products/batch/add`,
payload,
adminReqConfig
)
}
)
expect(response.status).toEqual(200)
@@ -725,26 +760,64 @@ medusaIntegrationTestRunner({
})
)
const attachedProduct = await dbConnection.manager.findOne(Product, {
where: { id: product.id },
relations: ["sales_channels"],
})
const attachedProduct = await breaking(
async () => {
return await dbConnection.manager.findOne(Product, {
where: { id: product.id },
relations: ["sales_channels"],
})
},
async () => {
const [product] = await remoteQuery({
products: {
fields: ["id"],
sales_channels: {
fields: ["id", "name", "description", "is_disabled"],
},
},
})
return product
}
)
// + default sales channel
expect(attachedProduct.sales_channels.length).toBe(2)
expect(attachedProduct.sales_channels.length).toBe(
breaking(
() => 2,
() => 1 // Comment: The product factory from v1 adds products to the default channel
)
)
expect(attachedProduct.sales_channels).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
name: "test name",
description: "test description",
is_disabled: false,
}),
expect.objectContaining({
id: expect.any(String),
is_disabled: false,
}),
])
expect.arrayContaining(
breaking(
() => {
return [
expect.objectContaining({
id: expect.any(String),
name: "test name",
description: "test description",
is_disabled: false,
}),
expect.objectContaining({
// Comment: Same as above
id: expect.any(String),
is_disabled: false,
}),
]
},
() => {
return [
expect.objectContaining({
id: expect.any(String),
name: "test name",
description: "test description",
is_disabled: false,
}),
]
}
)
)
)
})
})

View File

@@ -0,0 +1,47 @@
import { Modules } from "@medusajs/modules-sdk"
import { ContainerRegistrationKeys } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
links: {
sales_channel_id: string
product_ids: string[]
}[]
}
export const associateProductsWithSalesChannelsStepId =
"associate-products-with-channels"
export const associateProductsWithSalesChannelsStep = createStep(
associateProductsWithSalesChannelsStepId,
async (input: StepInput, { container }) => {
const remoteLink = container.resolve(ContainerRegistrationKeys.REMOTE_LINK)
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 createdLinks = await remoteLink.create(links)
return new StepResponse(createdLinks, links)
},
async (links, { container }) => {
if (!links) {
return
}
const remoteLink = container.resolve(ContainerRegistrationKeys.REMOTE_LINK)
await remoteLink.dismiss(links)
}
)

View File

@@ -1,3 +1,5 @@
export * from "./associate-products-with-channels"
export * from "./create-sales-channels"
export * from "./update-sales-channels"
export * from "./delete-sales-channels"
export * from "./update-sales-channels"

View File

@@ -0,0 +1,19 @@
import { SalesChannelDTO } from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { associateProductsWithSalesChannelsStep } from "../steps/associate-products-with-channels"
type WorkflowInput = {
data: {
sales_channel_id: string
product_ids: string[]
}[]
}
export const addProductsToSalesChannelsWorkflowId =
"add-products-to-sales-channels"
export const addProductsToSalesChannelsWorkflow = createWorkflow(
addProductsToSalesChannelsWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<SalesChannelDTO[]> => {
return associateProductsWithSalesChannelsStep({ links: input.data })
}
)

View File

@@ -1,3 +1,5 @@
export * from "./add-products-to-sales-channels"
export * from "./create-sales-channels"
export * from "./update-sales-channels"
export * from "./delete-sales-channels"
export * from "./update-sales-channels"

View File

@@ -0,0 +1,49 @@
import { addProductsToSalesChannelsWorkflow } from "@medusajs/core-flows"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../../../../types/routing"
import { defaultAdminSalesChannelFields } from "../../../../query-config"
import { AdminPostSalesChannelsChannelProductsBatchReq } from "../../../../validators"
export const POST = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
const body =
req.validatedBody as AdminPostSalesChannelsChannelProductsBatchReq
const workflowInput = {
data: [
{
sales_channel_id: req.params.id,
product_ids: body.product_ids,
},
],
}
const { errors } = await addProductsToSalesChannelsWorkflow(req.scope).run({
input: workflowInput,
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({
entryPoint: "sales_channels",
variables: { id: req.params.id },
fields: defaultAdminSalesChannelFields,
})
const [sales_channel] = await remoteQuery(queryObject)
res.status(200).json({ sales_channel })
}

View File

@@ -5,6 +5,7 @@ import * as QueryConfig from "./query-config"
import {
AdminGetSalesChannelsParams,
AdminGetSalesChannelsSalesChannelParams,
AdminPostSalesChannelsChannelProductsBatchReq,
AdminPostSalesChannelsReq,
AdminPostSalesChannelsSalesChannelReq,
} from "./validators"
@@ -56,4 +57,9 @@ export const adminSalesChannelRoutesMiddlewares: MiddlewareRoute[] = [
matcher: "/admin/sales-channels/:id",
middlewares: [],
},
{
method: ["POST"],
matcher: "/admin/sales-channels/:id/products/batch/add",
middlewares: [transformBody(AdminPostSalesChannelsChannelProductsBatchReq)],
},
]

View File

@@ -1,6 +1,7 @@
import { OperatorMap } from "@medusajs/types"
import { Type } from "class-transformer"
import {
IsArray,
IsBoolean,
IsNotEmpty,
IsOptional,
@@ -100,3 +101,8 @@ export class AdminPostSalesChannelsSalesChannelReq {
@IsOptional()
metadata?: Record<string, unknown>
}
export class AdminPostSalesChannelsChannelProductsBatchReq {
@IsArray()
product_ids: string[]
}