Files
clinicpro/.claude/prompt/mark-past-slots-unavailable.md
hamedandClaude Opus 4.8 4216e67d68 fix(appointment): mark past-time slots as unavailable
is_available only checked whether a slot was booked, so on today's date
slots whose start time had already passed (e.g. 10:00 when it is 12:00)
still showed as bookable until the POST /appointment 422. Treat a slot
with start < now as unavailable in getAllSlotsWithAvailability, and drop
past slots in filterBookedSlots so getAvailableSlots agrees. Future days
are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 16:48:50 +03:30

7.6 KiB

غیرقابل‌رزرو کردن اسلات‌های گذشته در روز جاری

پروژه

clinicpro (Backend). این پرامپت اول اجرا شود.

Cross-repo: خروجی is_available این endpoint توسط سایت عمومی مصرف می‌شود. پرامپت همتا در سمت فرانت: nobat724_front/.claude/prompt/verify-past-slots-disabled.md

زمینه

GET /api/v1/appointment-slots?doctor_uuid=&date= اسلات‌های یک روز را گروه‌بندی‌شده در sessions[].slots[] برمی‌گرداند و هر اسلات یک فلگ is_available دارد. سایت عمومی (List.js) دقیقاً بر اساس همین is_available اسلات را فعال/خاکستری می‌کند.

مشکل: is_available فقط چک می‌کند که اسلات رزرو شده است یا نه (isSlotTaken). زمان گذشته را در نظر نمی‌گیرد. پس اگر امروز ساعت ۱۲ باشد، اسلات ساعت ۱۰ همان روز هنوز is_available: true برمی‌گردد و در UI قابل کلیک است.

نکته: endpoint رزرو (POST /api/v1/appointment) از قبل اسلات گذشته را رد می‌کند (if ($slotStart < time()) → ۴۲۲ «زمان این اسلات گذشته است»). پس داده‌ی نامعتبر ثبت نمی‌شود؛ ولی کاربر اسلات گذشته را کلیک‌پذیر می‌بیند و فقط موقع ثبت خطا می‌گیرد. هدف این پرامپت این است که اسلات گذشته از همان ابتدا is_available: false باشد.

مشکل / هدف

در محاسبه‌ی is_available، اسلاتی که زمان شروعش (start، Unix timestamp) قبل از «اکنون» است باید is_available: false شود — دقیقاً مثل اسلات رزرو‌شده. این فقط برای روز جاری اثر دارد؛ روزهای آینده اسلات گذشته ندارند و روزهای گذشته از قبل توسط booking window حذف شده‌اند.

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

فایل نقش
src/Appointment/Service/SlotCalculatorService.php محاسبه‌ی is_available در getAllSlotsWithAvailability و فیلتر در getAvailableSlots
src/Appointment/Controller/AppointmentController.php book() — از قبل اسلات گذشته را رد می‌کند (تغییر نمی‌کند، فقط مرجع)
docs/api/appointment.md توضیح is_available و رفتار اسلات گذشته

وضعیت فعلی (کد واقعی)

SlotCalculatorService::getAllSlotsWithAvailability() — is_available فقط رزرو را چک می‌کند

public function getAllSlotsWithAvailability(Doctor $doctor, string $date): array
{
    $sessions = $this->buildAllSessions($doctor, $date);
    return array_map(fn(array $session) => [
        'start_time' => $session['start_time'],
        'end_time'   => $session['end_time'],
        'slots'      => array_map(fn(array $slot) => array_merge($slot, [
            'is_available' => !$this->appointmentRepo->isSlotTaken($doctor, $slot['start'], $slot['end']),
        ]), $session['slots']),
    ], $sessions);
}

getAvailableSlots() — برای چک کانفلیکت رزرو (اسلات‌های flat قابل‌رزرو)

public function getAvailableSlots(Doctor $doctor, string $date): array
{
    $sessions = $this->buildAllSessions($doctor, $date);
    if (empty($sessions)) return [];
    $flat = array_merge(...array_map(fn($s) => $s['slots'], $sessions));
    return $this->filterBookedSlots($doctor, $flat);
}

