fix(appointment): fall back to the resource's supervising doctor on public booking

A laser device is not a doctor, so booking one from the public site sent
resource_uuid and no doctor_uuid and got back "doctor_uuid یا resource_uuid
الزامی است" — a message telling the caller to send something it had already
sent. The panel path had resolved this from ClinicResource.supervisor since it
was written; only the public path had not, and the field was defined but never
read there.

A resource with no supervisor now gets its own message pointing at the actual
fix, instead of the generic one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hamed
2026-08-06 17:31:21 +03:30
co-authored by Claude Opus 5
parent 252e20bfe9
commit 12c1d2cbf4
3 changed files with 64 additions and 2 deletions
+13 -1
View File
@@ -250,7 +250,7 @@ Book an appointment slot.
| `slot_start` | integer | ✅ | Slot start (Unix timestamp) |
| `slot_end` | integer | ⚠️ | Slot end (Unix timestamp). **با `service_item_uuids` نادیده گرفته می‌شود** و سرور خودش حساب می‌کند (به مقدار کلاینت اعتماد نمی‌شود)؛ در آن حالت الزامی هم نیست. بدون سرویس، مقدار کلاینت حفظ می‌شود و الزامی است |
| `service_item_uuids` | string[] | ❌ | یک یا چند UUID سرویس. سرویس‌ها **ذخیره** می‌شوند (`service_items`)، اولین سرویس سرویسِ اصلی (`service_item`) است، و مدت/بافر روی نوبت ثبت می‌شود (`service_total_minutes` / `service_buffer_minutes`). UUID ناموجود، سرویسِ غیرbookable، سرویس بدون مدت، یا سرویسِ محیطی دیگر ⇒ `422` |
| `resource_uuid` | string (UUID) | ⚠️ | منبعی که نوبت **برایش** گرفته می‌شود (دستگاه، اتاق، یا خودِ پزشک). اگر داده شود `doctor_uuid` اختیاری است و برای منبعِ پزشک از خودش استنتاج می‌شود؛ محل نوبت هم از شعبهٔ همان منبع می‌آید. منبع باید در همان محیط رزرو باشد و اگر سرویس انتخاب‌شده را ارائه ندهد ⇒ `422` |
| `resource_uuid` | string (UUID) | ⚠️ | منبعی که نوبت **برایش** گرفته می‌شود (دستگاه، اتاق، یا خودِ پزشک). اگر داده شود `doctor_uuid` اختیاری است: برای منبعِ پزشک از خودش استنتاج می‌شود و برای دستگاه/اتاق از **پزشک ناظرِ** همان منبع؛ محل نوبت هم از شعبهٔ همان منبع می‌آید. منبع باید در همان محیط رزرو باشد و اگر سرویس انتخاب‌شده را ارائه ندهد ⇒ `422` |
| `for_self` | boolean | ❌ | `true` (default) = patient is the logged-in payer; `false` = booking for someone else |
| `patient_name` | string | ⚠️ | Required when `for_self=false`; otherwise filled from the payer's profile |
| `patient_mobile` | string | ⚠️ | Required when `for_self=false`; otherwise the payer's mobile |
@@ -263,6 +263,18 @@ Book an appointment slot.
> **`doctor_uuid` یا `resource_uuid`:** دست‌کم یکی الزامی است؛ نبودِ هر دو ⇒ `422`. مسیر
> قدیمیِ فقط-`doctor_uuid` دست‌نخورده است و سایت عمومی همان را می‌فرستد.
>
> **دستگاه پزشک نیست.** وقتی `resource_uuid` یک دستگاه یا اتاق است، پزشک از
> `ClinicResource.supervisor` برداشته می‌شود — اپراتور کار را می‌کند و پزشک پاسخگوی
> بالینی است. اگر منبع ناظر نداشته باشد پیام مخصوص خودش برمی‌گردد، نه پیامِ عمومیِ
> «`doctor_uuid` یا `resource_uuid` لازم است» که کلاینت هر دو را فرستاده بود:
>
> ```json
> {"success":false,"data":null,"errors":[{"code":"ERR_VALIDATION_002","message":"این منبع پزشک ناظر ندارد؛ ابتدا در تنظیمات منابع پزشک ناظر را مشخص کنید","field":"resource_uuid"}]}
> ```
>
> مسیر پنل (`POST /api/v1/my/appointment`) این رفتار را از قبل داشت؛ این تغییر مسیر
> عمومی را با آن هم‌تراز کرد.
>
> **پاسخ:** علاوه بر فیلدهای قبلی، `resource` (`uuid`, `name`, `type`) و `service_option`
> (`uuid`, `name`) برمی‌گردند. نوبت‌های پیش از مدل منبع‌محور هر دو را `null` دارند، پس
> کلاینت باید با `null` کنار بیاید.