feat: Add batch method to collection and clean up some batch implementations (#7102)
This commit is contained in:
@@ -2844,7 +2844,7 @@ medusaIntegrationTestRunner({
|
||||
}
|
||||
|
||||
const response = await api.post(
|
||||
"/admin/products/op/batch",
|
||||
"/admin/products/batch",
|
||||
{
|
||||
create: [createPayload],
|
||||
update: [updatePayload],
|
||||
@@ -2952,7 +2952,7 @@ medusaIntegrationTestRunner({
|
||||
}
|
||||
|
||||
const response = await api.post(
|
||||
`/admin/products/${createdProduct.id}/variants/op/batch`,
|
||||
`/admin/products/${createdProduct.id}/variants/batch`,
|
||||
{
|
||||
create: [createPayload],
|
||||
update: [updatePayload],
|
||||
@@ -2983,6 +2983,40 @@ medusaIntegrationTestRunner({
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("successfully adds and removes products to a collection", async () => {
|
||||
await breaking(
|
||||
() => {},
|
||||
async () => {
|
||||
const response = await api.post(
|
||||
`/admin/collections/${baseCollection.id}/products`,
|
||||
{
|
||||
add: [publishedProduct.id],
|
||||
remove: [baseProduct.id],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.added).toHaveLength(1)
|
||||
expect(response.data.removed).toHaveLength(1)
|
||||
|
||||
const collection = (
|
||||
await api.get(
|
||||
`/admin/collections/${baseCollection.id}?fields=*products`,
|
||||
adminHeaders
|
||||
)
|
||||
).data.collection
|
||||
|
||||
expect(collection.products).toHaveLength(1)
|
||||
expect(collection.products[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
id: publishedProduct.id,
|
||||
})
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
// TODO: Discuss how this should be handled
|
||||
|
||||
@@ -334,7 +334,7 @@ medusaIntegrationTestRunner({
|
||||
|
||||
it("should delete an inventory location level and create a new one", async () => {
|
||||
const result = await api.post(
|
||||
`/admin/inventory-items/${inventoryItem.id}/location-levels/op/batch`,
|
||||
`/admin/inventory-items/${inventoryItem.id}/location-levels/batch`,
|
||||
{
|
||||
create: [
|
||||
{
|
||||
|
||||
@@ -618,23 +618,21 @@ medusaIntegrationTestRunner({
|
||||
{ relations: ["prices"] }
|
||||
)
|
||||
|
||||
const data = { product_id: [product.id] }
|
||||
const data = { remove: [product.id] }
|
||||
const response = await api.post(
|
||||
`admin/price-lists/${priceList.id}/prices/batch`,
|
||||
`admin/price-lists/${priceList.id}/products`,
|
||||
data,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data).toEqual({
|
||||
created: [],
|
||||
updated: [],
|
||||
deleted: {
|
||||
ids: ["price-to-delete-1", "price-to-delete-2"],
|
||||
object: "price",
|
||||
deleted: true,
|
||||
},
|
||||
})
|
||||
expect(response.data.price_list).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
title: "test price list",
|
||||
description: "test",
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { BatchLinkProductsToCollectionDTO } from "@medusajs/types/src"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
export const batchLinkProductsToCollectionStepId =
|
||||
"batch-link-products-to-collection"
|
||||
export const batchLinkProductsToCollectionStep = createStep(
|
||||
batchLinkProductsToCollectionStepId,
|
||||
async (data: BatchLinkProductsToCollectionDTO, { container }) => {
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
if (!data.add?.length && !data.remove?.length) {
|
||||
return new StepResponse(void 0, null)
|
||||
}
|
||||
|
||||
const dbCollection = await service.retrieveCollection(data.id, {
|
||||
take: null,
|
||||
select: ["id", "products.id"],
|
||||
relations: ["products"],
|
||||
})
|
||||
const existingProductIds = dbCollection.products?.map((p) => p.id) ?? []
|
||||
const toRemoveMap = new Map(data.remove?.map((id) => [id, true]) ?? [])
|
||||
|
||||
const newProductIds = [
|
||||
...existingProductIds.filter((id) => !toRemoveMap.has(id)),
|
||||
...(data.add ?? []),
|
||||
]
|
||||
|
||||
await service.updateCollections(data.id, {
|
||||
product_ids: newProductIds,
|
||||
})
|
||||
|
||||
return new StepResponse(void 0, {
|
||||
id: data.id,
|
||||
productIds: existingProductIds,
|
||||
})
|
||||
},
|
||||
async (prevData, { container }) => {
|
||||
if (!prevData) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
await service.updateCollections(prevData.id, {
|
||||
product_ids: prevData.productIds,
|
||||
})
|
||||
}
|
||||
)
|
||||
@@ -14,6 +14,7 @@ export * from "./batch-product-variants"
|
||||
export * from "./create-collections"
|
||||
export * from "./update-collections"
|
||||
export * from "./delete-collections"
|
||||
export * from "./batch-link-products-collection"
|
||||
export * from "./create-product-types"
|
||||
export * from "./update-product-types"
|
||||
export * from "./delete-product-types"
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||
import { BatchLinkProductsToCollectionDTO } from "@medusajs/types/src"
|
||||
import { batchLinkProductsToCollectionStep } from "../steps/batch-link-products-collection"
|
||||
|
||||
export const batchLinkProductsToCollectionWorkflowId =
|
||||
"batch-link-products-to-collection"
|
||||
export const batchLinkProductsToCollectionWorkflow = createWorkflow(
|
||||
batchLinkProductsToCollectionWorkflowId,
|
||||
(
|
||||
input: WorkflowData<BatchLinkProductsToCollectionDTO>
|
||||
): WorkflowData<void> => {
|
||||
return batchLinkProductsToCollectionStep(input)
|
||||
}
|
||||
)
|
||||
@@ -12,6 +12,7 @@ export * from "./batch-product-variants"
|
||||
export * from "./create-collections"
|
||||
export * from "./delete-collections"
|
||||
export * from "./update-collections"
|
||||
export * from "./batch-link-products-collection"
|
||||
export * from "./create-product-types"
|
||||
export * from "./delete-product-types"
|
||||
export * from "./update-product-types"
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { batchLinkProductsToCollectionWorkflow } from "@medusajs/core-flows"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../../../types/routing"
|
||||
import { LinkMethodRequest } from "@medusajs/types/src"
|
||||
import { refetchCollection } from "../../helpers"
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<LinkMethodRequest>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const id = req.params.id
|
||||
const { add = [], remove = [] } = req.validatedBody
|
||||
|
||||
const workflow = batchLinkProductsToCollectionWorkflow(req.scope)
|
||||
const { result, errors } = await workflow.run({
|
||||
input: {
|
||||
id,
|
||||
add,
|
||||
remove,
|
||||
},
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
if (Array.isArray(errors) && errors[0]) {
|
||||
throw errors[0].error
|
||||
}
|
||||
|
||||
const collection = await refetchCollection(
|
||||
req.params.id,
|
||||
req.scope,
|
||||
req.remoteQueryConfig.fields
|
||||
)
|
||||
|
||||
res.status(200).json({
|
||||
collection,
|
||||
})
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
AdminUpdateCollection,
|
||||
} from "./validators"
|
||||
import { validateAndTransformBody } from "../../utils/validate-body"
|
||||
import { createLinkBody } from "../../utils/validators"
|
||||
|
||||
export const adminCollectionRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
{
|
||||
@@ -64,5 +65,15 @@ export const adminCollectionRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
matcher: "/admin/collections/:id",
|
||||
middlewares: [],
|
||||
},
|
||||
// TODO: There were two batch methods, they need to be handled
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/collections/:id/products",
|
||||
middlewares: [
|
||||
validateAndTransformBody(createLinkBody()),
|
||||
validateAndTransformQuery(
|
||||
AdminGetCollectionParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import {
|
||||
AdminCreateInventoryLocationLevelType,
|
||||
AdminUpdateInventoryLocationLevelType,
|
||||
} from "../../../../validators"
|
||||
import {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../../../../../types/routing"
|
||||
} from "../../../validators"
|
||||
import { MedusaRequest, MedusaResponse } from "../../../../../../types/routing"
|
||||
|
||||
import { bulkCreateDeleteLevelsWorkflow } from "@medusajs/core-flows"
|
||||
import { BatchMethodRequest } from "@medusajs/types"
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from "./validators"
|
||||
import { validateAndTransformBody } from "../../utils/validate-body"
|
||||
import { createBatchBody } from "../../utils/validators"
|
||||
import { unlessPath } from "../../utils/unless-path"
|
||||
|
||||
export const adminInventoryRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
{
|
||||
@@ -84,30 +85,9 @@ export const adminInventoryRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["DELETE"],
|
||||
matcher: "/admin/inventory-items/:id/location-levels/:location_id",
|
||||
middlewares: [
|
||||
validateAndTransformQuery(
|
||||
AdminGetInventoryItemParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/inventory-items/:id/location-levels/:location_id",
|
||||
middlewares: [
|
||||
validateAndTransformBody(AdminUpdateInventoryLocationLevel),
|
||||
validateAndTransformQuery(
|
||||
AdminGetInventoryItemParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/inventory-items/:id/location-levels/op/batch",
|
||||
matcher: "/admin/inventory-items/:id/location-levels/batch",
|
||||
middlewares: [
|
||||
validateAndTransformBody(
|
||||
createBatchBody(
|
||||
@@ -121,4 +101,34 @@ export const adminInventoryRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["DELETE"],
|
||||
matcher: "/admin/inventory-items/:id/location-levels/:location_id",
|
||||
middlewares: [
|
||||
unlessPath(
|
||||
/.*\/location-levels\/batch/,
|
||||
validateAndTransformQuery(
|
||||
AdminGetInventoryItemParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
)
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/inventory-items/:id/location-levels/:location_id",
|
||||
middlewares: [
|
||||
unlessPath(
|
||||
/.*\/location-levels\/batch/,
|
||||
validateAndTransformBody(AdminUpdateInventoryLocationLevel)
|
||||
),
|
||||
unlessPath(
|
||||
/.*\/location-levels\/batch/,
|
||||
validateAndTransformQuery(
|
||||
AdminGetInventoryItemParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
)
|
||||
),
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
@@ -43,7 +43,7 @@ export const adminPaymentRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
matcher: "/admin/payments/:id",
|
||||
middlewares: [
|
||||
unlessPath(
|
||||
new RegExp("/admin/payments/payment-providers"),
|
||||
/.*\/payments\/payment-providers/,
|
||||
validateAndTransformQuery(
|
||||
AdminGetPaymentParams,
|
||||
queryConfig.retrieveTransformQueryConfig
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
import { batchPriceListPricesWorkflow } from "@medusajs/core-flows"
|
||||
import { promiseAll } from "@medusajs/utils"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../../../../types/routing"
|
||||
import { fetchPriceListPriceIdsForProduct } from "../../../helpers"
|
||||
import { listPrices } from "../../../queries"
|
||||
import { adminPriceListPriceRemoteQueryFields } from "../../../query-config"
|
||||
import { AdminBatchPriceListPricesType } from "../../../validators"
|
||||
import { BatchMethodRequest } from "@medusajs/types"
|
||||
import {
|
||||
AdminCreatePriceListPriceType,
|
||||
AdminUpdatePriceListPriceType,
|
||||
} from "../../../validators"
|
||||
import { batchPriceListPricesWorkflow } from "@medusajs/core-flows"
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<AdminBatchPriceListPricesType>,
|
||||
req: AuthenticatedMedusaRequest<
|
||||
BatchMethodRequest<
|
||||
AdminCreatePriceListPriceType,
|
||||
AdminUpdatePriceListPriceType
|
||||
>
|
||||
>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const id = req.params.id
|
||||
@@ -18,16 +26,8 @@ export const POST = async (
|
||||
create = [],
|
||||
update = [],
|
||||
delete: deletePriceIds = [],
|
||||
product_id: productIds = [],
|
||||
} = req.validatedBody
|
||||
|
||||
const productPriceIds = await fetchPriceListPriceIdsForProduct(
|
||||
id,
|
||||
productIds,
|
||||
req.scope
|
||||
)
|
||||
|
||||
const priceIdsToDelete = [...deletePriceIds, ...productPriceIds]
|
||||
const workflow = batchPriceListPricesWorkflow(req.scope)
|
||||
const { result, errors } = await workflow.run({
|
||||
input: {
|
||||
@@ -35,7 +35,7 @@ export const POST = async (
|
||||
id,
|
||||
create,
|
||||
update,
|
||||
delete: priceIdsToDelete,
|
||||
delete: deletePriceIds,
|
||||
},
|
||||
},
|
||||
throwOnError: false,
|
||||
@@ -62,7 +62,7 @@ export const POST = async (
|
||||
created,
|
||||
updated,
|
||||
deleted: {
|
||||
ids: priceIdsToDelete,
|
||||
ids: deletePriceIds,
|
||||
object: "price",
|
||||
deleted: true,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../../../types/routing"
|
||||
import { batchPriceListPricesWorkflow } from "@medusajs/core-flows"
|
||||
import { LinkMethodRequest } from "@medusajs/types/src"
|
||||
import { fetchPriceList, fetchPriceListPriceIdsForProduct } from "../../helpers"
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<LinkMethodRequest>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const id = req.params.id
|
||||
const { add, remove = [] } = req.validatedBody
|
||||
if (add?.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Adding products directly to a price list is not supported, please use the /admin/price-lists/:id/prices/batch endpoint instead"
|
||||
)
|
||||
}
|
||||
|
||||
if (!remove.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"No product ids passed to remove from price list"
|
||||
)
|
||||
}
|
||||
|
||||
const productPriceIds = await fetchPriceListPriceIdsForProduct(
|
||||
id,
|
||||
remove,
|
||||
req.scope
|
||||
)
|
||||
|
||||
const workflow = batchPriceListPricesWorkflow(req.scope)
|
||||
const { result, errors } = await workflow.run({
|
||||
input: {
|
||||
data: {
|
||||
id,
|
||||
create: [],
|
||||
update: [],
|
||||
delete: productPriceIds,
|
||||
},
|
||||
},
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
if (Array.isArray(errors) && errors[0]) {
|
||||
throw errors[0].error
|
||||
}
|
||||
|
||||
const priceList = await fetchPriceList(
|
||||
id,
|
||||
req.scope,
|
||||
req.remoteQueryConfig.fields
|
||||
)
|
||||
|
||||
res.status(200).json({ price_list: priceList })
|
||||
}
|
||||
@@ -2,14 +2,16 @@ import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
||||
import { authenticate } from "../../../utils/authenticate-middleware"
|
||||
import { validateAndTransformBody } from "../../utils/validate-body"
|
||||
import { validateAndTransformQuery } from "../../utils/validate-query"
|
||||
import { createBatchBody, createLinkBody } from "../../utils/validators"
|
||||
import * as QueryConfig from "./query-config"
|
||||
import {
|
||||
AdminBatchPriceListPrices,
|
||||
AdminCreatePriceList,
|
||||
AdminCreatePriceListPrice,
|
||||
AdminGetPriceListParams,
|
||||
AdminGetPriceListPricesParams,
|
||||
AdminGetPriceListsParams,
|
||||
AdminUpdatePriceList,
|
||||
AdminUpdatePriceListPrice,
|
||||
} from "./validators"
|
||||
|
||||
export const adminPriceListsRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
@@ -60,11 +62,24 @@ export const adminPriceListsRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/price-lists/:id/products",
|
||||
middlewares: [
|
||||
validateAndTransformBody(createLinkBody()),
|
||||
validateAndTransformQuery(
|
||||
AdminGetPriceListParams,
|
||||
QueryConfig.listPriceListQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/price-lists/:id/prices/batch",
|
||||
middlewares: [
|
||||
validateAndTransformBody(AdminBatchPriceListPrices),
|
||||
validateAndTransformBody(
|
||||
createBatchBody(AdminCreatePriceListPrice, AdminUpdatePriceListPrice)
|
||||
),
|
||||
validateAndTransformQuery(
|
||||
AdminGetPriceListPricesParams,
|
||||
QueryConfig.listPriceListPriceQueryConfig
|
||||
|
||||
@@ -40,17 +40,6 @@ export type AdminUpdatePriceListPriceType = z.infer<
|
||||
typeof AdminUpdatePriceListPrice
|
||||
>
|
||||
|
||||
export const AdminBatchPriceListPrices = z.object({
|
||||
create: z.array(AdminCreatePriceListPrice).optional(),
|
||||
update: z.array(AdminUpdatePriceListPrice).optional(),
|
||||
delete: z.array(z.string()).optional(),
|
||||
product_id: z.array(z.string()).optional(),
|
||||
})
|
||||
|
||||
export type AdminBatchPriceListPricesType = z.infer<
|
||||
typeof AdminBatchPriceListPrices
|
||||
>
|
||||
|
||||
export const AdminCreatePriceList = z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
|
||||
@@ -2,13 +2,13 @@ import { batchProductVariantsWorkflow } from "@medusajs/core-flows"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../../../../../types/routing"
|
||||
} from "../../../../../../types/routing"
|
||||
import {
|
||||
AdminBatchUpdateProductVariantType,
|
||||
AdminCreateProductType,
|
||||
} from "../../../../validators"
|
||||
} from "../../../validators"
|
||||
import { BatchMethodRequest } from "@medusajs/types"
|
||||
import { refetchBatchVariants, remapVariantResponse } from "../../../../helpers"
|
||||
import { refetchBatchVariants, remapVariantResponse } from "../../../helpers"
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<
|
||||
@@ -2,14 +2,13 @@ import { batchProductsWorkflow } from "@medusajs/core-flows"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../../../types/routing"
|
||||
} from "../../../../types/routing"
|
||||
import {
|
||||
AdminBatchUpdateProductType,
|
||||
AdminCreateProductType,
|
||||
} from "../../validators"
|
||||
} from "../validators"
|
||||
import { BatchMethodRequest } from "@medusajs/types"
|
||||
import { refetchBatchProducts, remapProductResponse } from "../../helpers"
|
||||
import { CreateProductDTO, UpsertProductDTO } from "@medusajs/types"
|
||||
import { refetchBatchProducts, remapProductResponse } from "../helpers"
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<
|
||||
@@ -1,6 +1,7 @@
|
||||
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
||||
import { authenticate } from "../../../utils/authenticate-middleware"
|
||||
import { maybeApplyLinkFilter } from "../../utils/maybe-apply-link-filter"
|
||||
import { unlessPath } from "../../utils/unless-path"
|
||||
import { validateAndTransformBody } from "../../utils/validate-body"
|
||||
import { validateAndTransformQuery } from "../../utils/validate-query"
|
||||
import { createBatchBody } from "../../utils/validators"
|
||||
@@ -45,16 +46,6 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
maybeApplyPriceListsFilter(),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/admin/products/:id",
|
||||
middlewares: [
|
||||
validateAndTransformQuery(
|
||||
AdminGetProductParams,
|
||||
QueryConfig.retrieveProductQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/products",
|
||||
@@ -68,7 +59,7 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/products/op/batch",
|
||||
matcher: "/admin/products/batch",
|
||||
middlewares: [
|
||||
validateAndTransformBody(
|
||||
createBatchBody(AdminCreateProduct, AdminBatchUpdateProduct)
|
||||
@@ -79,14 +70,33 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/admin/products/:id",
|
||||
middlewares: [
|
||||
unlessPath(
|
||||
/.*\/products\/batch/,
|
||||
validateAndTransformQuery(
|
||||
AdminGetProductParams,
|
||||
QueryConfig.retrieveProductQueryConfig
|
||||
)
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/products/:id",
|
||||
middlewares: [
|
||||
validateAndTransformBody(AdminUpdateProduct),
|
||||
validateAndTransformQuery(
|
||||
AdminGetProductParams,
|
||||
QueryConfig.retrieveProductQueryConfig
|
||||
unlessPath(
|
||||
/.*\/products\/batch/,
|
||||
validateAndTransformBody(AdminUpdateProduct)
|
||||
),
|
||||
unlessPath(
|
||||
/.*\/products\/batch/,
|
||||
validateAndTransformQuery(
|
||||
AdminGetProductParams,
|
||||
QueryConfig.retrieveProductQueryConfig
|
||||
)
|
||||
),
|
||||
],
|
||||
},
|
||||
@@ -94,13 +104,15 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
method: ["DELETE"],
|
||||
matcher: "/admin/products/:id",
|
||||
middlewares: [
|
||||
validateAndTransformQuery(
|
||||
AdminGetProductParams,
|
||||
QueryConfig.retrieveProductQueryConfig
|
||||
unlessPath(
|
||||
/.*\/products\/batch/,
|
||||
validateAndTransformQuery(
|
||||
AdminGetProductParams,
|
||||
QueryConfig.retrieveProductQueryConfig
|
||||
)
|
||||
),
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/admin/products/:id/variants",
|
||||
@@ -113,7 +125,18 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/products/:id/variants/op/batch",
|
||||
matcher: "/admin/products/:id/variants",
|
||||
middlewares: [
|
||||
validateAndTransformBody(AdminCreateProductVariant),
|
||||
validateAndTransformQuery(
|
||||
AdminGetProductParams,
|
||||
QueryConfig.retrieveProductQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/products/:id/variants/batch",
|
||||
middlewares: [
|
||||
validateAndTransformBody(
|
||||
createBatchBody(
|
||||
@@ -132,20 +155,12 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
method: ["GET"],
|
||||
matcher: "/admin/products/:id/variants/:variant_id",
|
||||
middlewares: [
|
||||
validateAndTransformQuery(
|
||||
AdminGetProductVariantParams,
|
||||
QueryConfig.retrieveVariantConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/products/:id/variants",
|
||||
middlewares: [
|
||||
validateAndTransformBody(AdminCreateProductVariant),
|
||||
validateAndTransformQuery(
|
||||
AdminGetProductParams,
|
||||
QueryConfig.retrieveProductQueryConfig
|
||||
unlessPath(
|
||||
/.*\/variants\/batch/,
|
||||
validateAndTransformQuery(
|
||||
AdminGetProductVariantParams,
|
||||
QueryConfig.retrieveVariantConfig
|
||||
)
|
||||
),
|
||||
],
|
||||
},
|
||||
@@ -153,10 +168,16 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
method: ["POST"],
|
||||
matcher: "/admin/products/:id/variants/:variant_id",
|
||||
middlewares: [
|
||||
validateAndTransformBody(AdminUpdateProductVariant),
|
||||
validateAndTransformQuery(
|
||||
AdminGetProductParams,
|
||||
QueryConfig.retrieveProductQueryConfig
|
||||
unlessPath(
|
||||
/.*\/variants\/batch/,
|
||||
validateAndTransformBody(AdminUpdateProductVariant)
|
||||
),
|
||||
unlessPath(
|
||||
/.*\/variants\/batch/,
|
||||
validateAndTransformQuery(
|
||||
AdminGetProductParams,
|
||||
QueryConfig.retrieveProductQueryConfig
|
||||
)
|
||||
),
|
||||
],
|
||||
},
|
||||
@@ -164,9 +185,12 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
method: ["DELETE"],
|
||||
matcher: "/admin/products/:id/variants/:variant_id",
|
||||
middlewares: [
|
||||
validateAndTransformQuery(
|
||||
AdminGetProductParams,
|
||||
QueryConfig.retrieveProductQueryConfig
|
||||
unlessPath(
|
||||
/.*\/variants\/batch/,
|
||||
validateAndTransformQuery(
|
||||
AdminGetProductParams,
|
||||
QueryConfig.retrieveProductQueryConfig
|
||||
)
|
||||
),
|
||||
],
|
||||
},
|
||||
|
||||
@@ -8,7 +8,6 @@ export async function zodValidator<T>(
|
||||
body: T
|
||||
): Promise<z.ZodRawShape> {
|
||||
try {
|
||||
zodSchema
|
||||
return await zodSchema.parseAsync(body)
|
||||
} catch (err) {
|
||||
if (err instanceof ZodError) {
|
||||
|
||||
@@ -11,6 +11,13 @@ export const createBatchBody = (
|
||||
})
|
||||
}
|
||||
|
||||
export const createLinkBody = () => {
|
||||
return z.object({
|
||||
add: z.array(z.string()).optional(),
|
||||
remove: z.array(z.string()).optional(),
|
||||
})
|
||||
}
|
||||
|
||||
export const createSelectParams = () => {
|
||||
return z.object({
|
||||
fields: z.string().optional(),
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
ModulesSdkUtils,
|
||||
ProductStatus,
|
||||
promiseAll,
|
||||
removeUndefined,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
ProductCategoryEventData,
|
||||
@@ -771,6 +772,8 @@ export default class ProductModuleService<
|
||||
ProductModuleService.normalizeCreateProductCollectionInput
|
||||
)
|
||||
|
||||
// It's safe to use upsertWithReplace here since we only have product IDs and the only operation to do is update the product
|
||||
// with the collection ID
|
||||
return await this.productCollectionService_.upsertWithReplace(
|
||||
normalizedInput,
|
||||
{ relations: ["products"] },
|
||||
@@ -906,13 +909,48 @@ export default class ProductModuleService<
|
||||
): Promise<TProductCollection[]> {
|
||||
const normalizedInput = data.map(
|
||||
ProductModuleService.normalizeUpdateProductCollectionInput
|
||||
)
|
||||
) as UpdateCollectionInput[]
|
||||
|
||||
return await this.productCollectionService_.upsertWithReplace(
|
||||
normalizedInput,
|
||||
{ relations: ["products"] },
|
||||
// TODO: Maybe we can update upsertWithReplace to not remove oneToMany entities, but just disassociate them? With that we can remove the code below.
|
||||
// Another alternative is to not allow passing product_ids to a collection, and instead set the collection_id through the product update call.
|
||||
const updatedCollections = await this.productCollectionService_.update(
|
||||
normalizedInput.map((c) =>
|
||||
removeUndefined({ ...c, products: undefined })
|
||||
),
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const collectionWithProducts = await promiseAll(
|
||||
updatedCollections.map(async (collection, i) => {
|
||||
const input = normalizedInput.find((c) => c.id === collection.id)
|
||||
const productsToUpdate = (input as any)?.products
|
||||
if (!productsToUpdate) {
|
||||
return { ...collection, products: [] }
|
||||
}
|
||||
|
||||
await this.productService_.update(
|
||||
{
|
||||
selector: { collection_id: collection.id },
|
||||
data: { collection_id: null },
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
if (productsToUpdate.length > 0) {
|
||||
await this.productService_.update(
|
||||
productsToUpdate.map((p) => ({
|
||||
id: p.id,
|
||||
collection_id: collection.id,
|
||||
})),
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
return { ...collection, products: productsToUpdate }
|
||||
})
|
||||
)
|
||||
|
||||
return collectionWithProducts
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
export type LinkMethodRequest = {
|
||||
add?: string[]
|
||||
remove?: string[]
|
||||
}
|
||||
|
||||
export type BatchMethodRequest<TCreate extends any, TUpdate extends any> = {
|
||||
create?: TCreate[]
|
||||
update?: TUpdate[]
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./stock-locations"
|
||||
export * from "./products"
|
||||
|
||||
1
packages/types/src/workflows/products/index.ts
Normal file
1
packages/types/src/workflows/products/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./mutations"
|
||||
5
packages/types/src/workflows/products/mutations.ts
Normal file
5
packages/types/src/workflows/products/mutations.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { LinkMethodRequest } from "../../common"
|
||||
|
||||
export interface BatchLinkProductsToCollectionDTO extends LinkMethodRequest {
|
||||
id: string
|
||||
}
|
||||
Reference in New Issue
Block a user