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>
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؛ تغییری در قرارداد JSONappointment-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را بهروز کن.