feat(representation): add endpoints for doctor statistics and toggling doctor status
This commit is contained in:
@@ -114,10 +114,14 @@ export default function DoctorsPage() {
|
|||||||
// ── Queries ──
|
// ── Queries ──
|
||||||
|
|
||||||
const statsQ = useQuery({
|
const statsQ = useQuery({
|
||||||
queryKey: ['doctors-stats'],
|
queryKey: ['doctors-stats', isRepresentation],
|
||||||
queryFn: () => api.get<ApiResponse<DoctorStats>>('/api/v1/admin/doctors/stats'),
|
queryFn: () => {
|
||||||
|
const url = isRepresentation
|
||||||
|
? '/api/v1/representation/doctors/stats'
|
||||||
|
: '/api/v1/admin/doctors/stats';
|
||||||
|
return api.get<ApiResponse<DoctorStats>>(url);
|
||||||
|
},
|
||||||
staleTime: 30_000,
|
staleTime: 30_000,
|
||||||
enabled: !isRepresentation,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const specialtiesQ = useQuery({
|
const specialtiesQ = useQuery({
|
||||||
@@ -163,7 +167,10 @@ export default function DoctorsPage() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const toggleMut = useMutation({
|
const toggleMut = useMutation({
|
||||||
mutationFn: (uuid: string) => api.post<ApiResponse<{ is_active: boolean }>>(`/api/v1/admin/doctors/${uuid}/status`, {}),
|
mutationFn: (uuid: string) => {
|
||||||
|
const base = isRepresentation ? '/api/v1/representation/doctors' : '/api/v1/admin/doctors';
|
||||||
|
return api.post<ApiResponse<{ is_active: boolean }>>(`${base}/${uuid}/status`, {});
|
||||||
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success('وضعیت پزشک تغییر کرد');
|
toast.success('وضعیت پزشک تغییر کرد');
|
||||||
qc.invalidateQueries({ queryKey: ['admin-doctors'] });
|
qc.invalidateQueries({ queryKey: ['admin-doctors'] });
|
||||||
|
|||||||
@@ -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`
|
### GET `/api/v1/representation/clinics`
|
||||||
|
|
||||||
کلینیکهای ثبتشده توسط نمایندهی جاری (فقط `representation_id = نمایندهی کاربر جاری`). شکل آیتم سازگار با `GET /api/v1/admin/clinics`.
|
کلینیکهای ثبتشده توسط نمایندهی جاری (فقط `representation_id = نمایندهی کاربر جاری`). شکل آیتم سازگار با `GET /api/v1/admin/clinics`.
|
||||||
|
|||||||
@@ -391,4 +391,64 @@ class RepresentationActionController extends BaseController
|
|||||||
|
|
||||||
return $this->paginated($items, (int) $total, $page, $limit);
|
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()]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user