feat(api): add dashboard endpoints for clinic, doctor, and secretary roles
- Implemented GET /api/v1/dashboard/clinic to return clinic stats and today's schedule for clinic owners. - Implemented GET /api/v1/dashboard/doctor to return doctor's stats and today's schedule for doctors. - Implemented GET /api/v1/dashboard/secretary to return stats and conditional appointments for secretaries. feat(migrations): create user_active_context and mobile_verification_otp tables - Added migration to create user_active_context table for tracking active user sessions. - Added migration to create mobile_verification_otp table for handling mobile number verification. feat(migrations): create site_config table for application settings - Added migration to create site_config table to store various site configuration settings. feat(appointments): create MyAppointmentsController for user-specific appointments - Added MyAppointmentsController to handle fetching user-specific appointments with pagination and filtering. feat(auth): implement NotificationMobileController for mobile number verification - Added NotificationMobileController to handle OTP requests and verification for mobile number changes. feat(auth): create MobileVerificationOtp entity for OTP management - Created MobileVerificationOtp entity to manage OTP records for mobile verification. feat(auth): create UserActiveContext entity for user session management - Created UserActiveContext entity to manage user active sessions. feat(config): implement SiteConfigController for managing site settings - Added SiteConfigController to handle fetching and updating site configuration settings. feat(config): create SiteConfig entity and repository for configuration management - Created SiteConfig entity and repository to manage site configuration data.
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dashboard\Controller;
|
||||
|
||||
use App\Auth\Entity\User;
|
||||
use App\Clinic\Repository\ClinicRepository;
|
||||
use App\Doctor\Repository\DoctorRepository;
|
||||
use App\Secretary\Repository\DoctorSecretaryRepository;
|
||||
use App\Shared\Constant\ErrorCodes;
|
||||
use App\Shared\Controller\BaseController;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\CurrentUser;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
class DashboardController extends BaseController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ClinicRepository $clinicRepo,
|
||||
private readonly DoctorRepository $doctorRepo,
|
||||
private readonly DoctorSecretaryRepository $secretaryRepo,
|
||||
private readonly EntityManagerInterface $em,
|
||||
) {}
|
||||
|
||||
// ── Clinic Dashboard ────────────────────────────────────────────────────
|
||||
|
||||
#[Route('/api/v1/dashboard/clinic', methods: ['GET'])]
|
||||
#[IsGranted('ROLE_CLINIC')]
|
||||
public function clinic(#[CurrentUser] User $user): JsonResponse
|
||||
{
|
||||
$clinic = $this->clinicRepo->findByUser($user);
|
||||
if ($clinic === null) {
|
||||
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'کلینیک یافت نشد', 404);
|
||||
}
|
||||
|
||||
$clinicId = $clinic->getId();
|
||||
$todayStart = strtotime('today midnight');
|
||||
$todayEnd = strtotime('tomorrow midnight') - 1;
|
||||
$monthStart = strtotime('first day of this month midnight');
|
||||
|
||||
// آمار نوبتهای امروز و این ماه
|
||||
$stats = $this->em->createQuery('
|
||||
SELECT
|
||||
COUNT(a.id) AS today_appointments,
|
||||
SUM(CASE WHEN a.slotStart >= :monthStart THEN 1 ELSE 0 END) AS this_month_appointments
|
||||
FROM App\Appointment\Entity\Appointment a
|
||||
JOIN App\Doctor\Entity\Doctor d WITH a.doctor = d
|
||||
JOIN App\Clinic\Entity\Clinic c WITH d MEMBER OF c.doctors
|
||||
WHERE c.id = :clinicId
|
||||
AND a.slotStart >= :todayStart AND a.slotStart <= :todayEnd
|
||||
')->setParameters([
|
||||
'clinicId' => $clinicId,
|
||||
'todayStart' => $todayStart,
|
||||
'todayEnd' => $todayEnd,
|
||||
'monthStart' => $monthStart,
|
||||
])->getOneOrNullResult() ?? [];
|
||||
|
||||
// شمارش کل نوبتهای این ماه (query جداگانه)
|
||||
$monthCount = (int) $this->em->createQuery('
|
||||
SELECT COUNT(a.id)
|
||||
FROM App\Appointment\Entity\Appointment a
|
||||
JOIN App\Doctor\Entity\Doctor d WITH a.doctor = d
|
||||
JOIN App\Clinic\Entity\Clinic c WITH d MEMBER OF c.doctors
|
||||
WHERE c.id = :clinicId
|
||||
AND a.slotStart >= :monthStart
|
||||
')->setParameters([
|
||||
'clinicId' => $clinicId,
|
||||
'monthStart' => $monthStart,
|
||||
])->getSingleScalarResult();
|
||||
|
||||
// تعداد دعوتنامههای در انتظار
|
||||
$pendingInvitations = (int) $this->em->createQuery('
|
||||
SELECT COUNT(i.id)
|
||||
FROM App\ClinicInvitation\Entity\ClinicDoctorInvitation i
|
||||
WHERE i.clinic = :clinic AND i.status = :status
|
||||
')->setParameters([
|
||||
'clinic' => $clinic,
|
||||
'status' => 'pending',
|
||||
])->getSingleScalarResult();
|
||||
|
||||
// ۵ نوبت امروز این کلینیک
|
||||
$todayAppts = $this->em->createQuery('
|
||||
SELECT a.uuid, u.realName AS patient_name, d.name AS doctor_name,
|
||||
a.slotStart AS slot_start, a.status
|
||||
FROM App\Appointment\Entity\Appointment a
|
||||
JOIN a.doctor d
|
||||
JOIN a.user u
|
||||
JOIN App\Clinic\Entity\Clinic c WITH d MEMBER OF c.doctors
|
||||
WHERE c.id = :clinicId
|
||||
AND a.slotStart >= :todayStart AND a.slotStart <= :todayEnd
|
||||
ORDER BY a.slotStart ASC
|
||||
')->setMaxResults(5)->setParameters([
|
||||
'clinicId' => $clinicId,
|
||||
'todayStart' => $todayStart,
|
||||
'todayEnd' => $todayEnd,
|
||||
])->getArrayResult();
|
||||
|
||||
// لیست پزشکان با شمارش نوبت امروز
|
||||
$doctors = $this->em->createQuery('
|
||||
SELECT d.uuid, d.name,
|
||||
COUNT(a.id) AS today_count
|
||||
FROM App\Doctor\Entity\Doctor d
|
||||
JOIN App\Clinic\Entity\Clinic c WITH d MEMBER OF c.doctors
|
||||
LEFT JOIN App\Appointment\Entity\Appointment a
|
||||
WITH a.doctor = d
|
||||
AND a.slotStart >= :todayStart
|
||||
AND a.slotStart <= :todayEnd
|
||||
WHERE c.id = :clinicId
|
||||
GROUP BY d.id
|
||||
')->setParameters([
|
||||
'clinicId' => $clinicId,
|
||||
'todayStart' => $todayStart,
|
||||
'todayEnd' => $todayEnd,
|
||||
])->getArrayResult();
|
||||
|
||||
return $this->success([
|
||||
'clinic' => [
|
||||
'uuid' => $clinic->getUuid(),
|
||||
'name' => $clinic->getName(),
|
||||
'is_active' => $clinic->isActive(),
|
||||
'logo' => $clinic->getClinicLogo(),
|
||||
],
|
||||
'stats' => [
|
||||
'total_doctors' => count($doctors),
|
||||
'today_appointments' => (int) ($stats['today_appointments'] ?? 0),
|
||||
'this_month_appointments' => $monthCount,
|
||||
'pending_invitations' => $pendingInvitations,
|
||||
],
|
||||
'today_appointments' => $todayAppts,
|
||||
'doctors' => $doctors,
|
||||
]);
|
||||
}
|
||||
|
||||
// ── Doctor Dashboard ─────────────────────────────────────────────────────
|
||||
|
||||
#[Route('/api/v1/dashboard/doctor', methods: ['GET'])]
|
||||
#[IsGranted('ROLE_DOCTOR')]
|
||||
public function doctor(#[CurrentUser] User $user): JsonResponse
|
||||
{
|
||||
$doctor = $this->doctorRepo->findByUser($user);
|
||||
if ($doctor === null) {
|
||||
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'دکتر یافت نشد', 404);
|
||||
}
|
||||
|
||||
$doctorId = $doctor->getId();
|
||||
$todayStart = strtotime('today midnight');
|
||||
$todayEnd = strtotime('tomorrow midnight') - 1;
|
||||
$tmrStart = strtotime('tomorrow midnight');
|
||||
$tmrEnd = strtotime('tomorrow midnight') + 86399;
|
||||
$monthStart = strtotime('first day of this month midnight');
|
||||
|
||||
// آمار
|
||||
$todayCount = (int) $this->em->createQuery('
|
||||
SELECT COUNT(a.id) FROM App\Appointment\Entity\Appointment a
|
||||
WHERE a.doctor = :doctor AND a.slotStart >= :s AND a.slotStart <= :e
|
||||
')->setParameters(['doctor' => $doctor, 's' => $todayStart, 'e' => $todayEnd])
|
||||
->getSingleScalarResult();
|
||||
|
||||
$tmrCount = (int) $this->em->createQuery('
|
||||
SELECT COUNT(a.id) FROM App\Appointment\Entity\Appointment a
|
||||
WHERE a.doctor = :doctor AND a.slotStart >= :s AND a.slotStart <= :e
|
||||
')->setParameters(['doctor' => $doctor, 's' => $tmrStart, 'e' => $tmrEnd])
|
||||
->getSingleScalarResult();
|
||||
|
||||
$monthCount = (int) $this->em->createQuery('
|
||||
SELECT COUNT(a.id) FROM App\Appointment\Entity\Appointment a
|
||||
WHERE a.doctor = :doctor AND a.slotStart >= :s
|
||||
')->setParameters(['doctor' => $doctor, 's' => $monthStart])
|
||||
->getSingleScalarResult();
|
||||
|
||||
// میانگین و تعداد امتیاز
|
||||
$ratingRow = $this->em->createQuery('
|
||||
SELECT AVG(r.score) AS avg_score, COUNT(r.id) AS total
|
||||
FROM App\Rating\Entity\Rate r WHERE r.doctor = :doctor
|
||||
')->setParameter('doctor', $doctor)->getOneOrNullResult() ?? [];
|
||||
|
||||
// نوبتهای امروز
|
||||
$todayAppts = $this->em->createQuery('
|
||||
SELECT a.uuid, u.realName AS patient_name, u.mobileNumber AS patient_mobile,
|
||||
a.slotStart AS slot_start, a.status
|
||||
FROM App\Appointment\Entity\Appointment a
|
||||
JOIN a.user u
|
||||
WHERE a.doctor = :doctor AND a.slotStart >= :s AND a.slotStart <= :e
|
||||
ORDER BY a.slotStart ASC
|
||||
')->setMaxResults(10)->setParameters([
|
||||
'doctor' => $doctor,
|
||||
's' => $todayStart,
|
||||
'e' => $todayEnd,
|
||||
])->getArrayResult();
|
||||
|
||||
// کلینیکهای عضو
|
||||
$clinics = $this->em->createQuery('
|
||||
SELECT c.uuid, c.name, c.clinicLogo AS logo
|
||||
FROM App\Clinic\Entity\Clinic c
|
||||
JOIN c.doctors d
|
||||
WHERE d.id = :doctorId
|
||||
')->setParameter('doctorId', $doctorId)->getArrayResult();
|
||||
|
||||
return $this->success([
|
||||
'doctor' => [
|
||||
'uuid' => $doctor->getUuid(),
|
||||
'name' => $doctor->getName(),
|
||||
'degree' => $doctor->getDegree(),
|
||||
],
|
||||
'stats' => [
|
||||
'today_appointments' => $todayCount,
|
||||
'tomorrow_appointments' => $tmrCount,
|
||||
'this_month_appointments' => $monthCount,
|
||||
'avg_rating' => $ratingRow['avg_score'] ? round((float) $ratingRow['avg_score'], 1) : null,
|
||||
'total_ratings' => (int) ($ratingRow['total'] ?? 0),
|
||||
],
|
||||
'today_appointments' => $todayAppts,
|
||||
'clinics' => $clinics,
|
||||
]);
|
||||
}
|
||||
|
||||
// ── Secretary Dashboard ──────────────────────────────────────────────────
|
||||
|
||||
#[Route('/api/v1/dashboard/secretary', methods: ['GET'])]
|
||||
#[IsGranted('ROLE_SECRETARY')]
|
||||
public function secretary(#[CurrentUser] User $user): JsonResponse
|
||||
{
|
||||
$rel = $this->secretaryRepo->findActiveBySecretary($user);
|
||||
if ($rel === null) {
|
||||
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'دسترسی منشی تنظیم نشده', 403);
|
||||
}
|
||||
|
||||
$doctor = $rel->getDoctor();
|
||||
$permissions = $rel->getPermissions();
|
||||
$canView = (bool) ($permissions['resources']['appointments']['view'] ?? false);
|
||||
|
||||
$todayStart = strtotime('today midnight');
|
||||
$todayEnd = strtotime('tomorrow midnight') - 1;
|
||||
$tmrStart = strtotime('tomorrow midnight');
|
||||
$tmrEnd = strtotime('tomorrow midnight') + 86399;
|
||||
|
||||
$todayCount = (int) $this->em->createQuery('
|
||||
SELECT COUNT(a.id) FROM App\Appointment\Entity\Appointment a
|
||||
WHERE a.doctor = :doctor AND a.slotStart >= :s AND a.slotStart <= :e
|
||||
')->setParameters(['doctor' => $doctor, 's' => $todayStart, 'e' => $todayEnd])
|
||||
->getSingleScalarResult();
|
||||
|
||||
$tmrCount = (int) $this->em->createQuery('
|
||||
SELECT COUNT(a.id) FROM App\Appointment\Entity\Appointment a
|
||||
WHERE a.doctor = :doctor AND a.slotStart >= :s AND a.slotStart <= :e
|
||||
')->setParameters(['doctor' => $doctor, 's' => $tmrStart, 'e' => $tmrEnd])
|
||||
->getSingleScalarResult();
|
||||
|
||||
$todayAppts = [];
|
||||
if ($canView) {
|
||||
$todayAppts = $this->em->createQuery('
|
||||
SELECT a.uuid, u.realName AS patient_name, u.mobileNumber AS patient_mobile,
|
||||
a.slotStart AS slot_start, a.status
|
||||
FROM App\Appointment\Entity\Appointment a
|
||||
JOIN a.user u
|
||||
WHERE a.doctor = :doctor AND a.slotStart >= :s AND a.slotStart <= :e
|
||||
ORDER BY a.slotStart ASC
|
||||
')->setMaxResults(10)->setParameters([
|
||||
'doctor' => $doctor,
|
||||
's' => $todayStart,
|
||||
'e' => $todayEnd,
|
||||
])->getArrayResult();
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'doctor' => [
|
||||
'uuid' => $doctor->getUuid(),
|
||||
'name' => $doctor->getName(),
|
||||
'degree' => $doctor->getDegree(),
|
||||
],
|
||||
'permissions' => $permissions,
|
||||
'stats' => [
|
||||
'today_appointments' => $todayCount,
|
||||
'tomorrow_appointments' => $tmrCount,
|
||||
],
|
||||
'today_appointments' => $todayAppts,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user