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>
117 lines
7.6 KiB
Markdown
117 lines
7.6 KiB
Markdown
# غیرقابلرزرو کردن اسلاتهای گذشته در روز جاری
|
|
|
|
## پروژه
|
|
|
|
`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 فقط رزرو را چک میکند
|
|
|
|
```php
|
|
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 قابلرزرو)
|
|
|
|
```php
|
|
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` را طوری تغییر بده که علاوه بر «رزرو نشده»، «در آینده» هم باشد:
|
|
|
|
```php
|
|
'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 و هر مصرفکنندهی این متد با واقعیت همخوان بماند:
|
|
|
|
```php
|
|
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` را بهروز کن.
|