10 KiB
نمایش تعلق دکتر به نماینده + صفحهی نماینده با تعداد دکتر و نوبت
زمینه
هر دکتر از طریق ستون doctors.representation_id (FK به representations.id) به یک نماینده وصل میشود؛ این ستون از قبل وجود دارد و هنگام ثبتِ دکتر توسط نماینده مقداردهی میشود (RepresentationActionController::registerDoctor → setRepresentationId). اما این تعلق هیچجا در پنل ادمین دیده نمیشود: نه در لیست دکترها، نه در پروفایل دکتر، و صفحهی نمایندهها تعداد دکتر و نوبت نماینده را نشان نمیدهد. هدف: مرئیکردن این رابطه در ادمین. نیازی به migration نیست — فقط expose و UI.
مشکل / هدف
- در لیست و پروفایل دکترِ ادمین، مشخص باشد دکتر به کدام نماینده تعلق دارد (نام + لینک به نماینده).
- در صفحهی نمایندهها (
RepresentationsPage) هر نماینده ستون «تعداد پزشکان» و «تعداد نوبت» داشته باشد. - در جزئیات نماینده (
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):
$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):
$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):
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):
$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 کن و دو ستون اضافه کن:
"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 خروجی:
'representation_id' => $d['representation_id'] !== null ? (int) $d['representation_id'] : null,
'representation_name' => $d['representation_name'] ?? null,
۳. Backend — لیست نمایندهها: تعداد دکتر + نوبت
در AdminApiController::representations بعد از گرفتن $rows و $repIds، دو شمارش گروهی با یک کوئری هرکدام بگیر (نه N+1):
$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'];
}
}
و در آیتم خروجی:
'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-nesteddata?.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.