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(
|
const response = await api.post(
|
||||||
"/admin/products/op/batch",
|
"/admin/products/batch",
|
||||||
{
|
{
|
||||||
create: [createPayload],
|
create: [createPayload],
|
||||||
update: [updatePayload],
|
update: [updatePayload],
|
||||||
@@ -2952,7 +2952,7 @@ medusaIntegrationTestRunner({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const response = await api.post(
|
const response = await api.post(
|
||||||
`/admin/products/${createdProduct.id}/variants/op/batch`,
|
`/admin/products/${createdProduct.id}/variants/batch`,
|
||||||
{
|
{
|
||||||
create: [createPayload],
|
create: [createPayload],
|
||||||
update: [updatePayload],
|
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
|
// 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 () => {
|
it("should delete an inventory location level and create a new one", async () => {
|
||||||
const result = await api.post(
|
const result = await api.post(
|
||||||
`/admin/inventory-items/${inventoryItem.id}/location-levels/op/batch`,
|
`/admin/inventory-items/${inventoryItem.id}/location-levels/batch`,
|
||||||
{
|
{
|
||||||
create: [
|
create: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -618,23 +618,21 @@ medusaIntegrationTestRunner({
|
|||||||
{ relations: ["prices"] }
|
{ relations: ["prices"] }
|
||||||
)
|
)
|
||||||
|
|
||||||
const data = { product_id: [product.id] }
|
const data = { remove: [product.id] }
|
||||||
const response = await api.post(
|
const response = await api.post(
|
||||||
`admin/price-lists/${priceList.id}/prices/batch`,
|
`admin/price-lists/${priceList.id}/products`,
|
||||||
data,
|
data,
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(response.status).toEqual(200)
|
expect(response.status).toEqual(200)
|
||||||
expect(response.data).toEqual({
|
expect(response.data.price_list).toEqual(
|
||||||
created: [],
|
expect.objectContaining({
|
||||||
updated: [],
|
id: expect.any(String),
|
||||||
deleted: {
|
title: "test price list",
|
||||||
ids: ["price-to-delete-1", "price-to-delete-2"],
|
description: "test",
|
||||||
object: "price",
|
})
|
||||||
deleted: true,
|
)
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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 "./create-collections"
|
||||||
export * from "./update-collections"
|
export * from "./update-collections"
|
||||||
export * from "./delete-collections"
|
export * from "./delete-collections"
|
||||||
|
export * from "./batch-link-products-collection"
|
||||||
export * from "./create-product-types"
|
export * from "./create-product-types"
|
||||||
export * from "./update-product-types"
|
export * from "./update-product-types"
|
||||||
export * from "./delete-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 "./create-collections"
|
||||||
export * from "./delete-collections"
|
export * from "./delete-collections"
|
||||||
export * from "./update-collections"
|
export * from "./update-collections"
|
||||||
|
export * from "./batch-link-products-collection"
|
||||||
export * from "./create-product-types"
|
export * from "./create-product-types"
|
||||||
export * from "./delete-product-types"
|
export * from "./delete-product-types"
|
||||||
export * from "./update-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,
|
AdminUpdateCollection,
|
||||||
} from "./validators"
|
} from "./validators"
|
||||||
import { validateAndTransformBody } from "../../utils/validate-body"
|
import { validateAndTransformBody } from "../../utils/validate-body"
|
||||||
|
import { createLinkBody } from "../../utils/validators"
|
||||||
|
|
||||||
export const adminCollectionRoutesMiddlewares: MiddlewareRoute[] = [
|
export const adminCollectionRoutesMiddlewares: MiddlewareRoute[] = [
|
||||||
{
|
{
|
||||||
@@ -64,5 +65,15 @@ export const adminCollectionRoutesMiddlewares: MiddlewareRoute[] = [
|
|||||||
matcher: "/admin/collections/:id",
|
matcher: "/admin/collections/:id",
|
||||||
middlewares: [],
|
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
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
+2
-5
@@ -1,11 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
AdminCreateInventoryLocationLevelType,
|
AdminCreateInventoryLocationLevelType,
|
||||||
AdminUpdateInventoryLocationLevelType,
|
AdminUpdateInventoryLocationLevelType,
|
||||||
} from "../../../../validators"
|
} from "../../../validators"
|
||||||
import {
|
import { MedusaRequest, MedusaResponse } from "../../../../../../types/routing"
|
||||||
MedusaRequest,
|
|
||||||
MedusaResponse,
|
|
||||||
} from "../../../../../../../types/routing"
|
|
||||||
|
|
||||||
import { bulkCreateDeleteLevelsWorkflow } from "@medusajs/core-flows"
|
import { bulkCreateDeleteLevelsWorkflow } from "@medusajs/core-flows"
|
||||||
import { BatchMethodRequest } from "@medusajs/types"
|
import { BatchMethodRequest } from "@medusajs/types"
|
||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
} from "./validators"
|
} from "./validators"
|
||||||
import { validateAndTransformBody } from "../../utils/validate-body"
|
import { validateAndTransformBody } from "../../utils/validate-body"
|
||||||
import { createBatchBody } from "../../utils/validators"
|
import { createBatchBody } from "../../utils/validators"
|
||||||
|
import { unlessPath } from "../../utils/unless-path"
|
||||||
|
|
||||||
export const adminInventoryRoutesMiddlewares: MiddlewareRoute[] = [
|
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"],
|
method: ["POST"],
|
||||||
matcher: "/admin/inventory-items/:id/location-levels/:location_id",
|
matcher: "/admin/inventory-items/:id/location-levels/batch",
|
||||||
middlewares: [
|
|
||||||
validateAndTransformBody(AdminUpdateInventoryLocationLevel),
|
|
||||||
validateAndTransformQuery(
|
|
||||||
AdminGetInventoryItemParams,
|
|
||||||
QueryConfig.retrieveTransformQueryConfig
|
|
||||||
),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
method: ["POST"],
|
|
||||||
matcher: "/admin/inventory-items/:id/location-levels/op/batch",
|
|
||||||
middlewares: [
|
middlewares: [
|
||||||
validateAndTransformBody(
|
validateAndTransformBody(
|
||||||
createBatchBody(
|
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",
|
matcher: "/admin/payments/:id",
|
||||||
middlewares: [
|
middlewares: [
|
||||||
unlessPath(
|
unlessPath(
|
||||||
new RegExp("/admin/payments/payment-providers"),
|
/.*\/payments\/payment-providers/,
|
||||||
validateAndTransformQuery(
|
validateAndTransformQuery(
|
||||||
AdminGetPaymentParams,
|
AdminGetPaymentParams,
|
||||||
queryConfig.retrieveTransformQueryConfig
|
queryConfig.retrieveTransformQueryConfig
|
||||||
|
|||||||
@@ -1,16 +1,24 @@
|
|||||||
import { batchPriceListPricesWorkflow } from "@medusajs/core-flows"
|
|
||||||
import { promiseAll } from "@medusajs/utils"
|
import { promiseAll } from "@medusajs/utils"
|
||||||
import {
|
import {
|
||||||
AuthenticatedMedusaRequest,
|
AuthenticatedMedusaRequest,
|
||||||
MedusaResponse,
|
MedusaResponse,
|
||||||
} from "../../../../../../types/routing"
|
} from "../../../../../../types/routing"
|
||||||
import { fetchPriceListPriceIdsForProduct } from "../../../helpers"
|
|
||||||
import { listPrices } from "../../../queries"
|
import { listPrices } from "../../../queries"
|
||||||
import { adminPriceListPriceRemoteQueryFields } from "../../../query-config"
|
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 (
|
export const POST = async (
|
||||||
req: AuthenticatedMedusaRequest<AdminBatchPriceListPricesType>,
|
req: AuthenticatedMedusaRequest<
|
||||||
|
BatchMethodRequest<
|
||||||
|
AdminCreatePriceListPriceType,
|
||||||
|
AdminUpdatePriceListPriceType
|
||||||
|
>
|
||||||
|
>,
|
||||||
res: MedusaResponse
|
res: MedusaResponse
|
||||||
) => {
|
) => {
|
||||||
const id = req.params.id
|
const id = req.params.id
|
||||||
@@ -18,16 +26,8 @@ export const POST = async (
|
|||||||
create = [],
|
create = [],
|
||||||
update = [],
|
update = [],
|
||||||
delete: deletePriceIds = [],
|
delete: deletePriceIds = [],
|
||||||
product_id: productIds = [],
|
|
||||||
} = req.validatedBody
|
} = req.validatedBody
|
||||||
|
|
||||||
const productPriceIds = await fetchPriceListPriceIdsForProduct(
|
|
||||||
id,
|
|
||||||
productIds,
|
|
||||||
req.scope
|
|
||||||
)
|
|
||||||
|
|
||||||
const priceIdsToDelete = [...deletePriceIds, ...productPriceIds]
|
|
||||||
const workflow = batchPriceListPricesWorkflow(req.scope)
|
const workflow = batchPriceListPricesWorkflow(req.scope)
|
||||||
const { result, errors } = await workflow.run({
|
const { result, errors } = await workflow.run({
|
||||||
input: {
|
input: {
|
||||||
@@ -35,7 +35,7 @@ export const POST = async (
|
|||||||
id,
|
id,
|
||||||
create,
|
create,
|
||||||
update,
|
update,
|
||||||
delete: priceIdsToDelete,
|
delete: deletePriceIds,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
throwOnError: false,
|
throwOnError: false,
|
||||||
@@ -62,7 +62,7 @@ export const POST = async (
|
|||||||
created,
|
created,
|
||||||
updated,
|
updated,
|
||||||
deleted: {
|
deleted: {
|
||||||
ids: priceIdsToDelete,
|
ids: deletePriceIds,
|
||||||
object: "price",
|
object: "price",
|
||||||
deleted: true,
|
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 { authenticate } from "../../../utils/authenticate-middleware"
|
||||||
import { validateAndTransformBody } from "../../utils/validate-body"
|
import { validateAndTransformBody } from "../../utils/validate-body"
|
||||||
import { validateAndTransformQuery } from "../../utils/validate-query"
|
import { validateAndTransformQuery } from "../../utils/validate-query"
|
||||||
|
import { createBatchBody, createLinkBody } from "../../utils/validators"
|
||||||
import * as QueryConfig from "./query-config"
|
import * as QueryConfig from "./query-config"
|
||||||
import {
|
import {
|
||||||
AdminBatchPriceListPrices,
|
|
||||||
AdminCreatePriceList,
|
AdminCreatePriceList,
|
||||||
|
AdminCreatePriceListPrice,
|
||||||
AdminGetPriceListParams,
|
AdminGetPriceListParams,
|
||||||
AdminGetPriceListPricesParams,
|
AdminGetPriceListPricesParams,
|
||||||
AdminGetPriceListsParams,
|
AdminGetPriceListsParams,
|
||||||
AdminUpdatePriceList,
|
AdminUpdatePriceList,
|
||||||
|
AdminUpdatePriceListPrice,
|
||||||
} from "./validators"
|
} from "./validators"
|
||||||
|
|
||||||
export const adminPriceListsRoutesMiddlewares: MiddlewareRoute[] = [
|
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"],
|
method: ["POST"],
|
||||||
matcher: "/admin/price-lists/:id/prices/batch",
|
matcher: "/admin/price-lists/:id/prices/batch",
|
||||||
middlewares: [
|
middlewares: [
|
||||||
validateAndTransformBody(AdminBatchPriceListPrices),
|
validateAndTransformBody(
|
||||||
|
createBatchBody(AdminCreatePriceListPrice, AdminUpdatePriceListPrice)
|
||||||
|
),
|
||||||
validateAndTransformQuery(
|
validateAndTransformQuery(
|
||||||
AdminGetPriceListPricesParams,
|
AdminGetPriceListPricesParams,
|
||||||
QueryConfig.listPriceListPriceQueryConfig
|
QueryConfig.listPriceListPriceQueryConfig
|
||||||
|
|||||||
@@ -40,17 +40,6 @@ export type AdminUpdatePriceListPriceType = z.infer<
|
|||||||
typeof AdminUpdatePriceListPrice
|
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({
|
export const AdminCreatePriceList = z.object({
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
description: z.string(),
|
description: z.string(),
|
||||||
|
|||||||
+3
-3
@@ -2,13 +2,13 @@ import { batchProductVariantsWorkflow } from "@medusajs/core-flows"
|
|||||||
import {
|
import {
|
||||||
AuthenticatedMedusaRequest,
|
AuthenticatedMedusaRequest,
|
||||||
MedusaResponse,
|
MedusaResponse,
|
||||||
} from "../../../../../../../types/routing"
|
} from "../../../../../../types/routing"
|
||||||
import {
|
import {
|
||||||
AdminBatchUpdateProductVariantType,
|
AdminBatchUpdateProductVariantType,
|
||||||
AdminCreateProductType,
|
AdminCreateProductType,
|
||||||
} from "../../../../validators"
|
} from "../../../validators"
|
||||||
import { BatchMethodRequest } from "@medusajs/types"
|
import { BatchMethodRequest } from "@medusajs/types"
|
||||||
import { refetchBatchVariants, remapVariantResponse } from "../../../../helpers"
|
import { refetchBatchVariants, remapVariantResponse } from "../../../helpers"
|
||||||
|
|
||||||
export const POST = async (
|
export const POST = async (
|
||||||
req: AuthenticatedMedusaRequest<
|
req: AuthenticatedMedusaRequest<
|
||||||
+3
-4
@@ -2,14 +2,13 @@ import { batchProductsWorkflow } from "@medusajs/core-flows"
|
|||||||
import {
|
import {
|
||||||
AuthenticatedMedusaRequest,
|
AuthenticatedMedusaRequest,
|
||||||
MedusaResponse,
|
MedusaResponse,
|
||||||
} from "../../../../../types/routing"
|
} from "../../../../types/routing"
|
||||||
import {
|
import {
|
||||||
AdminBatchUpdateProductType,
|
AdminBatchUpdateProductType,
|
||||||
AdminCreateProductType,
|
AdminCreateProductType,
|
||||||
} from "../../validators"
|
} from "../validators"
|
||||||
import { BatchMethodRequest } from "@medusajs/types"
|
import { BatchMethodRequest } from "@medusajs/types"
|
||||||
import { refetchBatchProducts, remapProductResponse } from "../../helpers"
|
import { refetchBatchProducts, remapProductResponse } from "../helpers"
|
||||||
import { CreateProductDTO, UpsertProductDTO } from "@medusajs/types"
|
|
||||||
|
|
||||||
export const POST = async (
|
export const POST = async (
|
||||||
req: AuthenticatedMedusaRequest<
|
req: AuthenticatedMedusaRequest<
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
||||||
import { authenticate } from "../../../utils/authenticate-middleware"
|
import { authenticate } from "../../../utils/authenticate-middleware"
|
||||||
import { maybeApplyLinkFilter } from "../../utils/maybe-apply-link-filter"
|
import { maybeApplyLinkFilter } from "../../utils/maybe-apply-link-filter"
|
||||||
|
import { unlessPath } from "../../utils/unless-path"
|
||||||
import { validateAndTransformBody } from "../../utils/validate-body"
|
import { validateAndTransformBody } from "../../utils/validate-body"
|
||||||
import { validateAndTransformQuery } from "../../utils/validate-query"
|
import { validateAndTransformQuery } from "../../utils/validate-query"
|
||||||
import { createBatchBody } from "../../utils/validators"
|
import { createBatchBody } from "../../utils/validators"
|
||||||
@@ -45,16 +46,6 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
|
|||||||
maybeApplyPriceListsFilter(),
|
maybeApplyPriceListsFilter(),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
method: ["GET"],
|
|
||||||
matcher: "/admin/products/:id",
|
|
||||||
middlewares: [
|
|
||||||
validateAndTransformQuery(
|
|
||||||
AdminGetProductParams,
|
|
||||||
QueryConfig.retrieveProductQueryConfig
|
|
||||||
),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
method: ["POST"],
|
method: ["POST"],
|
||||||
matcher: "/admin/products",
|
matcher: "/admin/products",
|
||||||
@@ -68,7 +59,7 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
method: ["POST"],
|
method: ["POST"],
|
||||||
matcher: "/admin/products/op/batch",
|
matcher: "/admin/products/batch",
|
||||||
middlewares: [
|
middlewares: [
|
||||||
validateAndTransformBody(
|
validateAndTransformBody(
|
||||||
createBatchBody(AdminCreateProduct, AdminBatchUpdateProduct)
|
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"],
|
method: ["POST"],
|
||||||
matcher: "/admin/products/:id",
|
matcher: "/admin/products/:id",
|
||||||
middlewares: [
|
middlewares: [
|
||||||
validateAndTransformBody(AdminUpdateProduct),
|
unlessPath(
|
||||||
validateAndTransformQuery(
|
/.*\/products\/batch/,
|
||||||
AdminGetProductParams,
|
validateAndTransformBody(AdminUpdateProduct)
|
||||||
QueryConfig.retrieveProductQueryConfig
|
),
|
||||||
|
unlessPath(
|
||||||
|
/.*\/products\/batch/,
|
||||||
|
validateAndTransformQuery(
|
||||||
|
AdminGetProductParams,
|
||||||
|
QueryConfig.retrieveProductQueryConfig
|
||||||
|
)
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -94,13 +104,15 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
|
|||||||
method: ["DELETE"],
|
method: ["DELETE"],
|
||||||
matcher: "/admin/products/:id",
|
matcher: "/admin/products/:id",
|
||||||
middlewares: [
|
middlewares: [
|
||||||
validateAndTransformQuery(
|
unlessPath(
|
||||||
AdminGetProductParams,
|
/.*\/products\/batch/,
|
||||||
QueryConfig.retrieveProductQueryConfig
|
validateAndTransformQuery(
|
||||||
|
AdminGetProductParams,
|
||||||
|
QueryConfig.retrieveProductQueryConfig
|
||||||
|
)
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
method: ["GET"],
|
method: ["GET"],
|
||||||
matcher: "/admin/products/:id/variants",
|
matcher: "/admin/products/:id/variants",
|
||||||
@@ -113,7 +125,18 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
method: ["POST"],
|
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: [
|
middlewares: [
|
||||||
validateAndTransformBody(
|
validateAndTransformBody(
|
||||||
createBatchBody(
|
createBatchBody(
|
||||||
@@ -132,20 +155,12 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
|
|||||||
method: ["GET"],
|
method: ["GET"],
|
||||||
matcher: "/admin/products/:id/variants/:variant_id",
|
matcher: "/admin/products/:id/variants/:variant_id",
|
||||||
middlewares: [
|
middlewares: [
|
||||||
validateAndTransformQuery(
|
unlessPath(
|
||||||
AdminGetProductVariantParams,
|
/.*\/variants\/batch/,
|
||||||
QueryConfig.retrieveVariantConfig
|
validateAndTransformQuery(
|
||||||
),
|
AdminGetProductVariantParams,
|
||||||
],
|
QueryConfig.retrieveVariantConfig
|
||||||
},
|
)
|
||||||
{
|
|
||||||
method: ["POST"],
|
|
||||||
matcher: "/admin/products/:id/variants",
|
|
||||||
middlewares: [
|
|
||||||
validateAndTransformBody(AdminCreateProductVariant),
|
|
||||||
validateAndTransformQuery(
|
|
||||||
AdminGetProductParams,
|
|
||||||
QueryConfig.retrieveProductQueryConfig
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -153,10 +168,16 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
|
|||||||
method: ["POST"],
|
method: ["POST"],
|
||||||
matcher: "/admin/products/:id/variants/:variant_id",
|
matcher: "/admin/products/:id/variants/:variant_id",
|
||||||
middlewares: [
|
middlewares: [
|
||||||
validateAndTransformBody(AdminUpdateProductVariant),
|
unlessPath(
|
||||||
validateAndTransformQuery(
|
/.*\/variants\/batch/,
|
||||||
AdminGetProductParams,
|
validateAndTransformBody(AdminUpdateProductVariant)
|
||||||
QueryConfig.retrieveProductQueryConfig
|
),
|
||||||
|
unlessPath(
|
||||||
|
/.*\/variants\/batch/,
|
||||||
|
validateAndTransformQuery(
|
||||||
|
AdminGetProductParams,
|
||||||
|
QueryConfig.retrieveProductQueryConfig
|
||||||
|
)
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -164,9 +185,12 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
|
|||||||
method: ["DELETE"],
|
method: ["DELETE"],
|
||||||
matcher: "/admin/products/:id/variants/:variant_id",
|
matcher: "/admin/products/:id/variants/:variant_id",
|
||||||
middlewares: [
|
middlewares: [
|
||||||
validateAndTransformQuery(
|
unlessPath(
|
||||||
AdminGetProductParams,
|
/.*\/variants\/batch/,
|
||||||
QueryConfig.retrieveProductQueryConfig
|
validateAndTransformQuery(
|
||||||
|
AdminGetProductParams,
|
||||||
|
QueryConfig.retrieveProductQueryConfig
|
||||||
|
)
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ export async function zodValidator<T>(
|
|||||||
body: T
|
body: T
|
||||||
): Promise<z.ZodRawShape> {
|
): Promise<z.ZodRawShape> {
|
||||||
try {
|
try {
|
||||||
zodSchema
|
|
||||||
return await zodSchema.parseAsync(body)
|
return await zodSchema.parseAsync(body)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof ZodError) {
|
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 = () => {
|
export const createSelectParams = () => {
|
||||||
return z.object({
|
return z.object({
|
||||||
fields: z.string().optional(),
|
fields: z.string().optional(),
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import {
|
|||||||
ModulesSdkUtils,
|
ModulesSdkUtils,
|
||||||
ProductStatus,
|
ProductStatus,
|
||||||
promiseAll,
|
promiseAll,
|
||||||
|
removeUndefined,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import {
|
import {
|
||||||
ProductCategoryEventData,
|
ProductCategoryEventData,
|
||||||
@@ -771,6 +772,8 @@ export default class ProductModuleService<
|
|||||||
ProductModuleService.normalizeCreateProductCollectionInput
|
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(
|
return await this.productCollectionService_.upsertWithReplace(
|
||||||
normalizedInput,
|
normalizedInput,
|
||||||
{ relations: ["products"] },
|
{ relations: ["products"] },
|
||||||
@@ -906,13 +909,48 @@ export default class ProductModuleService<
|
|||||||
): Promise<TProductCollection[]> {
|
): Promise<TProductCollection[]> {
|
||||||
const normalizedInput = data.map(
|
const normalizedInput = data.map(
|
||||||
ProductModuleService.normalizeUpdateProductCollectionInput
|
ProductModuleService.normalizeUpdateProductCollectionInput
|
||||||
)
|
) as UpdateCollectionInput[]
|
||||||
|
|
||||||
return await this.productCollectionService_.upsertWithReplace(
|
// TODO: Maybe we can update upsertWithReplace to not remove oneToMany entities, but just disassociate them? With that we can remove the code below.
|
||||||
normalizedInput,
|
// Another alternative is to not allow passing product_ids to a collection, and instead set the collection_id through the product update call.
|
||||||
{ relations: ["products"] },
|
const updatedCollections = await this.productCollectionService_.update(
|
||||||
|
normalizedInput.map((c) =>
|
||||||
|
removeUndefined({ ...c, products: undefined })
|
||||||
|
),
|
||||||
sharedContext
|
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_")
|
@InjectManager("baseRepository_")
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
export type LinkMethodRequest = {
|
||||||
|
add?: string[]
|
||||||
|
remove?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
export type BatchMethodRequest<TCreate extends any, TUpdate extends any> = {
|
export type BatchMethodRequest<TCreate extends any, TUpdate extends any> = {
|
||||||
create?: TCreate[]
|
create?: TCreate[]
|
||||||
update?: TUpdate[]
|
update?: TUpdate[]
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
export * from "./stock-locations"
|
export * from "./stock-locations"
|
||||||
|
export * from "./products"
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./mutations"
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { LinkMethodRequest } from "../../common"
|
||||||
|
|
||||||
|
export interface BatchLinkProductsToCollectionDTO extends LinkMethodRequest {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user