feat: Implement resource booking functionality

- Add service timeline builder for appointments to manage available slots.
- Create a hook to fetch resource booking services with effective durations.
- Develop ResourceBookingSlotController to handle API requests for resource booking slots.
- Implement ResourceBookingSlotService to calculate available time slots based on resource occupancy and service durations.
- Add tests for resource appointment creation and booking slot functionality to ensure correct behavior and edge cases.
This commit is contained in:
hamed
2026-08-03 14:34:23 +03:30
parent 981261ed3a
commit 4f69bc9044
21 changed files with 2045 additions and 514 deletions
+56 -1
View File
@@ -451,6 +451,58 @@
> تعداد کوئری ثابت است: یک کوئری اشغال، یک کوئری شیفت، یک کوئری نوبت — نه یکی به‌ازای
> هر منبع.
### `GET /api/v1/resource/{uuid}/day-slots` (2026-08)
مجوز: `appointment_settings.view`. پارامتر: `date=Y-m-d` (الزامی).
بازه‌های **کاری** منبع در یک روز — ورودیِ تایم‌لاینِ سرویسیِ صفحهٔ نوبت‌ها. معادلِ
`appointment-slots` پزشک، ولی از تقویم خودِ منبع: ساعت شعبه ∩ شیفت منبع − تعطیلات −
استثناها.
نوبت‌ها اینجا **کسر نمی‌شوند**: تایم‌لاین نوبت‌های همان روز را جدا دارد و کارت‌ها را
داخل همین بازه‌ها می‌چیند؛ کسرشان یعنی نوبتِ ثبت‌شده جایی برای نشستن ندارد.
```json
{ "success": true, "data": {
"resource_uuid": "ce07…", "date": "2026-08-04", "timezone": "Asia/Tehran",
"windows": [{ "start": 1785220200, "end": 1785249000, "start_time": "09:00", "end_time": "17:00" }],
"empty_reason": null
} }
```
`empty_reason` وقتی `windows` خالی است می‌گوید چرا: `no_shift`، `national_holiday`،
`tenant_holiday`، `exception`، `resource_inactive`، `address_inactive`. خالی‌بودن خطا
نیست و کلاینت نباید همه را «تعطیل» بنامد.
### `GET /api/v1/resource/{uuid}/service-slots` (2026-08)
مجوز: `appointment_settings.view`.
| پارامتر | توضیح |
|---|---|
| `date` | `Y-m-d`، الزامی |
| `service_item_uuids[]` | یک یا چند سرویس؛ خالی ⇒ `422` |
| `durations[<uuid>]` | override مدت، فقط برای همین محاسبه |
زمان‌های خالیِ کافی برای مجموعِ مدتِ سرویس‌های انتخاب‌شده — معادلِ
`appointment-service-slots` پزشک. مدت هر سرویس از زنجیرهٔ حلِ همان منبع می‌آید
(`ResourceServiceResolver`)، اشغال از نوبت‌های `resource_id` **و** ردیف‌های
`resource_occupancy` خوانده می‌شود، و ظرفیت منبع رعایت می‌شود. زمان‌ها پشت‌سرهم
چیده می‌شوند (بدون بافر) و زمانِ گذشته پیشنهاد نمی‌شود.
```json
{ "success": true, "data": {
"resource_uuid": "ce07…", "date": "2026-08-04", "total_duration_minutes": 60,
"start_times": [{ "start": 1785220200, "end": 1785223800, "start_time": "09:00", "end_time": "10:00" }]
} }
```
**۴۲۲:** سرویسِ ناموجود · سرویسی که این منبع ارائه نمی‌دهد (یا offeringش غیرفعال است)
· سرویسِ بی‌مدت · تاریخ بدفرم. **۴۰۴:** منبعِ محیط دیگر.
ثبتِ خودِ نوبت با همین زمان‌ها از `POST /api/v1/my/appointment` با `resource_uuid`
انجام می‌شود ([`appointment.md`](appointment.md)).
### `PUT /api/v1/resource/{uuid}/categories`
مجوز: `appointment_settings.update`.
@@ -581,9 +633,12 @@ idempotent است: تکیه‌گاهش وجود یا نبودِ منبعِ مت
## تست‌ها
```bash
ddev exec php bin/phpunit tests/Resource # ۵۲ تست / ۱۲۹ assertion
ddev exec php bin/phpunit tests/Resource # ۱۳۱ تست / ۳۴۲ assertion
ddev exec php vendor/bin/phpstan analyse src/Resource
npx vitest run assets/admin/pages/ResourcesPage.test.tsx
npx vitest run assets/admin/components/appointments/ResourceDayPanel.test.tsx \
assets/admin/components/appointments/ResourceBookingModal.test.tsx \
assets/admin/components/appointments/serviceTimeline.test.ts
```
---