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:
@@ -1118,6 +1118,39 @@ New query param `reserve=1` → returns only reserve-list entries; without it on
|
||||
منبع تحت `TenantFilter` است: `resource_uuid`ِ محیط دیگر هیچ ردیفی برنمیگرداند (۲۰۰ با
|
||||
فهرست خالی، نه ۴۰۳).
|
||||
|
||||
### ثبت نوبت برای یک منبع — `resource_uuid` روی `POST /api/v1/my/appointment` (2026-08)
|
||||
|
||||
نوبتدهی منبع **سرویسی** است: مودالِ منبع همان فرمِ نوبتدهی سرویسیِ پزشک است و همین
|
||||
اندپوینت را صدا میزند، فقط با `resource_uuid`.
|
||||
|
||||
| فیلد | نوع | توضیح |
|
||||
|---|---|---|
|
||||
| `resource_uuid` | string | منبعِ نوبت. غیرفعال یا ناموجود ⇒ `422` |
|
||||
|
||||
قواعدی که فقط وقتی این فیلد بیاید اعمال میشوند:
|
||||
|
||||
- **پزشک از ناظرِ منبع میآید.** `doctor_uuid` اختیاری میشود؛ منبعِ بیناظر ⇒ `422`
|
||||
(رابطهٔ پزشک↔منبع یک جا تعریف شده است و پرسیدن دوبارهاش یعنی دو منبعِ حقیقت).
|
||||
- **مدت از زنجیرهٔ حلِ همان منبع** (`ResourceServiceResolver`) میآید نه از
|
||||
`duration_minutes` خامِ سرویس: همان «RF فرکشنال» روی یک دستگاه ۵۰ دقیقه است و روی
|
||||
دیگری ۴۰. `service_durations` همچنان همین نوبت را جابهجا میکند.
|
||||
- **گیتِ سرویس، `ResourceServiceOffering` فعال است** نه پرچم `bookable`: سرویسی که
|
||||
این منبع ارائه نمیدهد ⇒ `422` با فیلد `service_item_uuids`.
|
||||
- **تداخل روی خودِ منبع جدا سنجیده میشود** ⇒ `409`. `bookAtomically` فقط اسلاتِ پزشک
|
||||
را قفل میکند و دو پزشک میتوانند یک دستگاه را همزمان بگیرند. اشغال از دو جا خوانده
|
||||
میشود: نوبتهای `appointments.resource_id` و ردیفهای `resource_occupancy` (رزرو
|
||||
موقت، مسدودسازیِ دستی، نوبتهای موتور منبعمحور). ظرفیت منبع رعایت میشود: اتاق
|
||||
دوتخته با یک نوبت پر نمیشود.
|
||||
- **منبعِ محیط دیگر رد میشود** ⇒ `422`. منبع با uuid از بدنه میآید و `TenantFilter`
|
||||
پوششش نمیدهد.
|
||||
|
||||
> **محدودیت شناختهشده:** این مسیر ردیف `resource_occupancy` نمیسازد (مثل
|
||||
> `POST /api/v1/appointment` عمومی که از قبل همینطور بود). پس نوبتِ پنلی برای موتور
|
||||
> منبعمحور (`appointment-availability`) نامرئی است؛ در جهت عکس — پنل هر دو منبعِ
|
||||
> اشغال را میخواند — مشکلی نیست.
|
||||
|
||||
تست: `tests/Appointment/ResourceAppointmentCreateTest.php`.
|
||||
|
||||
خروجی واقعی `GET /api/v1/my/appointments?limit=1&resource_uuid=ce070910-…`:
|
||||
|
||||
```json
|
||||
|
||||
+56
-1
@@ -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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user