feat: Create product category flow (#7034)
* feat: Create product category * address PR comments
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@ export * from "./customer-group"
|
||||
export * from "./defaults"
|
||||
export * from "./definition"
|
||||
export * from "./definitions"
|
||||
export * from "./file"
|
||||
export * from "./fulfillment"
|
||||
export * as Handlers from "./handlers"
|
||||
export * from "./inventory"
|
||||
@@ -15,6 +16,7 @@ export * from "./payment"
|
||||
export * from "./price-list"
|
||||
export * from "./pricing"
|
||||
export * from "./product"
|
||||
export * from "./product-category"
|
||||
export * from "./promotion"
|
||||
export * from "./reservation"
|
||||
export * from "./region"
|
||||
@@ -24,4 +26,3 @@ export * from "./stock-location"
|
||||
export * from "./store"
|
||||
export * from "./tax"
|
||||
export * from "./user"
|
||||
export * from "./file"
|
||||
|
||||
2
packages/core-flows/src/product-category/index.ts
Normal file
2
packages/core-flows/src/product-category/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./steps"
|
||||
export * from "./workflows"
|
||||
@@ -0,0 +1,35 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import {
|
||||
CreateProductCategoryDTO,
|
||||
IProductModuleService,
|
||||
} from "@medusajs/types"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
type CreateProductCategoryStepInput = {
|
||||
product_category: CreateProductCategoryDTO
|
||||
}
|
||||
|
||||
export const createProductCategoryStepId = "create-product-category"
|
||||
export const createProductCategoryStep = createStep(
|
||||
createProductCategoryStepId,
|
||||
async (data: CreateProductCategoryStepInput, { container }) => {
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
const created = await service.createCategory(data.product_category)
|
||||
|
||||
return new StepResponse(created, created.id)
|
||||
},
|
||||
async (createdId, { container }) => {
|
||||
if (!createdId) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
await service.deleteCategory(createdId)
|
||||
}
|
||||
)
|
||||
1
packages/core-flows/src/product-category/steps/index.ts
Normal file
1
packages/core-flows/src/product-category/steps/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./create-product-category"
|
||||
@@ -0,0 +1,16 @@
|
||||
import { ProductCategoryWorkflow } from "@medusajs/types"
|
||||
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||
import { createProductCategoryStep } from "../steps"
|
||||
|
||||
type WorkflowInputData =
|
||||
ProductCategoryWorkflow.CreateProductCategoryWorkflowInput
|
||||
|
||||
export const createProductCategoryWorkflowId = "create-product-category"
|
||||
export const createProductCategoryWorkflow = createWorkflow(
|
||||
createProductCategoryWorkflowId,
|
||||
(input: WorkflowData<WorkflowInputData>) => {
|
||||
const category = createProductCategoryStep(input)
|
||||
|
||||
return category
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./create-product-category"
|
||||
@@ -1,8 +1,10 @@
|
||||
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 * as QueryConfig from "./query-config"
|
||||
import {
|
||||
AdminCreateProductCategory,
|
||||
AdminProductCategoriesParams,
|
||||
AdminProductCategoryParams,
|
||||
} from "./validators"
|
||||
@@ -33,4 +35,15 @@ export const adminProductCategoryRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/product-categories",
|
||||
middlewares: [
|
||||
validateAndTransformBody(AdminCreateProductCategory),
|
||||
validateAndTransformQuery(
|
||||
AdminProductCategoryParams,
|
||||
QueryConfig.retrieveProductCategoryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
@@ -11,10 +11,24 @@ export const defaults = [
|
||||
"updated_at",
|
||||
"metadata",
|
||||
|
||||
"parent_category.id",
|
||||
"parent_category.name",
|
||||
"category_children.id",
|
||||
"category_children.name",
|
||||
"*category_children",
|
||||
]
|
||||
|
||||
export const allowed = [
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
"handle",
|
||||
"is_active",
|
||||
"is_internal",
|
||||
"rank",
|
||||
"parent_category_id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"metadata",
|
||||
|
||||
"*parent_category",
|
||||
"*category_children",
|
||||
]
|
||||
|
||||
export const retrieveProductCategoryConfig = {
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { AdminProductCategoryListResponse } from "@medusajs/types"
|
||||
import { createProductCategoryWorkflow } from "@medusajs/core-flows"
|
||||
import {
|
||||
AdminProductCategoryListResponse,
|
||||
AdminProductCategoryResponse,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
remoteQueryObjectFromString,
|
||||
@@ -7,7 +11,10 @@ import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../types/routing"
|
||||
import { AdminProductCategoriesParamsType } from "./validators"
|
||||
import {
|
||||
AdminCreateProductCategoryType,
|
||||
AdminProductCategoriesParamsType,
|
||||
} from "./validators"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest<AdminProductCategoriesParamsType>,
|
||||
@@ -33,3 +40,33 @@ export const GET = async (
|
||||
limit: metadata.take,
|
||||
})
|
||||
}
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<AdminCreateProductCategoryType>,
|
||||
res: MedusaResponse<AdminProductCategoryResponse>
|
||||
) => {
|
||||
const { result, errors } = await createProductCategoryWorkflow(req.scope).run(
|
||||
{
|
||||
input: { product_category: req.validatedBody },
|
||||
throwOnError: false,
|
||||
}
|
||||
)
|
||||
|
||||
if (Array.isArray(errors) && errors[0]) {
|
||||
throw errors[0].error
|
||||
}
|
||||
|
||||
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||
|
||||
const queryObject = remoteQueryObjectFromString({
|
||||
entryPoint: "product_category",
|
||||
variables: {
|
||||
filters: { id: result.id },
|
||||
},
|
||||
fields: req.remoteQueryConfig.fields,
|
||||
})
|
||||
|
||||
const [product_category] = await remoteQuery(queryObject)
|
||||
|
||||
res.status(200).json({ product_category })
|
||||
}
|
||||
|
||||
@@ -43,6 +43,14 @@ export const AdminProductCategoriesParams = createFindParams({
|
||||
(val: any) => optionalBooleanMapper.get(val?.toLowerCase()),
|
||||
z.boolean().optional()
|
||||
),
|
||||
is_internal: z.preprocess(
|
||||
(val: any) => optionalBooleanMapper.get(val?.toLowerCase()),
|
||||
z.boolean().optional()
|
||||
),
|
||||
is_active: z.preprocess(
|
||||
(val: any) => optionalBooleanMapper.get(val?.toLowerCase()),
|
||||
z.boolean().optional()
|
||||
),
|
||||
created_at: createOperatorMap().optional(),
|
||||
updated_at: createOperatorMap().optional(),
|
||||
deleted_at: createOperatorMap().optional(),
|
||||
@@ -50,3 +58,19 @@ export const AdminProductCategoriesParams = createFindParams({
|
||||
$or: z.lazy(() => AdminProductCategoriesParams.array()).optional(),
|
||||
})
|
||||
)
|
||||
|
||||
export const AdminCreateProductCategory = z
|
||||
.object({
|
||||
name: z.string(),
|
||||
description: z.string().optional(),
|
||||
handle: z.string().optional(),
|
||||
is_internal: z.boolean().optional(),
|
||||
is_active: z.boolean().optional(),
|
||||
parent_category_id: z.string().optional(),
|
||||
metadata: z.record(z.unknown()).optional(),
|
||||
})
|
||||
.strict()
|
||||
|
||||
export type AdminCreateProductCategoryType = z.infer<
|
||||
typeof AdminCreateProductCategory
|
||||
>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { IProductModuleService, ProductTypes } from "@medusajs/types"
|
||||
import { Product, ProductCategory } from "@models"
|
||||
import { MockEventBusService } from "medusa-test-utils"
|
||||
import { MockEventBusService, SuiteOptions, moduleIntegrationTestRunner } from "medusa-test-utils"
|
||||
import { createProductCategories } from "../../../__fixtures__/product-category"
|
||||
import { productCategoriesRankData } from "../../../__fixtures__/product-category/data"
|
||||
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
|
||||
@@ -4,11 +4,7 @@ import {
|
||||
ProductCategoryTransformOptions,
|
||||
ProductTypes,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
DALUtils,
|
||||
MedusaError,
|
||||
isDefined
|
||||
} from "@medusajs/utils"
|
||||
import { DALUtils, MedusaError, isDefined } from "@medusajs/utils"
|
||||
import {
|
||||
LoadStrategy,
|
||||
FilterQuery as MikroFilterQuery,
|
||||
|
||||
@@ -915,11 +915,21 @@ export default class ProductModuleService<
|
||||
)
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
@InjectManager("baseRepository_")
|
||||
async createCategory(
|
||||
data: ProductTypes.CreateProductCategoryDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductTypes.ProductCategoryDTO> {
|
||||
const result = await this.createCategory_(data, sharedContext)
|
||||
|
||||
return await this.baseRepository_.serialize(result)
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async createCategory_(
|
||||
data: ProductTypes.CreateProductCategoryDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductCategory> {
|
||||
const productCategory = await this.productCategoryService_.create(
|
||||
data,
|
||||
sharedContext
|
||||
@@ -930,9 +940,7 @@ export default class ProductModuleService<
|
||||
{ id: productCategory.id }
|
||||
)
|
||||
|
||||
return await this.baseRepository_.serialize(productCategory, {
|
||||
populate: true,
|
||||
})
|
||||
return productCategory
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
@@ -1115,8 +1123,10 @@ export default class ProductModuleService<
|
||||
data: ProductTypes.CreateProductDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TProduct[]> {
|
||||
const normalizedInput = await Promise.all(
|
||||
data.map((d) => this.normalizeCreateProductInput(d, sharedContext))
|
||||
const normalizedInput = await promiseAll(
|
||||
data.map(
|
||||
async (d) => await this.normalizeCreateProductInput(d, sharedContext)
|
||||
)
|
||||
)
|
||||
|
||||
const productData = await this.productService_.upsertWithReplace(
|
||||
@@ -1171,8 +1181,10 @@ export default class ProductModuleService<
|
||||
data: UpdateProductInput[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TProduct[]> {
|
||||
const normalizedInput = await Promise.all(
|
||||
data.map((d) => this.normalizeUpdateProductInput(d, sharedContext))
|
||||
const normalizedInput = await promiseAll(
|
||||
data.map(
|
||||
async (d) => await this.normalizeUpdateProductInput(d, sharedContext)
|
||||
)
|
||||
)
|
||||
|
||||
const productData = await this.productService_.upsertWithReplace(
|
||||
@@ -1258,7 +1270,8 @@ export default class ProductModuleService<
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductTypes.CreateProductDTO> {
|
||||
const productData = (await this.normalizeUpdateProductInput(
|
||||
product as UpdateProductInput
|
||||
product as UpdateProductInput,
|
||||
sharedContext
|
||||
)) as ProductTypes.CreateProductDTO
|
||||
|
||||
if (!productData.handle && productData.title) {
|
||||
|
||||
@@ -358,11 +358,8 @@ export interface CreateProductCategoryDTO {
|
||||
rank?: number
|
||||
/**
|
||||
* The ID of the parent product category, if it has any.
|
||||
*
|
||||
* @privateRemarks
|
||||
* Shouldn't this be optional?
|
||||
*/
|
||||
parent_category_id: string | null
|
||||
parent_category_id?: string | null
|
||||
/**
|
||||
* Holds custom data in key-value pairs.
|
||||
*/
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
export * as CartWorkflow from "./cart"
|
||||
export * as CommonWorkflow from "./common"
|
||||
export * as ProductWorkflow from "./product"
|
||||
export * as InventoryWorkflow from "./inventory"
|
||||
export * as PriceListWorkflow from "./price-list"
|
||||
export * as UserWorkflow from "./user"
|
||||
export * as RegionWorkflow from "./region"
|
||||
export * as InviteWorkflow from "./invite"
|
||||
export * as FulfillmentWorkflow from "./fulfillment"
|
||||
export * as InventoryWorkflow from "./inventory"
|
||||
export * as InviteWorkflow from "./invite"
|
||||
export * as PriceListWorkflow from "./price-list"
|
||||
export * as ProductWorkflow from "./product"
|
||||
export * as ProductCategoryWorkflow from "./product-category"
|
||||
export * as RegionWorkflow from "./region"
|
||||
export * as ReservationWorkflow from "./reservation"
|
||||
export * as UserWorkflow from "./user"
|
||||
|
||||
|
||||
5
packages/types/src/workflow/product-category/index.ts
Normal file
5
packages/types/src/workflow/product-category/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { CreateProductCategoryDTO } from "../../product"
|
||||
|
||||
export interface CreateProductCategoryWorkflowInput {
|
||||
product_category: CreateProductCategoryDTO
|
||||
}
|
||||
Reference in New Issue
Block a user