Add a reserved meta key inside WeeklySchedule.setting (no migration) holding online_booking_enabled and a booking_window value+unit (week|month), with sane defaults. Expose meta separately in toArray() and keep it out of the day schedule map. setSetting now preserves meta when the day schedule is replaced; the weekly-schedule POST/PATCH endpoints accept an optional meta. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
12 KiB
تنظیمات نوبتدهی آنلاین دکتر: بازهی رزرو (booking window) + endpoint دردسترسبودن ماهانه
پروژه
clinicpro (Backend + Admin React panel). این پرامپت اول اجرا شود.
Cross-repo: قرارداد API این پرامپت توسط سایت عمومی مصرف میشود. پرامپت همتا در سمت فرانت:
nobat724_front/.claude/prompt/appointment-calendar-disabled-dates.md
زمینه
پزشک در پنل ادمین (assets/admin/pages/DoctorDetailPage.tsx) از قبل میتواند برنامهی هفتگی، date override (روز تعطیل/سفارشی) و تعطیلات (holidays) را تنظیم کند. SlotCalculatorService هم اینها را با اولویت درست اعمال میکند (holiday → override → weekly). پس «۲۷/۰۳/۱۴۰۵ تعطیل است» همین حالا با یک date override (active:false) قابل تنظیم است و appointment-slots برای آن روز آرایهی خالی برمیگرداند.
اما دو چیز وجود ندارد:
- بازهی رزرو آنلاین (booking window): پزشک نمیتواند تعیین کند «تا چند هفته/ماه جلوتر بیمار میتواند آنلاین نوبت بگیرد» و نمیتواند نوبتدهی آنلاین را کلاً خاموش کند. الان هیچ سقفی نیست و
appointment-slotsبرای هر تاریخ آیندهای اسلات میدهد. - دردسترسبودن ماهانه (month availability): سایت عمومی برای خاکستریکردن روزهای تعطیل/خارجازبازه روی تقویم، باید بداند کدام روزهای یک ماه قابلانتخاباند. الان فقط endpoint تکروزه (
appointment-slots) هست؛ صدازدن آن برای ۳۰ روز سنگین است. یک endpoint که برای یک ماه، آرایهی روزهای غیرفعال را برگرداند لازم است.
مشکل / هدف
۱. افزودن تنظیمات «نوبتدهی آنلاین» به پزشک: online_booking_enabled (boolean) و booking_window (عدد + واحد هفته/ماه). بدون migration — داخل همان WeeklySchedule.setting JSON ذخیره شود.
۲. اعمال این بازه در SlotCalculatorService: اگر نوبتدهی آنلاین خاموش است یا تاریخ خارج از بازه است → اسلات خالی.
۳. endpoint عمومی جدید: GET /api/v1/appointment-settings/month-availability/{doctorUuid}?year=&month= که برای یک ماه شمسی یا میلادی، روزهای غیرفعال (تعطیل/override بسته/خارجازبازه/بدون session) را برگرداند تا تقویم سایت آنها را غیرقابلانتخاب کند.
۴. UI در پنل ادمین (DoctorDetailPage.tsx) برای تنظیم این دو مقدار.
فایلهای مرتبط
| فایل | نقش |
|---|---|
src/Appointment/Entity/WeeklySchedule.php |
setting JSON — محل ذخیرهی booking window (بدون فیلد جدید DB) |
src/Appointment/Service/SlotCalculatorService.php |
منطق محاسبهی اسلات — اعمال window |
src/Appointment/Controller/AppointmentController.php |
appointment-slots (تکروز) — باید window را respect کند |
src/Appointment/Controller/AppointmentSettingsController.php |
weekly-schedule upsert + endpoint جدید month-availability |
src/Appointment/Repository/WeeklyScheduleRepository.php |
findByDoctor |
docs/api/appointment-settings.md |
مستندسازی window + endpoint جدید |
docs/api/appointment.md |
اگر رفتار appointment-slots تغییر کرد |
assets/admin/pages/DoctorDetailPage.tsx |
UI تنظیم booking window + toggle آنلاین |
وضعیت فعلی (کد واقعی)
WeeklySchedule.php — ذخیرهسازی در JSON
#[ORM\Column(type: 'json')]
private array $setting = [];
settingالان فقط کلیدهای"0".."6"(روزهای هفته) را دارد. میتوان یک کلید رزروشدهی غیرعددی مثل"meta"اضافه کرد بدون اینکهSlotCalculatorServiceکه فقط کلیدهای عددی روز را میخواند، بشکند.
SlotCalculatorService::buildAllSessions() — ترتیب اولویت فعلی
private function buildAllSessions(Doctor $doctor, string $date): array
{
$dayStart = (int) strtotime($date . ' 00:00:00');
$dayEnd = $dayStart + 86400;
// 1. Blocked by holiday
if (!empty($this->holidayRepo->findActiveByDoctor($doctor, $dayStart, $dayEnd - 1))) {
return [];
}
// 2. Date override
foreach ($this->overrideRepo->findByDoctor($doctor) as $override) {
if (date('Y-m-d', $override->getDate()) === $date) {
if (!$override->isActive()) return [];
return $this->buildSessionsFromOverride($override->getSetting() ?? [], $dayStart);
}
}
// 3. Weekly schedule
$schedule = $this->scheduleRepo->findByDoctor($doctor);
if ($schedule === null) return [];
// ...
}
appointment-slots controller — بدون چک window
#[Route('/api/v1/appointment-slots', methods: ['GET'])]
public function slots(Request $request): JsonResponse
{
$doctorUuid = trim($request->query->get('doctor_uuid', ''));
$date = trim($request->query->get('date', ''));
$doctor = $this->doctorRepo->findByUuid($doctorUuid);
// ... validation ...
$sessions = $this->slotCalculator->getAllSlotsWithAvailability($doctor, $date);
return $this->success([...]);
}
DoctorDetailPage.tsx — schedule از قبل ذخیره میشود
const saveMut = useMutation({
mutationFn: () => existing
? api.patch(`/api/v1/appointment-settings/weekly-schedule/${doctorUuid}`, { schedule: scheduleMap })
: api.post('/api/v1/appointment-settings/weekly-schedule', { doctor_uuid: doctorUuid, schedule: scheduleMap }),
});
وظایف
اجرای مرحلهبهمرحله؛ بعد از هر قابلیت تست (PHP lint + migrate در صورت نیاز + admin build) و سپس commit جدا.
۱. مدل booking window در WeeklySchedule.setting
- ساختار جدید زیر کلید رزروشده
"meta"(یا"settings") درsetting:
{
"0": { "sessions": [...] },
"...": {},
"meta": {
"online_booking_enabled": true,
"booking_window_value": 2,
"booking_window_unit": "month"
}
}
booking_window_unit: یکی از"week"یا"month".booking_window_value: عدد مثبت (مثلاً ۲ هفته یا ۲ ماه).- پیشفرض وقتی
metaنیست: نوبتدهی آنلاین روشن، بازهی پیشفرض (مثلاً ۱ ماه) — این پیشفرض را بهصورت ثابت در سرویس تعریف کن و در پاسخها هم برگردان تا فرانت بداند. - weekly-schedule controller (POST/PATCH) باید فیلدهای
metaرا در صورت ارسال بپذیرد و ذخیره کند (validation:unit ∈ {week, month}،value ≥ 1). کلیدهای عددی روز دستنخورده بمانند.
۲. اعمال window در SlotCalculatorService
- یک متد private مثل
isWithinBookingWindow(Doctor $doctor, string $date): boolبساز:metaرا ازschedule->getSetting()['meta']بخوان (با fallback پیشفرض).- اگر
online_booking_enabled === false→ خارج از بازه (false). - سقف را با
strtotime("+{$value} {$unit}", today)حساب کن؛ اگرdayStartبعد از سقف بود → false. تاریخهای گذشته هم false (همین حالا فرانت گذشته را میبندد ولی backend هم باید مقاوم باشد).
- در
buildAllSessionsقبل از بازگشت اسلاتها این چک را اعمال کن: اگر خارج از بازه →return []. - توجه: تاریخهای یونیکس صحیح (نه DateTime object).
۳. endpoint عمومی month-availability
- متد جدید در
AppointmentSettingsControllerبا route:GET /api/v1/appointment-settings/month-availability/{doctorUuid}— Permission: PUBLIC (مثلavailable-locations). - Query params:
yearوmonth(عددی). چون تقویم سایت شمسی است، هر دو حالت را بپذیر: اگرcalendar=jalaliآمد ورودی را شمسی تفسیر کن، در غیر این صورت میلادی. (یا سادهتر: همیشه میلادی بگیر و در پرامپت فرانت تبدیل شمسی→میلادی انجام شود — یکی را انتخاب کن و دقیق مستند کن.) - برای هر روز ماه، با همان منطق
SlotCalculatorService(holiday/override/window/weekly) تعیین کن آیا روز اسلات دارد یا نه. برای پرهیز از سنگینی، یک متد سبک در سرویس اضافه کن مثلhasAnyAvailability(Doctor, string $date): boolکه فقط وجود session را چک کند (نه ساخت کامل اسلاتها). - پاسخ:
{
"success": true,
"data": {
"year": 2026,
"month": 6,
"disabled_dates": ["2026-06-16", "2026-06-17", "2026-06-20"],
"enabled_dates": ["2026-06-21", "2026-06-22"],
"online_booking_enabled": true,
"booking_window": { "value": 2, "unit": "month" }
}
}
disabled_dates: روزهایی که تعطیل/خارجازبازه/بدون sessionاند (غیرقابلانتخاب). فرمتY-m-d. فرانت اینها را روی تقویم خاکستری میکند.
۴. UI در پنل ادمین (DoctorDetailPage.tsx)
- در بخش تنظیمات برنامهی هفتگی، یک کارت/سکشن «نوبتدهی آنلاین» اضافه کن:
- یک toggle برای
online_booking_enabled. - یک input عددی + select واحد (
هفته/ماه) برای بازه.
- یک toggle برای
- مقدار اولیه را از پاسخ
weekly-schedule(data?.data?.data?.meta) بخوان (double-nested). - در
saveMutهمان weekly-schedule، فیلدmetaرا هم به payload اضافه کن ({ schedule: scheduleMap, meta: {...} }یا داخل خودscheduleMapبا کلیدmeta). - از کلاسهای CSS موجود همان صفحه استفاده کن؛ RTL؛ بدون کتابخانهی جدید.
نکات مهم
- بدون migration: booking window در
WeeklySchedule.settingJSON میرود. اگر ترجیح میدهی فیلد مجزا در DB باشد، متوقف شو و بپرس (نیاز به migration دارد). - همهی controllerها از
BaseController؛ پاسخها$this->success()/$this->error(). خطاها باAppException(ErrorCodes::ERR_XXX, ...). appointment-slotsتکروزه هم باید window را respect کند (وگرنه بیمار میتواند تاریخ خارجازبازه را مستقیم صدا بزند و اسلات بگیرد).month-availabilityباید public باشد (تقویم سایت قبل از لاگین لود میشود) — مثلavailable-locations.- پاسخ weekly-schedule double-nested است (
data.data.data) — UI ادمین با همین الگو میخواند. - اولویت منطق دستنخورده بماند: holiday > override > window > weekly. window نباید روی اسلاتهای گذشتهی همین ماه تأثیر اشتباه بگذارد.
- بعد از تغییر API، هم
docs/api/appointment-settings.mdو هم (در صورت تغییر رفتار)docs/api/appointment.mdرا در همین session بهروز کن: endpoint جدید با method/path/permission/query/response، و فیلدهایmeta. - تست:
ddev exec php -l ...روی فایلهای PHP؛ddev exec php bin/console debug:router | grep month-availability؛ یکبار واقعیcurlرویmonth-availabilityبا پزشک تست4a0594b1-008b-478a-a593-259b95d8c2dd؛ وddev exec yarn devبرای build پنل ادمین. سپس commit.