Files
clinicpro/.claude/prompt/fix-doctor-schedule-fields.md
T

11 KiB
Raw Blame History

رفع فیلدهای free_turn و hours_of_work در API لیست پزشکان

زمینه

endpoint GET /api/v1/doctors و GET /api/v1/doctor/{uuid} در پاسخ خود دو فیلد free_turn و hours_of_work دارند که همیشه مقدار ثابت «نوبت آزادی موجود نیست» و «برنامه کاری تنظیم نشده» برمی‌گردانند — حتی برای پزشکانی که برنامه هفتگی (weekly_schedules) ست کرده‌اند. همچنین activeDoctorAppointment (فیلد active) باید با وجود یا نبود برنامه هفتگی هماهنگ باشد.

مشکل / هدف

  • free_turn: باید نزدیک‌ترین روز کاری پزشک را نشان دهد (مثلاً «شنبه ۹:۰۰–۱۳:۰۰»)؛ اگر برنامه‌ای نداشت «نوبت آزادی موجود نیست»
  • hours_of_work: باید ساعت‌های کاری روزهای فعال را خلاصه کند (مثلاً «شنبه تا چهارشنبه ۹–۱۳ و ۱۴–۱۸»)؛ اگر برنامه نداشت «برنامه کاری تنظیم نشده»
  • active (activeDoctorAppointment): اگر پزشک برنامه هفتگی نداشته باشد یا همه روزها sessions: [] باشند، باید false برگردد — نوبت‌دهی غیرفعال

فایل‌های مرتبط

فایل نقش
src/Doctor/Entity/Doctor.php متدهای toListArray() و toDetailArray() — مقادیر hardcoded اینجاست
src/Doctor/Repository/DoctorRepository.php findWithFilters() — JOIN با weekly_schedules ندارد
src/Doctor/Controller/DoctorController.php list() (خط ۲۳۳) و show() — از toListArray/toDetailArray استفاده می‌کنند
src/Appointment/Entity/WeeklySchedule.php Entity برنامه هفتگی — فیلد setting آرایه ۷ عنصری (شنبه تا جمعه)
src/Appointment/Repository/WeeklyScheduleRepository.php findByDoctor(Doctor $doctor): ?WeeklySchedule موجود است

وضعیت فعلی

Doctor.php — toListArray() خط ۱۷۸:

public function toListArray(): array
{
    return [
        // ...
        'free_turn'     => 'نوبت آزادی موجود نیست',   // ❌ hardcoded
        'hours_of_work' => 'برنامه کاری تنظیم نشده',  // ❌ hardcoded
        'active'        => $this->activeDoctorAppointment,
    ];
}

Doctor.php — toDetailArray() خط ۱۹۸:

'free_turn'           => 'نوبت آزادی موجود نیست',   // ❌ hardcoded
'hours_of_work'       => 'برنامه کاری تنظیم نشده',  // ❌ hardcoded

ساختار setting در weekly_schedules:

آرایه ۷ عنصری (ایندکس ۰=شنبه، ۱=یکشنبه، ..., ۶=جمعه):

[
  {"sessions": [{"active": true, "start_time": "09:00", "end_time": "13:00", ...}, ...]},
  {"sessions": [{"active": true, "start_time": "14:00", "end_time": "18:00", ...}]},
  ...
  {"sessions": []}  // جمعه — تعطیل
]

DoctorRepository.findWithFilters() — JOIN ندارد:

$qb = $this->createQueryBuilder('d')
    ->leftJoin('d.specialties', 's')
    ->leftJoin('d.provinces', 'pr')
    ->leftJoin('d.cities', 'ci')
    // ❌ هیچ JOIN با weekly_schedules ندارد

وظایف

۱. اضافه کردن متد کمکی به Doctor entity

در src/Doctor/Entity/Doctor.php یک متد computeScheduleFields(?WeeklySchedule $schedule): array اضافه کن:

use App\Appointment\Entity\WeeklySchedule;

private const DAY_NAMES = ['شنبه', 'یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه'];

public function computeScheduleFields(?WeeklySchedule $schedule): array
{
    if ($schedule === null) {
        return [
            'free_turn'     => 'نوبت آزادی موجود نیست',
            'hours_of_work' => 'برنامه کاری تنظیم نشده',
            'has_schedule'  => false,
        ];
    }

    $setting = $schedule->getSetting();  // آرایه ۷ عنصری

    // محاسبه hours_of_work — ساعت‌های روزهای فعال
    $workDays = [];
    foreach ($setting as $dayIdx => $day) {
        $activeSessions = array_filter(
            $day['sessions'] ?? [],
            fn($s) => ($s['active'] ?? false) && !empty($s['start_time'])
        );
        if (!empty($activeSessions)) {
            $times = array_map(fn($s) => $s['start_time'] . '' . $s['end_time'], $activeSessions);
            $workDays[] = self::DAY_NAMES[$dayIdx] . ': ' . implode(' و ', $times);
        }
    }

    if (empty($workDays)) {
        return [
            'free_turn'     => 'نوبت آزادی موجود نیست',
            'hours_of_work' => 'برنامه کاری تنظیم نشده',
            'has_schedule'  => false,
        ];
    }

    // محاسبه free_turn — نزدیک‌ترین روز کاری فعال
    // روز هفته فعلی را بگیر (PHP: 0=یکشنبه...6=شنبه → تبدیل به ایندکس ایرانی)
    $phpDay = (int) date('w');  // 0=Sun, 6=Sat
    $iranDay = $phpDay === 0 ? 1 : ($phpDay === 6 ? 0 : $phpDay + 1);  // 0=Sat, 1=Sun, ...

    $freeTurn = null;
    for ($i = 0; $i < 7; $i++) {
        $idx = ($iranDay + $i) % 7;
        $activeSessions = array_filter(
            $setting[$idx]['sessions'] ?? [],
            fn($s) => ($s['active'] ?? false) && !empty($s['start_time'])
        );
        if (!empty($activeSessions)) {
            $first = array_values($activeSessions)[0];
            $freeTurn = self::DAY_NAMES[$idx] . ' ' . $first['start_time'] . '' . $first['end_time'];
            break;
        }
    }

    return [
        'free_turn'     => $freeTurn ?? 'نوبت آزادی موجود نیست',
        'hours_of_work' => implode(' | ', $workDays),
        'has_schedule'  => true,
    ];
}

