diff --git a/packages/medusa/src/api-v2/admin/products/validators.ts b/packages/medusa/src/api-v2/admin/products/validators.ts index 0e75f84531..f4c41916a2 100644 --- a/packages/medusa/src/api-v2/admin/products/validators.ts +++ b/packages/medusa/src/api-v2/admin/products/validators.ts @@ -140,7 +140,6 @@ export class AdminGetProductsParams extends extendedFindParamsMixin({ // @Transform(({ value }) => optionalBooleanMapper.get(value.toLowerCase())) // include_category_children?: boolean - // TODO: The OperatorMap and DateOperator are slightly different, so the date comparisons is a breaking change. @IsOptional() @ValidateNested() @Type(() => OperatorMapValidator) diff --git a/packages/product/integration-tests/__tests__/services/product-module-service/products.spec.ts b/packages/product/integration-tests/__tests__/services/product-module-service/products.spec.ts index c26f983fcd..8f1c62b6c8 100644 --- a/packages/product/integration-tests/__tests__/services/product-module-service/products.spec.ts +++ b/packages/product/integration-tests/__tests__/services/product-module-service/products.spec.ts @@ -765,6 +765,23 @@ describe("ProductModuleService products", function () { } }) + it("should retrieve soft-deleted products if filtered on deleted_at", async () => { + const data = buildProductAndRelationsData({ + images, + thumbnail: images[0], + }) + + const products = await module.create([data]) + + await module.softDelete([products[0].id]) + + const softDeleted = await module.list({ + deleted_at: { $gt: "01-01-2022" }, + }) + + expect(softDeleted).toHaveLength(1) + }) + it("should emit events through eventBus", async () => { const eventBusSpy = jest.spyOn(EventBusService.prototype, "emit") const data = buildProductAndRelationsData({ diff --git a/packages/types/src/product/common.ts b/packages/types/src/product/common.ts index bc1b99646b..6b72baffca 100644 --- a/packages/types/src/product/common.ts +++ b/packages/types/src/product/common.ts @@ -595,12 +595,21 @@ export interface ProductOptionValueDTO { * @prop categories - Filters on a product's categories. * @prop collection_id - Filters a product by its associated collections. */ + export interface FilterableProductProps extends BaseFilterable { /** * Search through the products' attributes, such as titles and descriptions, using this search term. */ q?: string + /** + * The status to filter products by + */ + status?: ProductStatus | ProductStatus[] + /** + * The titles to filter products by. + */ + title?: string | string[] /** * The handles to filter products by. */ @@ -609,6 +618,10 @@ export interface FilterableProductProps * The IDs to filter products by. */ id?: string | string[] + /** + * Filters only or excluding gift card products + */ + is_giftcard?: boolean /** * Filters on a product's tags. */ @@ -635,6 +648,10 @@ export interface FilterableProductProps */ is_active?: boolean } + /** + * Filter a product by the ID of the associated type + */ + type_id?: string | string[] /** * Filter a product by the IDs of their associated categories. */ @@ -643,6 +660,18 @@ export interface FilterableProductProps * Filters a product by the IDs of their associated collections. */ collection_id?: string | string[] | OperatorMap + /** + * Filters a product based on when it was created + */ + created_at?: OperatorMap + /** + * Filters a product based on when it was updated + */ + updated_at?: OperatorMap + /** + * Filters soft-deleted products based on the date they were deleted at. + */ + deleted_at?: OperatorMap } /** diff --git a/packages/utils/src/modules-sdk/build-query.ts b/packages/utils/src/modules-sdk/build-query.ts index d075dff5dc..e62eb24f57 100644 --- a/packages/utils/src/modules-sdk/build-query.ts +++ b/packages/utils/src/modules-sdk/build-query.ts @@ -3,12 +3,20 @@ import { deduplicate, isDefined, isObject } from "../common" import { SoftDeletableFilterKey } from "../dal" +// Following convention here is fine, we can make it configurable if needed. +const DELETED_AT_FIELD_NAME = "deleted_at" + +type FilterFlags = { + withDeleted?: boolean +} + export function buildQuery( filters: Record = {}, config: FindConfig & { primaryKeyFields?: string | string[] } = {} ): DAL.FindOptions { const where: DAL.FilterQuery = {} - buildWhere(filters, where) + const filterFlags: FilterFlags = {} + buildWhere(filters, where, filterFlags) const primaryKeyFieldArray = isDefined(config.primaryKeyFields) ? !Array.isArray(config.primaryKeyFields) @@ -43,7 +51,7 @@ export function buildQuery( findOptions.orderBy = config.order as DAL.OptionsQuery["orderBy"] } - if (config.withDeleted) { + if (config.withDeleted || filterFlags.withDeleted) { findOptions.filters ??= {} findOptions.filters[SoftDeletableFilterKey] = { withDeleted: true, @@ -61,12 +69,20 @@ export function buildQuery( return { where, options: findOptions } } -function buildWhere(filters: Record = {}, where = {}) { +function buildWhere( + filters: Record = {}, + where = {}, + flags: FilterFlags = {} +) { for (let [prop, value] of Object.entries(filters)) { + if (prop === DELETED_AT_FIELD_NAME) { + flags.withDeleted = true + } + if (["$or", "$and"].includes(prop)) { where[prop] = value.map((val) => { const deepWhere = {} - buildWhere(val, deepWhere) + buildWhere(val, deepWhere, flags) return deepWhere }) continue @@ -80,7 +96,7 @@ function buildWhere(filters: Record = {}, where = {}) { if (isObject(value)) { where[prop] = {} - buildWhere(value, where[prop]) + buildWhere(value, where[prop], flags) continue }