feat: add PublicResourceBookingController and PublicResourceBookingService for public booking functionality
- Implemented PublicResourceBookingController to handle public resource booking requests. - Added methods for retrieving bookable resources, available slots, and month availability. - Created PublicResourceBookingService to manage public resource offerings and service visibility. - Developed tests for public resource booking to ensure correct functionality and error handling.
This commit is contained in:
@@ -291,6 +291,27 @@ Book an appointment slot.
|
||||
>
|
||||
> نوبت بدون منبع دقیقاً مثل قبل با کلید و قفل پزشک محافظت میشود.
|
||||
>
|
||||
> **مدت نوبت روی منبع (2026-08).** وقتی `resource_uuid` داده شده و منبع **همهٔ**
|
||||
> سرویسهای `service_item_uuids` را با ردیفِ ارائهٔ فعال و توگلِ «نمایش در نوبتدهی
|
||||
> آنلاین» روشن ارائه میدهد، `slot_end` از زنجیرهٔ حلِ خودِ منبع محاسبه میشود
|
||||
> (`ResourceServiceResolver`) — همان عددی که
|
||||
> [`appointment-resource-slots`](resource.md) با آن زمانها را ساخته. پیش از این مدت
|
||||
> همیشه از `ServiceBookingCalculator` پزشکمحور میآمد، پس نوبتِ ثبتشده با اسلاتی که به
|
||||
> بیمار نشان داده شده بود یکی نمیشد.
|
||||
>
|
||||
> شرطی است و نه همیشگی — سه رفتار که با هم فرق دارند:
|
||||
>
|
||||
> - **ردیف ارائه هست و فعال + سرویس روشن** ⇒ مدت از منبع. مثلاً سرویسِ ۳۰ دقیقهایِ
|
||||
> پیشفرض با `duration_minutes = 45` روی این دستگاه، نوبتِ ۴۵ دقیقهای میسازد.
|
||||
> - **هیچ ردیفی نیست** ⇒ مثل قبل، مدت از پیشفرضِ خودِ سرویس. محیطی که هنوز رابطههای
|
||||
> منبع↔سرویس را پر نکرده نباید یکشبه نوبتدهیاش قطع شود.
|
||||
> - **ردیف هست ولی غیرفعال** ⇒ `422` با «این منبع این سرویس را ارائه نمیدهد». غیرفعال
|
||||
> حرفِ صریحِ مالک است، نه سکوت.
|
||||
>
|
||||
> گیتِ `bookable` هم دور زده نمیشود: سرویسی که توگلش خاموش است حتی با ردیفِ ارائهٔ فعال
|
||||
> `422` میگیرد («این سرویس برای نوبتدهی فعال نیست») — همان رفتار مسیر پزشکمحور.
|
||||
> مسیر پنل (`POST /api/v1/my/appointment`) عمداً سختگیریِ `bookable` را ندارد.
|
||||
>
|
||||
> **پاسخ:** علاوه بر فیلدهای قبلی، `resource` (`uuid`, `name`, `type`) و `service_option`
|
||||
> (`uuid`, `name`) برمیگردند. نوبتهای پیش از مدل منبعمحور هر دو را `null` دارند، پس
|
||||
> کلاینت باید با `null` کنار بیاید.
|
||||
|
||||
@@ -604,6 +604,181 @@
|
||||
ثبتِ خودِ نوبت با همین زمانها از `POST /api/v1/my/appointment` با `resource_uuid`
|
||||
انجام میشود ([`appointment.md`](appointment.md)).
|
||||
|
||||
## نوبتدهی عمومیِ منبعمحور (سایت) (2026-08)
|
||||
|
||||
سه اندپوینتِ **بدون احراز هویت** که سایت عمومی (`nobat724_front`) با آنها منبع را کشف
|
||||
میکند و روی تقویم خودِ منبع نوبت میگیرد. کنترلرشان
|
||||
`src/Resource/Controller/PublicResourceBookingController.php` است — عمداً جدا از
|
||||
`ResourceBookingSlotController` که منبع را از محیطِ کاربرِ احرازشده حل میکند.
|
||||
|
||||
**گیتِ عمومیشدن** یک قاعده است و در `PublicResourceBookingService` یکجا تعریف شده:
|
||||
منبع فعال باشد، ردیفِ ارائه (`resource_service_offerings`) فعال باشد، و سرویسِ آن ردیف
|
||||
هم `bookable` (توگل «نمایش در نوبتدهی آنلاین») و هم `active` باشد. منبعی که هیچ سرویسِ
|
||||
روشنی ندارد اصلاً در پاسخ نمیآید.
|
||||
|
||||
### `GET /api/v1/appointment-booking-resources/{doctorUuid}` (2026-08)
|
||||
|
||||
عمومی — بدون توکن.
|
||||
|
||||
| پارامتر | توضیح |
|
||||
|---|---|
|
||||
| `clinic_uuid` | اختیاری. **نبودش یعنی همهٔ محیطهای این پزشک** — مطب شخصی بهعلاوهٔ هر کلینیکی که عضوش است |
|
||||
|
||||
فقط منابعی برمیگردند که **پزشکِ همین صفحه** یا ناظرشان است (`supervisor_id`) یا خودشان
|
||||
پلِ همان پزشکاند (`doctor_id`). `duration_minutes` و `price_rials` هر سرویس از زنجیرهٔ
|
||||
حلِ همان منبع میآیند (`ResourceServiceResolver`)، نه از پیشفرضِ خامِ سرویس.
|
||||
|
||||
هر منبع `clinic_uuid`ِ محیطِ خودش را همراه دارد (تهی = مطب شخصی) و سایت نوبت را با همان
|
||||
ثبت میکند. این عمدی است: منبع تقویم و شعبهٔ خودش را دارد و به برنامهٔ هفتگیِ پزشک وابسته
|
||||
نیست، پس پزشکی که خودش نوبت آنلاین نمیدهد هیچ «محل نوبتدهی»ای ندارد که سایت
|
||||
`clinic_uuid` را از آن بردارد. اگر پاسخِ بدون پارامتر فقط مطب شخصی را میداد، دستگاهِ
|
||||
قابلِ رزروِ چنین پزشکی هرگز در سایت پیدا نمیشد.
|
||||
|
||||
خروجی واقعی (اجرای محلی، بدون هدر `Authorization`):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"doctor_uuid": "f9c746ba-3b59-4e5f-a96a-986f5198c173",
|
||||
"clinic_uuid": "279859e9-be78-4ce9-aebd-e68f6f12126c",
|
||||
"resources": [
|
||||
{
|
||||
"uuid": "9bb129ac-6650-4078-8942-bef8d1ce844d",
|
||||
"name": "کندلا2021",
|
||||
"clinic_uuid": "279859e9-be78-4ce9-aebd-e68f6f12126c",
|
||||
"type": { "code": "laser_device", "name": "دستگاه لیزر" },
|
||||
"location": {
|
||||
"uuid": "6cafca59-8261-47f6-93d2-2d6e16f6aeb3",
|
||||
"title": "کلنیک مدیسا",
|
||||
"address": ""
|
||||
},
|
||||
"supervisor": {
|
||||
"uuid": "f9c746ba-3b59-4e5f-a96a-986f5198c173",
|
||||
"full_name": "پزشک دعوتشده"
|
||||
},
|
||||
"services": [
|
||||
{
|
||||
"uuid": "f3e8f166-8ec0-479b-a82a-b5133bb06698",
|
||||
"name": "لیزیر دست",
|
||||
"duration_minutes": 20,
|
||||
"price_rials": 2000000,
|
||||
"service_section": {
|
||||
"uuid": "fff74b7f-da59-4928-a549-e3ba96b43119",
|
||||
"name": "لیزیر"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**۲۰۰ با `resources: []`** — پزشکِ بدون منبع، منبعِ غیرفعال، سرویسِ خاموش، یا ردیفِ ارائهٔ
|
||||
غیرفعال. هیچکدام خطا نیستند.
|
||||
**۴۰۴:** پزشک یافت نشد (`ERR_VALIDATION_002`) · `clinic_uuid`ی که پزشک عضوش نیست
|
||||
(«محل نوبتدهی یافت نشد»).
|
||||
|
||||
`capacity` عمداً در پاسخ نیست: عددِ عملیاتیِ داخلِ کلینیک است و سایت مصرفی برایش ندارد.
|
||||
|
||||
### `GET /api/v1/appointment-resource-slots` (2026-08)
|
||||
|
||||
عمومی — بدون توکن. نسخهٔ عمومیِ `GET /api/v1/resource/{uuid}/service-slots`.
|
||||
|
||||
| پارامتر | توضیح |
|
||||
|---|---|
|
||||
| `resource_uuid` | الزامی |
|
||||
| `date` | `Y-m-d`، الزامی. تاریخِ تقویمیِ واقعی — «2026-13-99» رد میشود |
|
||||
| `service_item_uuids[]` | یک یا چند سرویسِ روشن؛ خالی ⇒ `422` |
|
||||
|
||||
`durations[]` که نسخهٔ پنلی میپذیرد اینجا **پشتیبانی نمیشود**: override مدت ابزار منشی
|
||||
است و در دست بازدیدکننده یعنی ساختنِ ظرفیتِ ساختگی.
|
||||
|
||||
مدت، اشغال، ظرفیت و چیدمانِ پشتسرهم دقیقاً مثل نسخهٔ پنلی است
|
||||
(`ResourceBookingSlotService`).
|
||||
|
||||
خروجی واقعی (بدون هدر `Authorization`؛ کوتاهشده):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"resource_uuid": "9bb129ac-6650-4078-8942-bef8d1ce844d",
|
||||
"date": "2026-08-10",
|
||||
"timezone": "Asia/Tehran",
|
||||
"total_duration_minutes": 20,
|
||||
"start_times": [
|
||||
{ "start": 1786339800, "end": 1786341000, "start_time": "09:00", "end_time": "09:20" },
|
||||
{ "start": 1786341000, "end": 1786342200, "start_time": "09:20", "end_time": "09:40" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**۴۲۲ — `field: resource_uuid`** (`ERR_VALIDATION_002`، «منبع یافت نشد»): منبعِ ناموجود،
|
||||
غیرفعال، یا منبعی که هیچ سرویسِ روشنی ندارد. عمداً ۴۲۲ است نه ۴۰۴، چون همان کدی است که
|
||||
`POST /api/v1/appointment` برای منبع برمیگرداند.
|
||||
|
||||
**۴۲۲ — `field: service_item_uuids`**: سرویسِ ناموجود (`ERR_VALIDATION_002`) · سرویسی که
|
||||
توگلِ آنلاینش خاموش است یا این منبع ارائهاش نمیدهد · سرویسِ بیمدت · فهرست خالی.
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"data": null,
|
||||
"errors": [
|
||||
{ "code": "ERR_VALIDATION_001", "message": "این سرویس برای نوبتدهی آنلاین فعال نیست", "field": "service_item_uuids" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**۴۲۲ — `field: date`**: فرمت یا تاریخِ ناموجود.
|
||||
|
||||
**۲۰۰ با `start_times: []`** — روزی که منبع شیفت ندارد یا کاملاً پر است. خطا نیست.
|
||||
|
||||
### `GET /api/v1/appointment-resource-month-availability/{resourceUuid}` (2026-08)
|
||||
|
||||
عمومی — بدون توکن. ورودیِ تقویمِ سایت؛ معادلِ منبعمحورِ
|
||||
`appointment-settings/month-availability/{doctorUuid}`.
|
||||
|
||||
| پارامتر | توضیح |
|
||||
|---|---|
|
||||
| `year` · `month` | **میلادی**، همان قرارداد نسخهٔ پزشکمحور |
|
||||
| `service_item_uuids[]` | الزامی |
|
||||
|
||||
سرویسها الزامیاند چون منبع اسلاتِ ثابت ندارد: «روز فعال» یعنی دستکم یک بازهٔ خالی به
|
||||
اندازهٔ مجموعِ مدتِ همین سرویسها. بدون آن، تقویم روزی را سبز نشان میداد که برای سرویسِ
|
||||
۹۰ دقیقهای جا ندارد.
|
||||
|
||||
`enabled_dates` و `disabled_dates` با هم **همهٔ** روزهای ماهاند؛ سایت روی همین دو فهرست
|
||||
تصمیم میگیرد. برخلاف نسخهٔ پزشکمحور فیلد `online_booking_enabled` ندارد — آن پرچم روی
|
||||
برنامهٔ هفتگیِ پزشک است و منبع همتایی برایش ندارد. مصرفکنندهٔ سایت نبودش را «روشن»
|
||||
تفسیر میکند.
|
||||
|
||||
خروجی واقعی (بدون توکن؛ فهرستها کوتاهشده):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"resource_uuid": "9bb129ac-6650-4078-8942-bef8d1ce844d",
|
||||
"year": 2026,
|
||||
"month": 9,
|
||||
"total_duration_minutes": 20,
|
||||
"enabled_dates": ["2026-09-01", "2026-09-02", "2026-09-05"],
|
||||
"disabled_dates": ["2026-09-03", "2026-09-04", "2026-09-10"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
خطاها: همان `resource_uuid` و `service_item_uuids`ِ اندپوینت بالا، بهعلاوهٔ **۴۲۲ با
|
||||
`field: month`** روی سال یا ماهِ نامعتبر.
|
||||
|
||||
**هزینه:** پیادهسازی همان الگوی حلقهٔ روزانهٔ نسخهٔ پزشکمحور است. اندازهگیری محلی روی
|
||||
ماهی با ۳۰ روز: حدود ۲۷ میلیثانیه در فراخوانی گرم (اولین فراخوانی ۸۷ میلیثانیه). بهینهسازی
|
||||
بازهای لازم نشد.
|
||||
|
||||
### `PUT /api/v1/resource/{uuid}/categories`
|
||||
|
||||
مجوز: `appointment_settings.update`.
|
||||
|
||||
Reference in New Issue
Block a user