feat(medusa): Filter products by category params in store/admin (#3155)

What:

Products can be filtered through the API by category parameters

Why:

To filter products by category

How:

- adds 2 params in admin/store route
- updates repository to accept 2 new parameters

RESOLVES CORE-1032
RESOLVES CORE-1033
This commit is contained in:
Riqwan Thamir authored and GitHub committed 2023-02-01 17:25:07 +00:00
1 parent 4fbf6b7ad3
commit 4105405f28
7 files changed
+357 -4

No files matched your search

@@ -83,6 +83,16 @@ import { FilterableProductProps } from "../../../../types/product"
* type: array
* items:
* type: string
* - in: query
* name: category_id
* style: form
* explode: false
* description: Category IDs to filter products by
* schema:
* type: array
* items:
* type: string
* - (query) include_category_children {boolean} Include category children when filtering by category_id
* - (query) title {string} title to search for.
* - (query) description {string} description to search for.
* - (query) handle {string} handle to search for.
@@ -125,6 +125,16 @@ import PublishableAPIKeysFeatureFlag from "../../../../loaders/feature-flags/pub
* type: string
* description: filter by dates greater than or equal to this date
* format: date
* - in: query
* name: category_id
* style: form
* explode: false
* description: Category ids to filter by.
* schema:
* type: array
* items:
* type: string
* - (query) include_category_children {boolean} Include category children when filtering by category_id.
* - (query) offset=0 {integer} How many products to skip in the result.
* - (query) limit=100 {integer} Limit the number of products returned.
* - (query) expand {string} (Comma separated) Which fields should be expanded in each order of the result.
@@ -303,6 +313,15 @@ export class StoreGetProductsParams extends StoreGetProductsPaginationParams {
@FeatureFlagDecorators(SalesChannelFeatureFlag.key, [IsOptional(), IsArray()])
sales_channel_id?: string[]
@IsArray()
@IsOptional()
category_id?: string[]
@IsBoolean()
@IsOptional()
@Transform(({ value }) => optionalBooleanMapper.get(value.toLowerCase()))
include_category_children?: boolean
@IsOptional()
@ValidateNested()
@Type(() => DateComparisonOperator)
+55 -1
View File
@@ -6,7 +6,7 @@ import {
In,
Repository,
} from "typeorm"
import { PriceList, Product, SalesChannel } from "../models"
import { PriceList, Product, SalesChannel, ProductCategory } from "../models"
import {
ExtendedFindConfig,
Selector,
@@ -27,6 +27,10 @@ export type FindWithoutRelationsOptions = DefaultWithoutRelations & {
where: DefaultWithoutRelations["where"] & {
price_list_id?: FindOperator<PriceList>
sales_channel_id?: FindOperator<SalesChannel>
category_id?: {
value: string[]
}
include_category_children?: boolean
discount_condition_id?: string
}
}
@@ -57,6 +61,13 @@ export class ProductRepository extends Repository<Product> {
const sales_channels = optionsWithoutRelations?.where?.sales_channel_id
delete optionsWithoutRelations?.where?.sales_channel_id
const categories = optionsWithoutRelations?.where?.category_id
delete optionsWithoutRelations?.where?.category_id
const include_category_children =
optionsWithoutRelations?.where?.include_category_children
delete optionsWithoutRelations?.where?.include_category_children
const discount_condition_id =
optionsWithoutRelations?.where?.discount_condition_id
delete optionsWithoutRelations?.where?.discount_condition_id
@@ -96,6 +107,48 @@ export class ProductRepository extends Repository<Product> {
)
}
if (categories) {
let categoryIds = categories.value
if (include_category_children) {
const categoryRepository =
this.manager.getTreeRepository(ProductCategory)
const categories = await categoryRepository.find({
where: { id: In(categoryIds) },
})
categoryIds = []
for (const category of categories) {
const categoryChildren = await categoryRepository.findDescendantsTree(
category
)
const getAllIdsRecursively = (productCategory: ProductCategory) => {
let result = [productCategory.id]
;(productCategory.category_children || []).forEach((child) => {
result = result.concat(getAllIdsRecursively(child))
})
return result
}
categoryIds = categoryIds.concat(
getAllIdsRecursively(categoryChildren)
)
}
}
if (categoryIds.length) {
qb.innerJoin(
`${productAlias}.categories`,
"categories",
"categories.id IN (:...categoryIds)",
{ categoryIds }
)
}
}
if (discount_condition_id) {
qb.innerJoin(
"discount_condition_product",
@@ -220,6 +273,7 @@ export class ProductRepository extends Repository<Product> {
): Promise<[Product[], number]> {
let count: number
let entities: Product[]
if (Array.isArray(idsOrOptionsWithoutRelations)) {
entities = await this.findByIds(idsOrOptionsWithoutRelations, {
withDeleted: idsOrOptionsWithoutRelations.withDeleted ?? false,
+7 -2
View File
@@ -75,13 +75,18 @@ export class FilterableProductProps {
@FeatureFlagDecorators(SalesChannelFeatureFlag.key, [IsOptional(), IsArray()])
sales_channel_id?: string[]
@IsString()
@IsOptional()
discount_condition_id?: string
@IsArray()
@IsOptional()
category_id?: string[]
@IsString()
@IsBoolean()
@IsOptional()
discount_condition_id?: string
@Transform(({ value }) => optionalBooleanMapper.get(value.toLowerCase()))
include_category_children?: boolean
@IsOptional()
@ValidateNested()