feat(representation): expose doctor representation details and add counts in admin views
This commit is contained in:
@@ -333,6 +333,10 @@ class AdminApiController extends BaseController
|
||||
$where[] = 'EXISTS (SELECT 1 FROM doctor_specialties ds2 WHERE ds2.doctor_id = d.id AND ds2.specialty_id = :specId)';
|
||||
$params['specId'] = $specId;
|
||||
}
|
||||
// پزشکانِ بدون نماینده — برای انتخاب و اتصال به یک نماینده.
|
||||
if ($request->query->get('unassigned') === '1') {
|
||||
$where[] = 'd.representation_id IS NULL';
|
||||
}
|
||||
|
||||
$whereStr = implode(' AND ', $where);
|
||||
$orderBy = match ($sort) {
|
||||
@@ -352,8 +356,10 @@ class AdminApiController extends BaseController
|
||||
d.mobile_number as doctor_mobile, d.active_doctor_appointment,
|
||||
d.doctor_rate, d.doctor_rate_percentage, d.images, d.created_at,
|
||||
d.owner_status, d.source,
|
||||
d.representation_id, rep.uuid AS representation_uuid, 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",
|
||||
$params
|
||||
);
|
||||
@@ -386,6 +392,9 @@ class AdminApiController extends BaseController
|
||||
'owner_status' => $d['owner_status'],
|
||||
'source' => $d['source'],
|
||||
'rate' => (float) $d['doctor_rate'],
|
||||
'representation_id' => $d['representation_id'] !== null ? (int) $d['representation_id'] : null,
|
||||
'representation_uuid' => $d['representation_uuid'] ?? null,
|
||||
'representation_name' => $d['representation_name'] ?? null,
|
||||
'specialties' => $specMap[(int) $d['id']] ?? [],
|
||||
'profile_image' => !empty($images) ? ($images[0]['url'] ?? null) : null,
|
||||
'created_at' => date('c', (int) $d['created_at']),
|
||||
@@ -1058,7 +1067,30 @@ class AdminApiController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
$items = array_map(function (array $r) use ($cityNames) {
|
||||
// تعداد پزشکان و نوبتهای هر نماینده در دو کوئری گروهی (بدون N+1).
|
||||
$docCounts = [];
|
||||
$apptCounts = [];
|
||||
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'];
|
||||
}
|
||||
}
|
||||
|
||||
$items = array_map(function (array $r) use ($cityNames, $docCounts, $apptCounts) {
|
||||
$cities = $cityNames[(int) $r['id']] ?? [];
|
||||
return [
|
||||
'id' => (int) $r['id'],
|
||||
@@ -1073,6 +1105,8 @@ class AdminApiController extends BaseController
|
||||
'city' => $cities !== [] ? implode('، ', array_column($cities, 'name')) : null,
|
||||
'commission_percent' => (float) $r['commissionPercent'],
|
||||
'wallet_balance' => 0,
|
||||
'doctor_count' => $docCounts[(int) $r['id']] ?? 0,
|
||||
'appointment_count' => $apptCounts[(int) $r['id']] ?? 0,
|
||||
'is_active' => (bool) $r['active'],
|
||||
'created_at' => date('c', (int) $r['createdAt']),
|
||||
];
|
||||
@@ -1081,6 +1115,46 @@ class AdminApiController extends BaseController
|
||||
return $this->paginated($items, (int) $total, $page, $limit);
|
||||
}
|
||||
|
||||
#[Route('/api/v1/admin/representations/{uuid}/doctors', methods: ['GET'])]
|
||||
public function representationDoctors(string $uuid, Request $request, RepresentationRepository $repRepo): JsonResponse
|
||||
{
|
||||
$rep = $repRepo->findByUuid($uuid);
|
||||
if ($rep === null) {
|
||||
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'نماینده یافت نشد', 404);
|
||||
}
|
||||
|
||||
$page = max(1, (int) $request->query->get('page', 1));
|
||||
$limit = min(100, max(1, (int) $request->query->get('limit', 15)));
|
||||
$repId = $rep->getId();
|
||||
|
||||
$conn = $this->em->getConnection();
|
||||
$total = (int) $conn->fetchOne('SELECT COUNT(*) FROM doctors WHERE representation_id = ?', [$repId]);
|
||||
|
||||
$offset = ($page - 1) * $limit;
|
||||
$rows = $conn->fetchAllAssociative(
|
||||
"SELECT d.id, d.uuid, d.name, d.gender, d.medical_system_code,
|
||||
d.active_doctor_appointment, d.owner_status, d.created_at,
|
||||
(SELECT COUNT(*) FROM appointments a WHERE a.doctor_id = d.id) AS appointment_count
|
||||
FROM doctors d
|
||||
WHERE d.representation_id = ? ORDER BY d.created_at DESC LIMIT $limit OFFSET $offset",
|
||||
[$repId]
|
||||
);
|
||||
|
||||
$items = array_map(fn(array $d): array => [
|
||||
'uuid' => $d['uuid'],
|
||||
'id' => (int) $d['id'],
|
||||
'name' => $d['name'],
|
||||
'gender' => $d['gender'],
|
||||
'medical_code' => $d['medical_system_code'],
|
||||
'is_active' => (bool) $d['active_doctor_appointment'],
|
||||
'owner_status' => $d['owner_status'],
|
||||
'appointment_count' => (int) $d['appointment_count'],
|
||||
'created_at' => date('c', (int) $d['created_at']),
|
||||
], $rows);
|
||||
|
||||
return $this->paginated($items, $total, $page, $limit);
|
||||
}
|
||||
|
||||
// ── Financial Breakdowns ──────────────────────────────────────────────────
|
||||
|
||||
#[OA\Get(
|
||||
|
||||
Reference in New Issue
Block a user