feat(products): fix search query filter overwrite and add minPrice maxPrice sorting filters [TASK-26.10] [TASK-26.11]

This commit is contained in:
parsa aghaei 2026-08-05 14:39:58 +03:30
parent 7f3c04acf5
commit 3497a743b3
3 changed files with 59 additions and 37 deletions

View File

@ -25,4 +25,24 @@ export class GetProductsDto extends PaginationDto {
@IsOptional()
@IsString()
requiresRx?: string;
@ApiPropertyOptional({ description: 'حداقل قیمت (تومان)' })
@IsOptional()
@IsString()
minPrice?: string;
@ApiPropertyOptional({ description: 'حداکثر قیمت (تومان)' })
@IsOptional()
@IsString()
maxPrice?: string;
@ApiPropertyOptional({ description: 'فیلد مرتب‌سازی', enum: ['priceValue', 'createdAt', 'nameFa'] })
@IsOptional()
@IsString()
sortBy?: string;
@ApiPropertyOptional({ description: 'جهت مرتب‌سازی', enum: ['asc', 'desc'] })
@IsOptional()
@IsEnum(['asc', 'desc'])
sortOrder?: 'asc' | 'desc';
}

View File

@ -13,66 +13,68 @@ export class ProductsService {
search,
symptom,
requiresRx,
minPrice,
maxPrice,
page = 1,
limit = 10,
sortBy = 'createdAt',
sortOrder = 'desc',
} = filters;
const whereClause: any = {};
const andConditions: any[] = [];
if (requiresRx !== undefined && requiresRx !== null && requiresRx !== '') {
whereClause.requiresRx = requiresRx === 'true' || requiresRx === '1';
andConditions.push({ requiresRx: requiresRx === 'true' || requiresRx === '1' });
}
if (category) {
whereClause.categorySlug = category;
andConditions.push({ categorySlug: category });
}
if (petType && petType !== 'all') {
whereClause.suitableFor = { in: [petType, 'هر دو'] };
andConditions.push({ suitableFor: { in: [petType, 'هر دو'] } });
}
// Filter by a specific symptom (from URL param ?symptom=...)
if (symptom) {
whereClause.symptoms = {
some: {
symptom: { contains: symptom, mode: 'insensitive' },
andConditions.push({
symptoms: {
some: {
symptom: { contains: symptom, mode: 'insensitive' },
},
},
};
});
}
if (minPrice) {
andConditions.push({ priceValue: { gte: Number(minPrice) } });
}
if (maxPrice) {
andConditions.push({ priceValue: { lte: Number(maxPrice) } });
}
if (search) {
// If symptom filter is already applied, extend via AND to also search names/desc
// If not, use OR across names, description AND symptoms
const searchConditions = [
{ artNo: { contains: search, mode: 'insensitive' } },
{ barcode: { contains: search, mode: 'insensitive' } },
{ nameFa: { contains: search, mode: 'insensitive' } },
{ nameEn: { contains: search, mode: 'insensitive' } },
{ description: { contains: search, mode: 'insensitive' } },
{ shortDescription: { contains: search, mode: 'insensitive' } },
{
symptoms: {
some: {
symptom: { contains: search, mode: 'insensitive' },
andConditions.push({
OR: [
{ artNo: { contains: search, mode: 'insensitive' } },
{ barcode: { contains: search, mode: 'insensitive' } },
{ nameFa: { contains: search, mode: 'insensitive' } },
{ nameEn: { contains: search, mode: 'insensitive' } },
{ description: { contains: search, mode: 'insensitive' } },
{ shortDescription: { contains: search, mode: 'insensitive' } },
{
symptoms: {
some: {
symptom: { contains: search, mode: 'insensitive' },
},
},
},
},
];
if (symptom) {
// Already have a symptom filter; combine with AND
whereClause.AND = [
{ symptoms: whereClause.symptoms },
{ OR: searchConditions.filter((c) => !('symptoms' in c)) },
];
delete whereClause.symptoms;
} else {
whereClause.OR = searchConditions;
}
],
});
}
const whereClause: any = andConditions.length > 0 ? { AND: andConditions } : {};
const skip = (page - 1) * limit;
const [rawProducts, total] = await Promise.all([

View File

@ -207,8 +207,8 @@
- [ ] **TASK-26.7 [SSR & HttpOnly Cookie Auth]:** ذخیره توکن‌ها درون HttpOnly Cookie جهت همگام‌سازی کامل Server & Client Components در Next.js.
- [x] **TASK-26.8 [Logout State Cleanup]:** پاکسازی کامل کاتالوگ، سبد خرید و تمام کش‌های کاربر هنگام خروج (Logout Cleanup).
- [x] **TASK-26.9 [Admin Token Validation Guard]:** اعتبارسنجی واقعی توکن ادمین در `ProtectedRoute.tsx` از طریق فراخوانی API پروفایل در هنگام لود پنل.
- [ ] **TASK-26.10 [Fix Pagination & Search Query Overwrite]:** اصلاح منطق `whereClause` در `products.service.ts` جهت جلوگیری از اوررایت شدن فیلترها.
- [ ] **TASK-26.11 [Price Range & Sorting Filters]:** افزودن فیلتر محدوده قیمت (`minPrice`, `maxPrice`) و مرتب‌سازی (`sort`) به DTO و اندپویینت‌های محصولات.
- [x] **TASK-26.10 [Fix Pagination & Search Query Overwrite]:** اصلاح منطق `whereClause` در `products.service.ts` جهت جلوگیری از اوررایت شدن فیلترها.
- [x] **TASK-26.11 [Price Range & Sorting Filters]:** افزودن فیلتر محدوده قیمت (`minPrice`, `maxPrice`) و مرتب‌سازی (`sort`) به DTO و اندپویینت‌های محصولات.
- [ ] **TASK-26.12 [Sync Fallback IDs with Database]:** همگام‌سازی شناسه محصولات استاتیک فرانت‌اند با دیتابیس واقعی جهت جلوگیری از 404 در سفارش.
- [ ] **TASK-26.13 [Admin Products Table Pagination]:** پیاده‌سازی پجینیشن سرورساید کامل در جدول مدیریت محصولات پنل ادمین.
- [ ] **TASK-26.14 [Admin Wallet Adjust Endpoint]:** ایجاد اندپویینت `POST /api/users/:id/wallet-adjust` و فرم شارژ/کسر مستقیم کیف‌پول در پنل ادمین.