private function filterBookedSlots(Doctor $doctor, array $slots): array
{
    return array_values(array_filter($slots, fn(array $slot): bool =>
        !$this->appointmentRepo->isSlotTaken($doctor, $slot['start'], $slot['end'])
    ));
}

وظایف

۱. اسلات گذشته را در is_available غیرفعال کن

در getAllSlotsWithAvailability، شرط is_available را طوری تغییر بده که علاوه بر «رزرو نشده»، «در آینده» هم باشد:

'slots' => array_map(function (array $slot) use ($doctor): array {
    $isPast  = $slot['start'] < time();
    $isTaken = $this->appointmentRepo->isSlotTaken($doctor, $slot['start'], $slot['end']);
    return array_merge($slot, [
        'is_available' => !$isPast && !$isTaken,
    ]);
}, $session['slots']),

از time() استفاده کن (تایم‌زون سرور همان است که buildSessionSlots با آن start/end را می‌سازد — هر دو از strtotime($date.' 00:00:00') می‌آیند، پس مقایسه‌ی Unix-با-Unix درست است). تبدیل تایم‌زون اضافه نکن.

۲. اسلات گذشته را از getAvailableSlots هم حذف کن (سازگاری)

filterBookedSlots (یا یک فیلتر هم‌تراز) باید اسلات گذشته را هم بیندازد، تا منطق conflict-check و هر مصرف‌کننده‌ی این متد با واقعیت هم‌خوان بماند:

private function filterBookedSlots(Doctor $doctor, array $slots): array
{
    $now = time();
    return array_values(array_filter($slots, fn(array $slot): bool =>
        $slot['start'] >= $now &&
        !$this->appointmentRepo->isSlotTaken($doctor, $slot['start'], $slot['end'])
    ));
}

اگر اسم متد دیگر گویا نیست می‌توانی به چیزی مثل filterUnbookableSlots تغییر دهی، ولی الزامی نیست.

۳. مستندسازی

در docs/api/appointment.md بخش appointment-slots، توضیح is_available را به‌روز کن: «is_available: false یعنی اسلات یا رزرو شده یا زمانش گذشته است.»

نکات مهم

  • فقط روز جاری متأثر می‌شود. برای روزهای آینده همه‌ی startها از time() بزرگ‌ترند، پس رفتار تغییر نمی‌کند. روزهای گذشته قبلاً با booking window حذف شده‌اند.
  • تایم‌زون: start/end اسلات‌ها Unix timestamp ساخته‌شده از strtotime($date . ' 00:00:00') در تایم‌زون سرورند؛ time() هم Unix همان لحظه است — مقایسه‌ی مستقیم درست است. تبدیل دستی نکن.
  • اسلات در حال جریان: تصمیم بگیر مبنا start باشد (اسلاتی که شروعش گذشته → غیرفعال). همین کافی است و ساده‌ترین رفتار قابل‌انتظار است؛ معیار پیچیده‌تر (مثل «تا n دقیقه قبل از شروع») اضافه نکن مگر خواسته شود.
  • book() در AppointmentController از قبل $slotStart < time() را رد می‌کند — این لایه‌ی دفاعی را دست‌نخورده نگه‌دار (نباید حذف شود).
  • همه‌ی پاسخ‌ها از BaseController؛ تغییری در قرارداد JSON appointment-slots نیست جز معنای is_available.
  • تست:
    • ddev exec php -l src/Appointment/Service/SlotCalculatorService.php
    • یک curl واقعی روی appointment-slots برای پزشک 4a0594b1-008b-478a-a593-259b95d8c2dd با date=<امروز میلادی> بزن و تأیید کن اسلات‌هایی که start_time آن‌ها قبل از الان است is_available: false دارند و اسلات‌های بعد از الان true.
    • بعد از تغییر، فایل docs/api/appointment.md را به‌روز کن.