From cbcff26e63d1bfdbec843b74861f7fa43d9f3f40 Mon Sep 17 00:00:00 2001
From: parsa aghaei
Date: Wed, 5 Aug 2026 14:41:26 +0330
Subject: [PATCH] feat(pets): add admin pet health records query and paginated
pet list endpoint [TASK-26.17]
---
backend/src/admin/admin.controller.ts | 15 +++++++
backend/src/admin/admin.service.ts | 42 ++++++++++++++++++
backend/src/pets/pets.service.ts | 61 +++++++++++++++++++++++++++
docs/backlog.md | 2 +-
4 files changed, 119 insertions(+), 1 deletion(-)
diff --git a/backend/src/admin/admin.controller.ts b/backend/src/admin/admin.controller.ts
index 62445c6..ffbf70f 100644
--- a/backend/src/admin/admin.controller.ts
+++ b/backend/src/admin/admin.controller.ts
@@ -254,6 +254,21 @@ export class AdminController {
return { success: true, data: settings };
}
+ @UseGuards(JwtAuthGuard)
+ @Get('pets')
+ @ApiOperation({ summary: 'لیست پتها (مدیریت)' })
+ @ApiQuery({ name: 'page', required: false })
+ @ApiQuery({ name: 'limit', required: false })
+ @ApiQuery({ name: 'search', required: false })
+ async getPets(
+ @Query('page') page?: string,
+ @Query('limit') limit?: string,
+ @Query('search') search?: string,
+ ) {
+ const data = await this.adminService.getPets({ page, limit, search });
+ return { success: true, ...data };
+ }
+
// --- Doctors / Vets ---
@Get('doctors')
@ApiOperation({ summary: 'لیست پزشکان و متخصصان' })
diff --git a/backend/src/admin/admin.service.ts b/backend/src/admin/admin.service.ts
index afad26a..333e517 100644
--- a/backend/src/admin/admin.service.ts
+++ b/backend/src/admin/admin.service.ts
@@ -475,6 +475,48 @@ export class AdminService {
return this.getSettings();
}
+ async getPets(query: any) {
+ const page = Number(query.page) || 1;
+ const limit = Number(query.limit) || 10;
+ const skip = (page - 1) * limit;
+
+ const where: any = {};
+ if (query.search) {
+ where.OR = [
+ { name: { contains: query.search, mode: 'insensitive' } },
+ { breed: { contains: query.search, mode: 'insensitive' } },
+ { user: { firstName: { contains: query.search, mode: 'insensitive' } } },
+ { user: { lastName: { contains: query.search, mode: 'insensitive' } } },
+ ];
+ }
+
+ const [data, total] = await Promise.all([
+ this.prisma.pet.findMany({
+ where,
+ skip,
+ take: limit,
+ orderBy: { createdAt: 'desc' },
+ include: {
+ user: {
+ select: { id: true, firstName: true, lastName: true, mobile: true, email: true },
+ },
+ medicalConditions: true,
+ reminders: true,
+ healthLogs: {
+ orderBy: { loggedDate: 'desc' },
+ take: 10,
+ },
+ },
+ }),
+ this.prisma.pet.count({ where }),
+ ]);
+
+ return {
+ data,
+ meta: { total, page, limit, lastPage: Math.ceil(total / limit) },
+ };
+ }
+
async getDoctors() {
return this.prisma.doctor.findMany({
orderBy: { createdAt: 'desc' },
diff --git a/backend/src/pets/pets.service.ts b/backend/src/pets/pets.service.ts
index ecc22d5..b4410a4 100644
--- a/backend/src/pets/pets.service.ts
+++ b/backend/src/pets/pets.service.ts
@@ -31,6 +31,66 @@ export class PetsService {
});
}
+ async findAllAdmin(filters: PaginationDto) {
+ const {
+ search,
+ page = 1,
+ limit = 10,
+ sortBy = 'createdAt',
+ sortOrder = 'desc',
+ } = filters;
+
+ const whereClause: any = {};
+ if (search) {
+ whereClause.OR = [
+ { name: { contains: search, mode: 'insensitive' } },
+ { breed: { contains: search, mode: 'insensitive' } },
+ { user: { firstName: { contains: search, mode: 'insensitive' } } },
+ { user: { lastName: { contains: search, mode: 'insensitive' } } },
+ { user: { mobile: { contains: search } } },
+ ];
+ }
+
+ const skip = (page - 1) * limit;
+
+ const [data, total] = await Promise.all([
+ this.prisma.pet.findMany({
+ where: whereClause,
+ skip,
+ take: limit,
+ orderBy: { [sortBy]: sortOrder },
+ include: {
+ user: {
+ select: {
+ id: true,
+ firstName: true,
+ lastName: true,
+ mobile: true,
+ email: true,
+ },
+ },
+ medicalConditions: true,
+ reminders: true,
+ healthLogs: {
+ orderBy: { loggedDate: 'desc' },
+ take: 10,
+ },
+ },
+ }),
+ this.prisma.pet.count({ where: whereClause }),
+ ]);
+
+ return {
+ data,
+ meta: {
+ total,
+ page,
+ lastPage: Math.ceil(total / limit),
+ limit,
+ },
+ };
+ }
+
async findAllByUser(userId: string, filters: PaginationDto) {
const {
search,
@@ -56,6 +116,7 @@ export class PetsService {
skip,
take: limit,
orderBy: { [sortBy]: sortOrder },
+ include: { medicalConditions: true, reminders: true, healthLogs: true },
}),
this.prisma.pet.count({ where: whereClause }),
]);
diff --git a/docs/backlog.md b/docs/backlog.md
index 5975516..320ff8e 100644
--- a/docs/backlog.md
+++ b/docs/backlog.md
@@ -214,7 +214,7 @@
- [x] **TASK-26.14 [Admin Wallet Adjust Endpoint]:** ایجاد اندپویینت `POST /api/users/:id/wallet-adjust` و فرم شارژ/کسر مستقیم کیفپول در پنل ادمین.
- [x] **TASK-26.15 [Admin User Details, Addresses & Transactions Modal]:** امکان مشاهده کامل آدرسها و ریزتراکنشهای کیفپول کاربر در پنل ادمین.
- [x] **TASK-26.16 [Atomic Prisma Transaction for Wallet Top-up]:** اتمیک کردن عملیات شارژ کیفپول در دیتابیس با `prisma.$transaction`.
-- [ ] **TASK-26.17 [Admin Pet Health Records & Pagination]:** امکان مشاهده پرونده پزشکی، یادآورها و لاگهای سلامت پت در پنل ادمین همراه با پجینیشن کامل لیست پتها.
+- [x] **TASK-26.17 [Admin Pet Health Records & Pagination]:** امکان مشاهده پرونده پزشکی، یادآورها و لاگهای سلامت پت در پنل ادمین همراه با پجینیشن کامل لیست پتها.
- [ ] **TASK-26.18 [Pet Image File Upload]:** امکان آپلود مستقیم فایل تصویر پت به سرور در فرمهای فرانتاند.
- [ ] **TASK-26.19 [Atomic Order Creation Transaction & Refill Cron Job]:** اتمیک کردن ساخت سفارش با کیفپول در بکاند و ایجاد Cron Job روزانه برای پیامک خودکار یادآوری Refill.
- [ ] **TASK-26.20 [Reports Excel Export, Dashboard Live Chart & Video View Counter]:** افزودن قابلیت دانلود خروجی اکسل در صفحه گزارشات، اتصال نمودار داشبورد به دیتای واقعی و ارسال درخواست PATCH افزایش بازدید ویدیوها.