feat(medusa, medusa-js, medusa-react): Bulk add Products to a SalesChannel (#1833)
This commit is contained in:
1 parent
cdd91974f9
commit
f35ea5156a
12 files changed
+375
-18
No files matched your search
@@ -6,7 +6,6 @@ const { useApi } = require("../../../helpers/use-api")
|
|||||||
const { useDb } = require("../../../helpers/use-db")
|
const { useDb } = require("../../../helpers/use-db")
|
||||||
|
|
||||||
const adminSeeder = require("../../helpers/admin-seeder")
|
const adminSeeder = require("../../helpers/admin-seeder")
|
||||||
|
|
||||||
const {
|
const {
|
||||||
simpleSalesChannelFactory,
|
simpleSalesChannelFactory,
|
||||||
simpleProductFactory,
|
simpleProductFactory,
|
||||||
@@ -744,4 +743,72 @@ describe("sales channels", () => {
|
|||||||
expect(attachedProduct.sales_channels.length).toBe(0)
|
expect(attachedProduct.sales_channels.length).toBe(0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("POST /admin/sales-channels/:id/products/batch", () => {
|
||||||
|
let salesChannel
|
||||||
|
let product
|
||||||
|
|
||||||
|
beforeEach(async() => {
|
||||||
|
try {
|
||||||
|
await adminSeeder(dbConnection)
|
||||||
|
salesChannel = await simpleSalesChannelFactory(dbConnection, {
|
||||||
|
name: "test name",
|
||||||
|
description: "test description",
|
||||||
|
})
|
||||||
|
product = await simpleProductFactory(dbConnection, {
|
||||||
|
id: "product_1",
|
||||||
|
title: "test title",
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
await db.teardown()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should add products to a sales channel", async() => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
product_ids: [{ id: product.id }]
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = await api.post(
|
||||||
|
`/admin/sales-channels/${salesChannel.id}/products/batch`,
|
||||||
|
payload,
|
||||||
|
adminReqConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.sales_channel).toEqual({
|
||||||
|
id: expect.any(String),
|
||||||
|
name: "test name",
|
||||||
|
description: "test description",
|
||||||
|
is_disabled: false,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
deleted_at: null,
|
||||||
|
})
|
||||||
|
|
||||||
|
let attachedProduct = await dbConnection.manager.findOne(Product, {
|
||||||
|
where: { id: product.id },
|
||||||
|
relations: ["sales_channels"]
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(attachedProduct.sales_channels.length).toBe(1)
|
||||||
|
expect(attachedProduct.sales_channels).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
name: "test name",
|
||||||
|
description: "test description",
|
||||||
|
is_disabled: false,
|
||||||
|
})
|
||||||
|
])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
AdminSalesChannelsDeleteRes,
|
AdminSalesChannelsDeleteRes,
|
||||||
AdminSalesChannelsListRes,
|
AdminSalesChannelsListRes,
|
||||||
AdminDeleteSalesChannelsChannelProductsBatchReq,
|
AdminDeleteSalesChannelsChannelProductsBatchReq,
|
||||||
|
AdminPostSalesChannelsChannelProductsBatchReq,
|
||||||
} from "@medusajs/medusa"
|
} from "@medusajs/medusa"
|
||||||
import { ResponsePromise } from "../../typings"
|
import { ResponsePromise } from "../../typings"
|
||||||
import BaseResource from "../base"
|
import BaseResource from "../base"
|
||||||
@@ -104,6 +105,22 @@ class AdminSalesChannelsResource extends BaseResource {
|
|||||||
const path = `/admin/sales-channels/${salesChannelId}/products/batch`
|
const path = `/admin/sales-channels/${salesChannelId}/products/batch`
|
||||||
return this.client.request("DELETE", path, payload, {}, customHeaders)
|
return this.client.request("DELETE", path, payload, {}, customHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add products to a sales channel
|
||||||
|
* @experimental This feature is under development and may change in the future.
|
||||||
|
* To use this feature please enable featureflag `sales_channels` in your medusa backend project.
|
||||||
|
* @description Add products to a sales channel
|
||||||
|
* @returns a medusa sales channel
|
||||||
|
*/
|
||||||
|
addProducts(
|
||||||
|
salesChannelId: string,
|
||||||
|
payload: AdminPostSalesChannelsChannelProductsBatchReq,
|
||||||
|
customHeaders: Record<string, any> = {}
|
||||||
|
): ResponsePromise<AdminSalesChannelsRes> {
|
||||||
|
const path = `/admin/sales-channels/${salesChannelId}/products/batch`
|
||||||
|
return this.client.request("POST", path, payload, {}, customHeaders)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AdminSalesChannelsResource
|
export default AdminSalesChannelsResource
|
||||||
@@ -1735,4 +1735,13 @@ export const adminHandlers = [
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
rest.post("/admin/sales-channels/:id/products/batch", (req, res, ctx) => {
|
||||||
|
return res(
|
||||||
|
ctx.status(200),
|
||||||
|
ctx.json({
|
||||||
|
sales_channel: fixtures.get("sales_channel"),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}),
|
||||||
]
|
]
|
||||||
@@ -3,7 +3,8 @@ import {
|
|||||||
AdminSalesChannelsRes,
|
AdminSalesChannelsRes,
|
||||||
AdminPostSalesChannelsSalesChannelReq,
|
AdminPostSalesChannelsSalesChannelReq,
|
||||||
AdminSalesChannelsDeleteRes,
|
AdminSalesChannelsDeleteRes,
|
||||||
AdminDeleteSalesChannelsChannelProductsBatchReq
|
AdminDeleteSalesChannelsChannelProductsBatchReq,
|
||||||
|
AdminPostSalesChannelsChannelProductsBatchReq,
|
||||||
} from "@medusajs/medusa"
|
} from "@medusajs/medusa"
|
||||||
import { Response } from "@medusajs/medusa-js"
|
import { Response } from "@medusajs/medusa-js"
|
||||||
import { useMutation, UseMutationOptions, useQueryClient } from "react-query"
|
import { useMutation, UseMutationOptions, useQueryClient } from "react-query"
|
||||||
@@ -118,3 +119,33 @@ export const useAdminDeleteProductsFromSalesChannel = (
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add products to a sales channel
|
||||||
|
* @experimental This feature is under development and may change in the future.
|
||||||
|
* To use this feature please enable featureflag `sales_channels` in your medusa backend project.
|
||||||
|
* @description Add products to a sales channel
|
||||||
|
* @param id
|
||||||
|
* @param options
|
||||||
|
*/
|
||||||
|
export const useAdminAddProductsToSalesChannel = (
|
||||||
|
id: string,
|
||||||
|
options?: UseMutationOptions<
|
||||||
|
Response<AdminSalesChannelsRes>,
|
||||||
|
Error,
|
||||||
|
AdminPostSalesChannelsChannelProductsBatchReq
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
const { client } = useMedusa()
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
return useMutation(
|
||||||
|
(payload: AdminPostSalesChannelsChannelProductsBatchReq) => {
|
||||||
|
return client.admin.salesChannels.addProducts(id, payload)
|
||||||
|
},
|
||||||
|
buildOptions(
|
||||||
|
queryClient,
|
||||||
|
[adminSalesChannelsKeys.lists(), adminSalesChannelsKeys.detail(id)],
|
||||||
|
options
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
useAdminCreateSalesChannel,
|
useAdminCreateSalesChannel,
|
||||||
useAdminUpdateSalesChannel,
|
useAdminUpdateSalesChannel,
|
||||||
useAdminDeleteProductsFromSalesChannel,
|
useAdminDeleteProductsFromSalesChannel,
|
||||||
|
useAdminAddProductsToSalesChannel,
|
||||||
} from "../../../../src"
|
} from "../../../../src"
|
||||||
import { fixtures } from "../../../../mocks/data"
|
import { fixtures } from "../../../../mocks/data"
|
||||||
import { createWrapper } from "../../../utils"
|
import { createWrapper } from "../../../utils"
|
||||||
@@ -107,3 +108,25 @@ describe("useAdminDeleteProductsFromSalesChannel hook", () => {
|
|||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("useAdminAddProductsToSalesChannel hook", () => {
|
||||||
|
test("add products to a sales channel", async () => {
|
||||||
|
const id = fixtures.get("sales_channel").id
|
||||||
|
const productId = fixtures.get("product").id
|
||||||
|
|
||||||
|
const { result, waitFor } = renderHook(
|
||||||
|
() => useAdminAddProductsToSalesChannel(id),
|
||||||
|
{ wrapper: createWrapper() }
|
||||||
|
)
|
||||||
|
|
||||||
|
result.current.mutate({ product_ids: [
|
||||||
|
{ id: productId }
|
||||||
|
]})
|
||||||
|
|
||||||
|
await waitFor(() => result.current.isSuccess)
|
||||||
|
|
||||||
|
expect(result.current.data).toEqual(expect.objectContaining({
|
||||||
|
sales_channel: fixtures.get("sales_channel"),
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { SalesChannelServiceMock } from "../../../../../services/__mocks__/sales-channel"
|
||||||
|
|
||||||
|
describe("POST /admin/sales-channels/:id/products/batch", () => {
|
||||||
|
describe("add product to a sales channel", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"POST",
|
||||||
|
`/admin/sales-channels/${IdMap.getId("sales_channel_1")}/products/batch`,
|
||||||
|
{
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
product_ids: [{ id: IdMap.getId("sales_channel_1_product_1") }]
|
||||||
|
},
|
||||||
|
flags: ["sales_channels"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls the retrieve method from the sales channel service", () => {
|
||||||
|
expect(SalesChannelServiceMock.addProducts).toHaveBeenCalledTimes(1)
|
||||||
|
expect(SalesChannelServiceMock.addProducts).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("sales_channel_1"),
|
||||||
|
[IdMap.getId("sales_channel_1_product_1")]
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { Request, Response } from "express"
|
||||||
|
import { SalesChannelService } from "../../../../services"
|
||||||
|
import { IsArray, ValidateNested } from "class-validator"
|
||||||
|
import { Type } from "class-transformer"
|
||||||
|
import { ProductBatchSalesChannel } from "../../../../types/sales-channels"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @oas [post] /sales-channels/{id}/products/batch
|
||||||
|
* operationId: "PostSalesChannelsChannelProductsBatch"
|
||||||
|
* summary: "Assign a batch of product to a sales channel"
|
||||||
|
* description: "Assign a batch of product to a sales channel."
|
||||||
|
* x-authenticated: true
|
||||||
|
* parameters:
|
||||||
|
* - (path) id=* {string} The id of the Sales channel.
|
||||||
|
* - (body) product_ids=* {ProductBatchSalesChannel} The product ids that must be assigned to the sales channel.
|
||||||
|
* tags:
|
||||||
|
* - Sales Channel
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* properties:
|
||||||
|
* sales_channel:
|
||||||
|
* $ref: "#/components/schemas/sales_channel"
|
||||||
|
*/
|
||||||
|
export default async (req: Request, res: Response): Promise<void> => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const salesChannelService: SalesChannelService = req.scope.resolve(
|
||||||
|
"salesChannelService"
|
||||||
|
)
|
||||||
|
|
||||||
|
const validatedBody =
|
||||||
|
req.validatedBody as AdminPostSalesChannelsChannelProductsBatchReq
|
||||||
|
const salesChannel = await salesChannelService.addProducts(
|
||||||
|
id,
|
||||||
|
validatedBody.product_ids.map((p) => p.id)
|
||||||
|
)
|
||||||
|
res.status(200).json({ sales_channel: salesChannel })
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdminPostSalesChannelsChannelProductsBatchReq {
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => ProductBatchSalesChannel)
|
||||||
|
product_ids: ProductBatchSalesChannel[]
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import { AdminPostSalesChannelsSalesChannelReq } from "./update-sales-channel"
|
|||||||
import { AdminPostSalesChannelsReq } from "./create-sales-channel"
|
import { AdminPostSalesChannelsReq } from "./create-sales-channel"
|
||||||
import { AdminGetSalesChannelsParams } from "./list-sales-channels"
|
import { AdminGetSalesChannelsParams } from "./list-sales-channels"
|
||||||
import { AdminDeleteSalesChannelsChannelProductsBatchReq } from "./delete-products-batch"
|
import { AdminDeleteSalesChannelsChannelProductsBatchReq } from "./delete-products-batch"
|
||||||
|
import { AdminPostSalesChannelsChannelProductsBatchReq } from "./add-product-batch"
|
||||||
|
|
||||||
const route = Router()
|
const route = Router()
|
||||||
|
|
||||||
@@ -32,6 +33,11 @@ export default (app) => {
|
|||||||
"/",
|
"/",
|
||||||
middlewares.wrap(require("./get-sales-channel").default)
|
middlewares.wrap(require("./get-sales-channel").default)
|
||||||
)
|
)
|
||||||
|
salesChannelRouter.post(
|
||||||
|
"/",
|
||||||
|
transformBody(AdminPostSalesChannelsSalesChannelReq),
|
||||||
|
middlewares.wrap(require("./update-sales-channel").default)
|
||||||
|
)
|
||||||
salesChannelRouter.delete(
|
salesChannelRouter.delete(
|
||||||
"/",
|
"/",
|
||||||
middlewares.wrap(require("./delete-sales-channel").default)
|
middlewares.wrap(require("./delete-sales-channel").default)
|
||||||
@@ -46,6 +52,11 @@ export default (app) => {
|
|||||||
transformBody(AdminDeleteSalesChannelsChannelProductsBatchReq),
|
transformBody(AdminDeleteSalesChannelsChannelProductsBatchReq),
|
||||||
middlewares.wrap(require("./delete-products-batch").default)
|
middlewares.wrap(require("./delete-products-batch").default)
|
||||||
)
|
)
|
||||||
|
salesChannelRouter.post(
|
||||||
|
"/products/batch",
|
||||||
|
transformBody(AdminPostSalesChannelsChannelProductsBatchReq),
|
||||||
|
middlewares.wrap(require("./add-product-batch").default)
|
||||||
|
)
|
||||||
|
|
||||||
route.post(
|
route.post(
|
||||||
"/",
|
"/",
|
||||||
@@ -53,12 +64,6 @@ export default (app) => {
|
|||||||
middlewares.wrap(require("./create-sales-channel").default)
|
middlewares.wrap(require("./create-sales-channel").default)
|
||||||
)
|
)
|
||||||
|
|
||||||
route.post(
|
|
||||||
"/:id",
|
|
||||||
transformBody(AdminPostSalesChannelsSalesChannelReq),
|
|
||||||
middlewares.wrap(require("./update-sales-channel").default)
|
|
||||||
)
|
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,10 +73,6 @@ export type AdminSalesChannelsRes = {
|
|||||||
|
|
||||||
export type AdminSalesChannelsDeleteRes = DeleteResponse
|
export type AdminSalesChannelsDeleteRes = DeleteResponse
|
||||||
|
|
||||||
export type AdminSalesChannelListRes = PaginatedResponse & {
|
|
||||||
sales_channels: SalesChannel[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AdminSalesChannelsListRes = PaginatedResponse & {
|
export type AdminSalesChannelsListRes = PaginatedResponse & {
|
||||||
sales_channels: SalesChannel[]
|
sales_channels: SalesChannel[]
|
||||||
}
|
}
|
||||||
@@ -82,3 +83,4 @@ export * from "./list-sales-channels"
|
|||||||
export * from "./update-sales-channel"
|
export * from "./update-sales-channel"
|
||||||
export * from "./delete-sales-channel"
|
export * from "./delete-sales-channel"
|
||||||
export * from "./delete-products-batch"
|
export * from "./delete-products-batch"
|
||||||
|
export * from "./add-product-batch"
|
||||||
@@ -44,4 +44,21 @@ export class SalesChannelRepository extends Repository<SalesChannel> {
|
|||||||
})
|
})
|
||||||
.execute()
|
.execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addProducts(
|
||||||
|
salesChannelId: string,
|
||||||
|
productIds: string[]
|
||||||
|
): Promise<void> {
|
||||||
|
await this.createQueryBuilder()
|
||||||
|
.insert()
|
||||||
|
.into("product_sales_channel")
|
||||||
|
.values(
|
||||||
|
productIds.map((id) => ({
|
||||||
|
sales_channel_id: salesChannelId,
|
||||||
|
product_id: id,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
.orIgnore()
|
||||||
|
.execute()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -51,6 +51,10 @@ export const SalesChannelServiceMock = {
|
|||||||
removeProducts: jest.fn().mockImplementation((id, productIds) => {
|
removeProducts: jest.fn().mockImplementation((id, productIds) => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
addProducts: jest.fn().mockImplementation((id, productIds) => {
|
||||||
|
return Promise.resolve()
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
const mock = jest.fn().mockImplementation(() => {
|
const mock = jest.fn().mockImplementation(() => {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
||||||
import { FindConditions, FindOneOptions } from "typeorm"
|
|
||||||
import { SalesChannel } from "../../models"
|
|
||||||
import { EventBusService, StoreService } from "../index"
|
|
||||||
import SalesChannelService from "../sales-channel"
|
import SalesChannelService from "../sales-channel"
|
||||||
import { EventBusServiceMock } from "../__mocks__/event-bus"
|
import { EventBusServiceMock } from "../__mocks__/event-bus"
|
||||||
import { store, StoreServiceMock } from "../__mocks__/store"
|
import { EventBusService, ProductService, StoreService } from "../index"
|
||||||
|
import { FindConditions, FindOneOptions } from "typeorm"
|
||||||
|
import { SalesChannel } from "../../models"
|
||||||
|
import { ProductServiceMock } from "../__mocks__/product";
|
||||||
|
import { store, StoreServiceMock } from "../__mocks__/store";
|
||||||
|
|
||||||
describe("SalesChannelService", () => {
|
describe("SalesChannelService", () => {
|
||||||
const salesChannelData = {
|
const salesChannelData = {
|
||||||
@@ -15,7 +16,10 @@ describe("SalesChannelService", () => {
|
|||||||
|
|
||||||
const salesChannelRepositoryMock = {
|
const salesChannelRepositoryMock = {
|
||||||
...MockRepository({
|
...MockRepository({
|
||||||
findOne: jest.fn().mockImplementation((queryOrId: string | FindOneOptions<SalesChannel>): any => {
|
findOne: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(
|
||||||
|
(queryOrId: string | FindOneOptions<SalesChannel>): any => {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
id:
|
id:
|
||||||
typeof queryOrId === "string"
|
typeof queryOrId === "string"
|
||||||
@@ -49,9 +53,17 @@ describe("SalesChannelService", () => {
|
|||||||
id: IdMap.getId("sales_channel_1"),
|
id: IdMap.getId("sales_channel_1"),
|
||||||
...salesChannelData
|
...salesChannelData
|
||||||
},
|
},
|
||||||
]),
|
])
|
||||||
),
|
),
|
||||||
removeProducts: jest.fn().mockImplementation((id: string, productIds: string[]): any => {
|
removeProducts: jest.fn().mockImplementation((id: string, productIds: string[]): any => {
|
||||||
|
Promise.resolve([
|
||||||
|
{
|
||||||
|
id: IdMap.getId("sales_channel_1"),
|
||||||
|
...salesChannelData
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
addProducts: jest.fn().mockImplementation((id: string, productIds: string[]): any => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@@ -62,6 +74,7 @@ describe("SalesChannelService", () => {
|
|||||||
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
||||||
salesChannelRepository: salesChannelRepositoryMock,
|
salesChannelRepository: salesChannelRepositoryMock,
|
||||||
storeService: StoreServiceMock as unknown as StoreService,
|
storeService: StoreServiceMock as unknown as StoreService,
|
||||||
|
productService: ProductServiceMock as unknown as ProductService,
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -84,6 +97,7 @@ describe("SalesChannelService", () => {
|
|||||||
manager: MockManager,
|
manager: MockManager,
|
||||||
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
||||||
salesChannelRepository: salesChannelRepositoryMock,
|
salesChannelRepository: salesChannelRepositoryMock,
|
||||||
|
productService: ProductServiceMock as unknown as ProductService,
|
||||||
storeService: {
|
storeService: {
|
||||||
...StoreServiceMock,
|
...StoreServiceMock,
|
||||||
retrieve: jest.fn().mockImplementation(() => {
|
retrieve: jest.fn().mockImplementation(() => {
|
||||||
@@ -116,6 +130,7 @@ describe("SalesChannelService", () => {
|
|||||||
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
||||||
salesChannelRepository: salesChannelRepositoryMock,
|
salesChannelRepository: salesChannelRepositoryMock,
|
||||||
storeService: StoreServiceMock as unknown as StoreService,
|
storeService: StoreServiceMock as unknown as StoreService,
|
||||||
|
productService: ProductServiceMock as unknown as ProductService,
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -146,6 +161,7 @@ describe("SalesChannelService", () => {
|
|||||||
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
||||||
salesChannelRepository: salesChannelRepositoryMock,
|
salesChannelRepository: salesChannelRepositoryMock,
|
||||||
storeService: StoreServiceMock as unknown as StoreService,
|
storeService: StoreServiceMock as unknown as StoreService,
|
||||||
|
productService: ProductServiceMock as unknown as ProductService,
|
||||||
})
|
})
|
||||||
|
|
||||||
const update = {
|
const update = {
|
||||||
@@ -181,6 +197,7 @@ describe("SalesChannelService", () => {
|
|||||||
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
||||||
salesChannelRepository: salesChannelRepositoryMock,
|
salesChannelRepository: salesChannelRepositoryMock,
|
||||||
storeService: StoreServiceMock as unknown as StoreService,
|
storeService: StoreServiceMock as unknown as StoreService,
|
||||||
|
productService: ProductServiceMock as unknown as ProductService,
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -244,6 +261,7 @@ describe("SalesChannelService", () => {
|
|||||||
manager: MockManager,
|
manager: MockManager,
|
||||||
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
||||||
salesChannelRepository: salesChannelRepositoryMock,
|
salesChannelRepository: salesChannelRepositoryMock,
|
||||||
|
productService: ProductServiceMock as unknown as ProductService,
|
||||||
storeService: {
|
storeService: {
|
||||||
...StoreServiceMock,
|
...StoreServiceMock,
|
||||||
retrieve: jest.fn().mockImplementation(() => {
|
retrieve: jest.fn().mockImplementation(() => {
|
||||||
@@ -299,6 +317,7 @@ describe("SalesChannelService", () => {
|
|||||||
manager: MockManager,
|
manager: MockManager,
|
||||||
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
||||||
salesChannelRepository: salesChannelRepositoryMock,
|
salesChannelRepository: salesChannelRepositoryMock,
|
||||||
|
productService: ProductServiceMock as unknown as ProductService,
|
||||||
storeService: StoreServiceMock as unknown as StoreService,
|
storeService: StoreServiceMock as unknown as StoreService,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -324,4 +343,36 @@ describe("SalesChannelService", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("Add products", () => {
|
||||||
|
const salesChannelService = new SalesChannelService({
|
||||||
|
manager: MockManager,
|
||||||
|
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
||||||
|
salesChannelRepository: salesChannelRepositoryMock,
|
||||||
|
storeService: StoreServiceMock as unknown as StoreService,
|
||||||
|
productService: ProductServiceMock as unknown as ProductService,
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should add a list of product to a sales channel', async () => {
|
||||||
|
const salesChannel = await salesChannelService.addProducts(
|
||||||
|
IdMap.getId("sales_channel_1"),
|
||||||
|
[IdMap.getId("sales_channel_1_product_1")]
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(salesChannelRepositoryMock.addProducts).toHaveBeenCalledTimes(1)
|
||||||
|
expect(salesChannelRepositoryMock.addProducts).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("sales_channel_1"),
|
||||||
|
[IdMap.getId("sales_channel_1_product_1")]
|
||||||
|
)
|
||||||
|
expect(salesChannel).toBeTruthy()
|
||||||
|
expect(salesChannel).toEqual({
|
||||||
|
id: IdMap.getId("sales_channel_1"),
|
||||||
|
...salesChannelData,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
@@ -12,12 +12,15 @@ import {
|
|||||||
import { buildQuery } from "../utils"
|
import { buildQuery } from "../utils"
|
||||||
import EventBusService from "./event-bus"
|
import EventBusService from "./event-bus"
|
||||||
import StoreService from "./store"
|
import StoreService from "./store"
|
||||||
|
import { formatException, PostgresError } from "../utils/exception-formatter"
|
||||||
|
import ProductService from "./product"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
salesChannelRepository: typeof SalesChannelRepository
|
salesChannelRepository: typeof SalesChannelRepository
|
||||||
eventBusService: EventBusService
|
eventBusService: EventBusService
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
storeService: StoreService
|
storeService: StoreService
|
||||||
|
productService: ProductService
|
||||||
}
|
}
|
||||||
|
|
||||||
class SalesChannelService extends TransactionBaseService<SalesChannelService> {
|
class SalesChannelService extends TransactionBaseService<SalesChannelService> {
|
||||||
@@ -33,12 +36,14 @@ class SalesChannelService extends TransactionBaseService<SalesChannelService> {
|
|||||||
protected readonly salesChannelRepository_: typeof SalesChannelRepository
|
protected readonly salesChannelRepository_: typeof SalesChannelRepository
|
||||||
protected readonly eventBusService_: EventBusService
|
protected readonly eventBusService_: EventBusService
|
||||||
protected readonly storeService_: StoreService
|
protected readonly storeService_: StoreService
|
||||||
|
protected readonly productService_: ProductService
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
salesChannelRepository,
|
salesChannelRepository,
|
||||||
eventBusService,
|
eventBusService,
|
||||||
manager,
|
manager,
|
||||||
storeService,
|
storeService,
|
||||||
|
productService,
|
||||||
}: InjectedDependencies) {
|
}: InjectedDependencies) {
|
||||||
// eslint-disable-next-line prefer-rest-params
|
// eslint-disable-next-line prefer-rest-params
|
||||||
super(arguments[0])
|
super(arguments[0])
|
||||||
@@ -47,6 +52,7 @@ class SalesChannelService extends TransactionBaseService<SalesChannelService> {
|
|||||||
this.salesChannelRepository_ = salesChannelRepository
|
this.salesChannelRepository_ = salesChannelRepository
|
||||||
this.eventBusService_ = eventBusService
|
this.eventBusService_ = eventBusService
|
||||||
this.storeService_ = storeService
|
this.storeService_ = storeService
|
||||||
|
this.productService_ = productService
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -265,6 +271,48 @@ class SalesChannelService extends TransactionBaseService<SalesChannelService> {
|
|||||||
return await this.retrieve(salesChannelId)
|
return await this.retrieve(salesChannelId)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a batch of product to a sales channel
|
||||||
|
* @param salesChannelId - The id of the sales channel on which to add the products
|
||||||
|
* @param productIds - The products ids to attach to the sales channel
|
||||||
|
* @return the sales channel on which the products have been added
|
||||||
|
*/
|
||||||
|
async addProducts(
|
||||||
|
salesChannelId: string,
|
||||||
|
productIds: string[]
|
||||||
|
): Promise<SalesChannel | never> {
|
||||||
|
return await this.atomicPhase_(
|
||||||
|
async (transactionManager) => {
|
||||||
|
const salesChannelRepo = transactionManager.getCustomRepository(
|
||||||
|
this.salesChannelRepository_
|
||||||
|
)
|
||||||
|
|
||||||
|
await salesChannelRepo.addProducts(salesChannelId, productIds)
|
||||||
|
|
||||||
|
return await this.retrieve(salesChannelId)
|
||||||
|
},
|
||||||
|
async (error: { code: string }) => {
|
||||||
|
if (error.code === PostgresError.FOREIGN_KEY_ERROR) {
|
||||||
|
const existingProducts = await this.productService_.list({
|
||||||
|
id: productIds,
|
||||||
|
})
|
||||||
|
|
||||||
|
const nonExistingProducts = productIds.filter(
|
||||||
|
(cId) => existingProducts.findIndex((el) => el.id === cId) === -1
|
||||||
|
)
|
||||||
|
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`The following product ids do not exist: ${JSON.stringify(
|
||||||
|
nonExistingProducts.join(", ")
|
||||||
|
)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
throw formatException(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SalesChannelService
|
export default SalesChannelService
|
||||||
Reference in new issue
Block a user