From b7df8cf9eeda91854202b499d04aeb9c8c993b83 Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Wed, 24 Jun 2026 12:24:53 +0330 Subject: [PATCH] feat(representation): add endpoints for doctor statistics and toggling doctor status --- assets/admin/pages/DoctorsPage.tsx | 15 +++-- docs/api/representation.md | 42 +++++++++++++ .../RepresentationActionController.php | 60 +++++++++++++++++++ 3 files changed, 113 insertions(+), 4 deletions(-) diff --git a/assets/admin/pages/DoctorsPage.tsx b/assets/admin/pages/DoctorsPage.tsx index 728bab6f..06f0980e 100644 --- a/assets/admin/pages/DoctorsPage.tsx +++ b/assets/admin/pages/DoctorsPage.tsx @@ -114,10 +114,14 @@ export default function DoctorsPage() { // ── Queries ── const statsQ = useQuery({ - queryKey: ['doctors-stats'], - queryFn: () => api.get>('/api/v1/admin/doctors/stats'), + queryKey: ['doctors-stats', isRepresentation], + queryFn: () => { + const url = isRepresentation + ? '/api/v1/representation/doctors/stats' + : '/api/v1/admin/doctors/stats'; + return api.get>(url); + }, staleTime: 30_000, - enabled: !isRepresentation, }); const specialtiesQ = useQuery({ @@ -163,7 +167,10 @@ export default function DoctorsPage() { }); const toggleMut = useMutation({ - mutationFn: (uuid: string) => api.post>(`/api/v1/admin/doctors/${uuid}/status`, {}), + mutationFn: (uuid: string) => { + const base = isRepresentation ? '/api/v1/representation/doctors' : '/api/v1/admin/doctors'; + return api.post>(`${base}/${uuid}/status`, {}); + }, onSuccess: () => { toast.success('وضعیت پزشک تغییر کرد'); qc.invalidateQueries({ queryKey: ['admin-doctors'] }); diff --git a/docs/api/representation.md b/docs/api/representation.md index 201f7058..622895d4 100644 --- a/docs/api/representation.md +++ b/docs/api/representation.md @@ -398,6 +398,48 @@ Get yearly earnings dashboard for a representation. --- +### GET `/api/v1/representation/doctors/stats` + +آمار پزشکانِ ثبت‌شده توسط نماینده‌ی جاری (فقط `representation_id = نماینده‌ی کاربر جاری`). شکل پاسخ سازگار با `GET /api/v1/admin/doctors/stats` (بدون `top_specialty`). فرانت‌اند کارت‌های «کل پزشکان / فعال / غیرفعال / مرد / زن» را از این endpoint برای نقش نماینده پر می‌کند. + +> **Permission:** `ROLE_REPRESENTATION` — id نماینده از `#[CurrentUser]`. + +#### Response `200` +```json +{ + "success": true, + "data": { "total": 12, "active": 9, "inactive": 3, "male": 7, "female": 5 } +} +``` +#### Errors +| Code | HTTP | Description | +|------|------|-------------| +| `ERR_NOT_FOUND_001` | 404 | کاربر جاری نماینده نیست | + +--- + +### POST `/api/v1/representation/doctors/{uuid}/status` + +فعال/غیرفعال کردن پزشکِ زیرمجموعه‌ی نماینده‌ی جاری (toggle `active_doctor_appointment`). فقط روی پزشکانی که `representation_id` آن‌ها برابر نماینده‌ی کاربر جاری است؛ در غیر این صورت 404. + +> **Permission:** `ROLE_REPRESENTATION` — مالکیت از `#[CurrentUser]` چک می‌شود. + +#### Path Parameters +| Param | Type | Description | +|-------|------|-------------| +| `uuid` | string | uuid پزشک | + +#### Response `200` +```json +{ "success": true, "data": { "is_active": false } } +``` +#### Errors +| Code | HTTP | Description | +|------|------|-------------| +| `ERR_NOT_FOUND_001` | 404 | کاربر جاری نماینده نیست، یا پزشک یافت نشد / متعلق به این نماینده نیست | + +--- + ### GET `/api/v1/representation/clinics` کلینیک‌های ثبت‌شده توسط نماینده‌ی جاری (فقط `representation_id = نماینده‌ی کاربر جاری`). شکل آیتم سازگار با `GET /api/v1/admin/clinics`. diff --git a/src/Representation/Controller/RepresentationActionController.php b/src/Representation/Controller/RepresentationActionController.php index e7d5e010..fe1168ad 100644 --- a/src/Representation/Controller/RepresentationActionController.php +++ b/src/Representation/Controller/RepresentationActionController.php @@ -391,4 +391,64 @@ class RepresentationActionController extends BaseController return $this->paginated($items, (int) $total, $page, $limit); } + + #[OA\Get( + path: '/api/v1/representation/doctors/stats', + summary: 'آمار پزشکانِ ثبت‌شده توسط نماینده‌ی جاری (شکلِ یکسان با /admin/doctors/stats)', + security: [['bearerAuth' => []]], + responses: [new OA\Response(response: 200, description: 'آمار پزشکان نماینده')] + )] + #[Route('/api/v1/representation/doctors/stats', methods: ['GET'])] + public function doctorStats(#[CurrentUser] User $user): JsonResponse + { + $rep = $this->representationRepo->findByUser($user); + if ($rep === null) { + return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'نماینده‌ای برای این کاربر یافت نشد', 404); + } + + $conn = $this->em->getConnection(); + $repId = $rep->getId(); + + $total = (int) $conn->fetchOne('SELECT COUNT(*) FROM doctors WHERE representation_id = ?', [$repId]); + $active = (int) $conn->fetchOne('SELECT COUNT(*) FROM doctors WHERE representation_id = ? AND active_doctor_appointment = 1', [$repId]); + $male = (int) $conn->fetchOne("SELECT COUNT(*) FROM doctors WHERE representation_id = ? AND gender IN ('man','male')", [$repId]); + $female = (int) $conn->fetchOne("SELECT COUNT(*) FROM doctors WHERE representation_id = ? AND gender IN ('woman','female')", [$repId]); + + return $this->success([ + 'total' => $total, + 'active' => $active, + 'inactive' => $total - $active, + 'male' => $male, + 'female' => $female, + ]); + } + + #[OA\Post( + path: '/api/v1/representation/doctors/{uuid}/status', + summary: 'فعال/غیرفعال کردن پزشکِ زیرمجموعه‌ی نماینده‌ی جاری', + security: [['bearerAuth' => []]], + parameters: [new OA\Parameter(name: 'uuid', in: 'path', required: true, schema: new OA\Schema(type: 'string'))], + responses: [ + new OA\Response(response: 200, description: 'وضعیت جدید پزشک'), + new OA\Response(response: 404, description: 'پزشک یافت نشد یا متعلق به این نماینده نیست'), + ] + )] + #[Route('/api/v1/representation/doctors/{uuid}/status', methods: ['POST'])] + public function toggleDoctorStatus(string $uuid, #[CurrentUser] User $user): JsonResponse + { + $rep = $this->representationRepo->findByUser($user); + if ($rep === null) { + return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'نماینده‌ای برای این کاربر یافت نشد', 404); + } + + $doctor = $this->em->getRepository(Doctor::class)->findOneBy(['uuid' => $uuid]); + if ($doctor === null || $doctor->getRepresentationId() !== $rep->getId()) { + return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'پزشک یافت نشد', 404); + } + + $doctor->setActiveDoctorAppointment(!$doctor->isActiveDoctorAppointment()); + $this->em->flush(); + + return $this->success(['is_active' => $doctor->isActiveDoctorAppointment()]); + } }