Files
clinicpro/.claude/prompt/doctor-representation-visibility.md
T

145 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# نمایش تعلق دکتر به نماینده + صفحه‌ی نماینده با تعداد دکتر و نوبت
## زمینه
هر دکتر از طریق ستون `doctors.representation_id` (FK به `representations.id`) به یک نماینده وصل می‌شود؛ این ستون **از قبل وجود دارد** و هنگام ثبتِ دکتر توسط نماینده مقداردهی می‌شود (`RepresentationActionController::registerDoctor``setRepresentationId`). اما این تعلق **هیچ‌جا در پنل ادمین دیده نمی‌شود**: نه در لیست دکترها، نه در پروفایل دکتر، و صفحه‌ی نماینده‌ها تعداد دکتر و نوبت نماینده را نشان نمی‌دهد. هدف: مرئی‌کردن این رابطه در ادمین. **نیازی به migration نیست** — فقط expose و UI.
## مشکل / هدف
1. در لیست و پروفایل دکترِ ادمین، مشخص باشد دکتر به کدام نماینده تعلق دارد (نام + لینک به نماینده).
2. در صفحه‌ی نماینده‌ها (`RepresentationsPage`) هر نماینده ستون «تعداد پزشکان» و «تعداد نوبت» داشته باشد.
3. در جزئیات نماینده (`RepresentationDetailPage`) بشود پزشکان زیرمجموعه و نوبت‌های آن نماینده را دید.
## فایل‌های مرتبط
| فایل | نقش |
|------|-----|
| `src/Doctor/Entity/Doctor.php` | `representationId` (ستون L76-77، getter L217)؛ `toDetailArray()` L530 — الان نماینده را برنمی‌گرداند |
| `src/Doctor/Controller/DoctorController.php` | `show()` L149-173 — `GET /api/v1/doctor/{uuid}`؛ پنل ادمینِ پروفایل دکتر از همین می‌خواند (`DoctorDetailPage.tsx:2363`) |
| `src/Admin/Controller/AdminApiController.php` | `doctorsList` (SELECT L350-359)؛ `representations` (L1016-1082) |
| `src/Representation/Repository/RepresentationRepository.php` | برای resolve نامِ نماینده از روی id |
| `assets/admin/pages/DoctorsPage.tsx` | لیست دکتر ادمین (fetch L144) |
| `assets/admin/pages/DoctorDetailPage.tsx` | پروفایل دکتر ادمین (fetch L2358-2364) |
| `assets/admin/pages/RepresentationsPage.tsx` | لیست نماینده‌ها (fetch L51-56) |
| `assets/admin/pages/RepresentationDetailPage.tsx` | جزئیات نماینده (fetch `/api/v1/representation/{uuid}` L42) |
| `docs/api/admin.md`, `docs/api/doctor.md` | مستندسازی |
## وضعیت فعلی
**الگوی aggregateِ آماده (برای adaptation)** — شمارش دکتر نماینده (`RepresentationActionController.php:606`):
```php
$total = (int) $conn->fetchOne('SELECT COUNT(*) FROM doctors WHERE representation_id = ?', [$repId]);
```
نوبت‌های نماینده (`RepresentationActionController.php:673` تقریبی): `JOIN a.doctor d WHERE d.representationId = :repId`.
**لیست دکتر ادمین** الان نماینده را select نمی‌کند (`AdminApiController.php:350-359`):
```php
$rows = $conn->fetchAllAssociative(
"SELECT d.id, d.uuid, d.name, ..., d.owner_status, d.source,
u.mobile_number as user_mobile, u.email
FROM doctors d JOIN users u ON u.id = d.user_id
WHERE $whereStr ORDER BY $orderBy LIMIT $limit OFFSET $offset", $params);
```
**لیست نماینده‌ها** بدون تعداد دکتر/نوبت (`AdminApiController.php:1063-1078`):
```php
return [
'id' => (int) $r['id'], ...,
'commission_percent' => (float) $r['commissionPercent'],
'wallet_balance' => 0,
'is_active' => (bool) $r['active'],
'created_at' => date('c', (int) $r['createdAt']),
];
```
**پروفایل دکترِ ادمین** از `toDetailArray()` تغذیه می‌شود که `representation` ندارد.
## وظایف
### ۱. Backend — expose نماینده روی جزئیات دکتر (`show()`)
در `DoctorController::show()` بعد از ساخت آرایه‌ی دکتر، نماینده را از روی `representation_id` resolve کن و merge کن (مثل الگوی `clinics` که همان‌جا merge می‌شود). `null` اگر دکتر نماینده ندارد (مثل ایمپورت‌های IRIMC):
```php
$repId = $doctor->getRepresentationId();
$rep = $repId ? $this->representationRepo->find($repId) : null;
$data = array_merge($doctor->toDetailArray($schedule), [
'clinics' => $clinicData,
'representation' => $rep ? ['id' => $rep->getId(), 'uuid' => $rep->getUuid(), 'full_name' => $rep->getFullName()] : null,
]);
```
`RepresentationRepository` را به constructor تزریق کن (اگر نیست). پاسخ همچنان double-nested است؛ شکل را نشکن.
### ۲. Backend — لیست دکتر ادمین: افزودن نماینده
در `AdminApiController::doctorsList` کوئری را `LEFT JOIN representations` کن و دو ستون اضافه کن:
```php
"SELECT d.id, ..., d.owner_status, d.source,
d.representation_id, rep.full_name AS representation_name,
u.mobile_number as user_mobile, u.email
FROM doctors d JOIN users u ON u.id = d.user_id
LEFT JOIN representations rep ON rep.id = d.representation_id
WHERE $whereStr ORDER BY $orderBy LIMIT $limit OFFSET $offset"
```
و در `array_map` خروجی:
```php
'representation_id' => $d['representation_id'] !== null ? (int) $d['representation_id'] : null,
'representation_name' => $d['representation_name'] ?? null,
```
### ۳. Backend — لیست نماینده‌ها: تعداد دکتر + نوبت
در `AdminApiController::representations` بعد از گرفتن `$rows` و `$repIds`، دو شمارش گروهی با یک کوئری هرکدام بگیر (نه N+1):
```php
$docCounts = []; // representation_id => count
$apptCounts = []; // representation_id => count
if ($repIds !== []) {
foreach ($this->em->getConnection()->fetchAllAssociative(
'SELECT representation_id, COUNT(*) c FROM doctors
WHERE representation_id IN (?) GROUP BY representation_id',
[$repIds], [\Doctrine\DBAL\ArrayParameterType::INTEGER]) as $row) {
$docCounts[(int) $row['representation_id']] = (int) $row['c'];
}
foreach ($this->em->getConnection()->fetchAllAssociative(
'SELECT d.representation_id, COUNT(*) c FROM appointments a
JOIN doctors d ON d.id = a.doctor_id
WHERE d.representation_id IN (?) GROUP BY d.representation_id',
[$repIds], [\Doctrine\DBAL\ArrayParameterType::INTEGER]) as $row) {
$apptCounts[(int) $row['representation_id']] = (int) $row['c'];
}
}
```
و در آیتم خروجی:
```php
'doctor_count' => $docCounts[(int) $r['id']] ?? 0,
'appointment_count' => $apptCounts[(int) $r['id']] ?? 0,
```
### ۴. Backend — endpoint پزشکان یک نماینده (برای صفحه‌ی جزئیات)
یک endpoint ادمین اضافه کن: `GET /api/v1/admin/representations/{uuid}/doctors` (paginated) که پزشکان `representation_id = rep.id` را برمی‌گرداند (همان الگوی `d.representation_id = :repId`). `$this->paginated(...)`. برای نوبت‌ها می‌توانی از `appointment_count` تجمیعی وظیفه‌ی ۳ در خود صفحه استفاده کنی، یا در صورت نیاز endpoint مشابه `.../appointments` بساز.
### ۵. Frontend — ستون «نماینده» در لیست و کارت در پروفایل دکتر
- `DoctorsPage.tsx`: به `AdminDoctor` تایپ `representation_id`/`representation_name` اضافه کن و یک ستون «نماینده» در `<DataTable>` نشان بده؛ اگر null → «بدون نماینده». (فقط حالت ادمین؛ نقش representation خودش زیرمجموعه است.)
- `DoctorDetailPage.tsx`: یک بخش/کارت «نماینده» با نام و لینک به `/representations/${representation.uuid}` از `data.representation` بساز؛ اگر null → «این پزشک به نماینده‌ای متصل نیست».
### ۶. Frontend — ستون‌های نماینده + بخش پزشکان/نوبت‌ها
- `RepresentationsPage.tsx`: دو ستون «تعداد پزشکان» (`doctor_count`) و «تعداد نوبت» (`appointment_count`) در جدول.
- `RepresentationDetailPage.tsx`: یک بخش «پزشکان این نماینده» با `useQuery` روی `/api/v1/admin/representations/${uuid}/doctors` (paginated: items از `data?.data`) و نمایش `appointment_count`/`doctor_count`.
### ۷. مستندسازی
- `docs/api/admin.md`: فیلدهای جدید `doctor_count`/`appointment_count` در پاسخ `representations`؛ فیلدهای `representation_id`/`representation_name` در `doctors`؛ endpoint جدید `GET /api/v1/admin/representations/{uuid}/doctors`.
- `docs/api/doctor.md`: فیلد `representation` در پاسخ single (`show`).
## نکات مهم
- **بدون migration**: `representation_id` از قبل روی `doctors` هست (Doctor.php:76). فقط expose.
- **ایمپورت‌های IRIMC** `representation_id = NULL` دارند (فقط `managed_by` ست می‌شود، `DoctorImportService.php:106`) — در UI «بدون نماینده» نشان بده؛ **auto-assign نکن** (خارج از دامنه‌ی این تسک).
- **پاسخ‌ها**: paginated → items از `data?.data`، total از `data?.meta?.totalRecords`؛ single (`show`) → double-nested `data?.data?.data`. همه‌ی endpointها از `BaseController` (`success`/`paginated`).
- شمارش‌ها گروهی (`GROUP BY ... IN (?)`) باشند تا N+1 نشود؛ همان الگوی `cityNames` موجود در `representations` (AdminApiController.php:1048).
- `show()` عمومی است و توسط `nobat724_front` هم مصرف می‌شود؛ افزودن `representation` مشکلی ایجاد نمی‌کند (سایت آن را نادیده می‌گیرد) ولی شکل پاسخ را تغییر نده.
- تاریخ‌ها Unix timestamp؛ نمایش با `formatDate()` شمسی.
- تست: `ddev exec php -l ...`، `ddev exec php bin/console cache:clear`، `ddev exec yarn dev`، `ddev exec php bin/console debug:router | grep representations`.