feat: Revamp of product categories (#7695)

* feat: Normalize the categories interface to match standards

* feat: Revamp the product category implementation

* fix: Adjustments to code and tests around product categories
This commit is contained in:
Stevche Radevski
2024-06-13 09:10:12 +02:00
committed by GitHub
parent fbd8eef18b
commit d862d03de0
35 changed files with 1135 additions and 874 deletions
@@ -10,11 +10,11 @@ import {
} from "@medusajs/utils"
import { ProductCategory } from "@models"
import { ProductCategoryRepository } from "@repositories"
import { UpdateCategoryInput } from "@types"
type InjectedDependencies = {
productCategoryRepository: DAL.TreeRepositoryService
}
export default class ProductCategoryService<
TEntity extends ProductCategory = ProductCategory
> {
@@ -24,6 +24,7 @@ export default class ProductCategoryService<
this.productCategoryRepository_ = productCategoryRepository
}
// TODO: Add support for object filter
@InjectManager("productCategoryRepository_")
async retrieve(
productCategoryId: string,
@@ -44,6 +45,8 @@ export default class ProductCategoryService<
config
)
// TODO: Currently remoteQuery doesn't allow passing custom objects, so the `include*` are part of the filters
// Modify remoteQuery to allow passing custom objects
const transformOptions = {
includeDescendantsTree: true,
}
@@ -140,30 +143,47 @@ export default class ProductCategoryService<
@InjectTransactionManager("productCategoryRepository_")
async create(
data: ProductTypes.CreateProductCategoryDTO,
data: ProductTypes.CreateProductCategoryDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<TEntity> {
): Promise<TEntity[]> {
return (await (
this.productCategoryRepository_ as unknown as ProductCategoryRepository
).create(data, sharedContext)) as TEntity
).create(data, sharedContext)) as TEntity[]
}
@InjectTransactionManager("productCategoryRepository_")
async update(
id: string,
data: ProductTypes.UpdateProductCategoryDTO,
data: UpdateCategoryInput[],
@MedusaContext() sharedContext: Context = {}
): Promise<TEntity> {
): Promise<TEntity[]> {
return (await (
this.productCategoryRepository_ as unknown as ProductCategoryRepository
).update(id, data, sharedContext)) as TEntity
).update(data, sharedContext)) as TEntity[]
}
@InjectTransactionManager("productCategoryRepository_")
async delete(
id: string,
ids: string[],
@MedusaContext() sharedContext: Context = {}
): Promise<void> {
await this.productCategoryRepository_.delete(id, sharedContext)
await this.productCategoryRepository_.delete(ids, sharedContext)
}
async softDelete(
ids: string[],
@MedusaContext() sharedContext?: Context
): Promise<Record<string, string[]> | void> {
return (await (
this.productCategoryRepository_ as unknown as ProductCategoryRepository
).softDelete(ids, sharedContext)) as any
}
async restore(
ids: string[],
@MedusaContext() sharedContext?: Context
): Promise<Record<string, string[]> | void> {
return (await (
this.productCategoryRepository_ as unknown as ProductCategoryRepository
).restore(ids, sharedContext)) as any
}
}
@@ -44,6 +44,7 @@ import {
ProductCollectionEvents,
ProductEventData,
ProductEvents,
UpdateCategoryInput,
UpdateCollectionInput,
UpdateProductInput,
UpdateProductOptionInput,
@@ -1114,67 +1115,166 @@ export default class ProductModuleService<
return collections
}
createCategories(
data: ProductTypes.CreateProductCategoryDTO[],
sharedContext?: Context
): Promise<ProductTypes.ProductCategoryDTO[]>
createCategories(
data: ProductTypes.CreateProductCategoryDTO,
sharedContext?: Context
): Promise<ProductTypes.ProductCategoryDTO>
@InjectManager("baseRepository_")
async createCategory(
data: ProductTypes.CreateProductCategoryDTO,
@EmitEvents()
async createCategories(
data:
| ProductTypes.CreateProductCategoryDTO[]
| ProductTypes.CreateProductCategoryDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<ProductTypes.ProductCategoryDTO> {
const result = await this.createCategory_(data, sharedContext)
): Promise<
ProductTypes.ProductCategoryDTO[] | ProductTypes.ProductCategoryDTO
> {
const input = Array.isArray(data) ? data : [data]
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,
const categories = await this.productCategoryService_.create(
input,
sharedContext
)
await this.eventBusModuleService_?.emit<ProductCategoryEventData>({
eventName: ProductCategoryEvents.CATEGORY_CREATED,
data: { id: productCategory.id },
const createdCategories = await this.baseRepository_.serialize<
ProductTypes.ProductCategoryDTO[]
>(categories)
eventBuilders.createdProductCategory({
data: createdCategories,
sharedContext,
})
return productCategory
return Array.isArray(data) ? createdCategories : createdCategories[0]
}
async upsertCategories(
data: ProductTypes.UpsertProductCategoryDTO[],
sharedContext?: Context
): Promise<ProductTypes.ProductCategoryDTO[]>
async upsertCategories(
data: ProductTypes.UpsertProductCategoryDTO,
sharedContext?: Context
): Promise<ProductTypes.ProductCategoryDTO>
@InjectTransactionManager("baseRepository_")
async updateCategory(
categoryId: string,
@EmitEvents()
async upsertCategories(
data:
| ProductTypes.UpsertProductCategoryDTO[]
| ProductTypes.UpsertProductCategoryDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<
ProductTypes.ProductCategoryDTO[] | ProductTypes.ProductCategoryDTO
> {
const input = Array.isArray(data) ? data : [data]
const forUpdate = input.filter(
(category): category is UpdateCategoryInput => !!category.id
)
const forCreate = input.filter(
(category): category is ProductTypes.CreateProductCategoryDTO =>
!category.id
)
let created: ProductCategory[] = []
let updated: ProductCategory[] = []
if (forCreate.length) {
created = await this.productCategoryService_.create(
forCreate,
sharedContext
)
}
if (forUpdate.length) {
updated = await this.productCategoryService_.update(
forUpdate,
sharedContext
)
}
const createdCategories = await this.baseRepository_.serialize<
ProductTypes.ProductCategoryDTO[]
>(created)
const updatedCategories = await this.baseRepository_.serialize<
ProductTypes.ProductCategoryDTO[]
>(updated)
eventBuilders.createdProductCategory({
data: createdCategories,
sharedContext,
})
eventBuilders.updatedProductCategory({
data: updatedCategories,
sharedContext,
})
const result = [...createdCategories, ...updatedCategories]
return Array.isArray(data) ? result : result[0]
}
updateCategories(
id: string,
data: ProductTypes.UpdateProductCategoryDTO,
sharedContext?: Context
): Promise<ProductTypes.ProductCategoryDTO>
updateCategories(
selector: ProductTypes.FilterableProductTypeProps,
data: ProductTypes.UpdateProductCategoryDTO,
sharedContext?: Context
): Promise<ProductTypes.ProductCategoryDTO[]>
@InjectManager("baseRepository_")
@EmitEvents()
async updateCategories(
idOrSelector: string | ProductTypes.FilterableProductTypeProps,
data: ProductTypes.UpdateProductCategoryDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<ProductTypes.ProductCategoryDTO> {
const productCategory = await this.productCategoryService_.update(
categoryId,
data,
): Promise<
ProductTypes.ProductCategoryDTO[] | ProductTypes.ProductCategoryDTO
> {
let normalizedInput: UpdateCategoryInput[] = []
if (isString(idOrSelector)) {
// Check if the type exists in the first place
await this.productCategoryService_.retrieve(
idOrSelector,
{},
sharedContext
)
normalizedInput = [{ id: idOrSelector, ...data }]
} else {
const categories = await this.productCategoryService_.list(
idOrSelector,
{},
sharedContext
)
normalizedInput = categories.map((type) => ({
id: type.id,
...data,
}))
}
const categories = await this.productCategoryService_.update(
normalizedInput,
sharedContext
)
await this.eventBusModuleService_?.emit<ProductCategoryEventData>({
eventName: ProductCategoryEvents.CATEGORY_UPDATED,
data: { id: productCategory.id },
const updatedCategories = await this.baseRepository_.serialize<
ProductTypes.ProductCategoryDTO[]
>(categories)
eventBuilders.updatedProductCategory({
data: updatedCategories,
sharedContext,
})
return await this.baseRepository_.serialize(productCategory, {
populate: true,
})
}
@InjectTransactionManager("baseRepository_")
async deleteCategory(
categoryId: string,
@MedusaContext() sharedContext: Context = {}
): Promise<void> {
await this.productCategoryService_.delete(categoryId, sharedContext)
await this.eventBusModuleService_?.emit<ProductCategoryEventData>({
eventName: ProductCategoryEvents.CATEGORY_DELETED,
data: { id: categoryId },
})
return isString(idOrSelector) ? updatedCategories[0] : updatedCategories
}
create(