۲. آپدیت toListArray() و toDetailArray()

toListArray() و toDetailArray() باید یک ?WeeklySchedule دریافت کنند:

public function toListArray(?WeeklySchedule $schedule = null): array
{
    $scheduleFields = $this->computeScheduleFields($schedule);
    return [
        // ... بقیه فیلدها
        'free_turn'     => $scheduleFields['free_turn'],
        'hours_of_work' => $scheduleFields['hours_of_work'],
        'active'        => $this->activeDoctorAppointment && $scheduleFields['has_schedule'],
    ];
}

public function toDetailArray(?WeeklySchedule $schedule = null): array
{
    $scheduleFields = $this->computeScheduleFields($schedule);
    return [
        // ... بقیه فیلدها
        'free_turn'     => $scheduleFields['free_turn'],
        'hours_of_work' => $scheduleFields['hours_of_work'],
        'active'        => $this->activeDoctorAppointment && $scheduleFields['has_schedule'],
        // ... address, state, city
    ];
}

۳. آپدیت DoctorRepository.findWithFilters()

LEFT JOIN با weekly_schedules اضافه کن تا schedule را یکجا load کند:

$qb = $this->createQueryBuilder('d')
    ->leftJoin('d.specialties', 's')
    ->leftJoin('d.provinces', 'pr')
    ->leftJoin('d.cities', 'ci')
    ->addSelect('d')  // ensure d is selected for eager loading
    ->distinct();

نکته مهم: از eager loading یا جداگانه query استفاده کن. چون Doctor entity OneToOne با WeeklySchedule ندارد (رابطه از طرف WeeklySchedule است)، بهترین راه این است که در Controller بعد از گرفتن لیست doctors، scheduleها را batch load کنی.

۴. آپدیت DoctorController — متد list()

در src/Doctor/Controller/DoctorController.php متد list() خط ۲۳۳:

public function __construct(
    // اضافه کن:
    private readonly WeeklyScheduleRepository $scheduleRepo,
    // ...
)

public function list(Request $request): JsonResponse
{
    $filters = $request->query->all();
    $result  = $this->doctorRepo->findWithFilters($filters);

    // batch load schedules برای همه doctors
    $scheduleMap = [];
    foreach ($this->scheduleRepo->findByDoctors($result['items']) as $schedule) {
        $scheduleMap[$schedule->getDoctor()->getId()] = $schedule;
    }

    return $this->paginated(
        array_map(
            fn(Doctor $d) => $d->toListArray($scheduleMap[$d->getId()] ?? null),
            $result['items']
        ),
        $result['total'],
        $result['page'],
        $result['limit']
    );
}

۵. اضافه کردن findByDoctors() به WeeklyScheduleRepository

/**
 * @param Doctor[] $doctors
 * @return WeeklySchedule[]
 */
public function findByDoctors(array $doctors): array
{
    if (empty($doctors)) return [];

    return $this->createQueryBuilder('ws')
        ->where('ws.doctor IN (:doctors)')
        ->setParameter('doctors', $doctors)
        ->getQuery()
        ->getResult();
}

۶. آپدیت متدهای show() در DoctorController

هر جایی که $doctor->toDetailArray() صدا زده می‌شود (خطوط ۱۱۷، ۱۶۳، ۱۸۴، ۳۱۲)، schedule را پاس بده:

$schedule = $this->scheduleRepo->findByDoctor($doctor);
return $this->success(['data' => $doctor->toDetailArray($schedule)]);

نکات مهم

  • Doctor entity رابطه مستقیم با WeeklySchedule ندارد — رابطه OneToOne از طرف WeeklySchedule به Doctor است؛ پس $doctor->getSchedule() وجود ندارد و باید از Repository بخوانی
  • Circular dependency: Doctor.php نباید مستقیم WeeklyScheduleRepository inject کند — بهتر است در Controller schedule را بگیری و به متد پاس بدهی (دقیقاً همان الگوی پیشنهادی بالا)
  • active flag: فقط وقتی هم activeDoctorAppointment=true هم has_schedule=true باشد نوبت‌دهی فعال است
  • batch load: برای endpoint لیست، حتماً از findByDoctors() batch استفاده کن — N+1 query نساز
  • ایندکس روزها: setting[0]=شنبه, setting[1]=یکشنبه, ..., setting[6]=جمعه — PHP date('w') را باید به این ایندکس تبدیل کنی
  • sessions خالی: اگر sessions: [] باشد آن روز تعطیل است — فقط active: true و start_time غیرخالی معتبر است
  • مستندات: بعد از تغییر، docs/api/doctor.md را آپدیت کن (فیلدهای free_turn، hours_of_work، active را توضیح بده)
  • هیچ migration لازم نیست — Entity تغییر ساختاری ندارد