feat(appointment): let a platform admin change a doctor's locked booking mode

The booking mode locks after the first save because existing appointments were
computed under that mode's rules. A lock with no key, though, traps a practice
that picked the wrong mode on day one, so ROLE_ADMIN can now open it.

The first attempt is still refused when the doctor has active appointments in
the next year, and says how many; the admin repeats the request with
force_mode_change to confirm they know what happens to those. The flag does
nothing for anyone else. GET now returns booking_mode_changeable so the panel
enables the toggle from the server's answer rather than guessing from the role.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hamed
2026-08-20 17:32:52 +03:30
co-authored by Claude Opus 5
parent 7096b8980d
commit a3fc7f5c8b
5 changed files with 274 additions and 19 deletions
+10
View File
@@ -88,6 +88,14 @@ Create or update the weekly schedule for a doctor (upsert).
>
> **ادغام هنگام ذخیره:** شیفت‌هایی که آدرسشان بیرون از دسترس درخواست‌کننده است، از نسخهٔ ذخیره‌شده دست‌نخورده برمی‌گردند؛ ورودی نه می‌تواند حذفشان کند نه عوضشان.
>
> **تغییر نوع نوبت‌دهی:** نوع پس از اولین ثبت قفل می‌شود، چون نوبت‌های ثبت‌شده با قواعد همان نوع محاسبه شده‌اند. فقط `ROLE_ADMIN` می‌تواند بازش کند:
>
> - غیر ادمین → `422 ERR_VALIDATION_001` روی `booking_mode` («نوع نوبت‌دهی پس از ثبت قابل تغییر نیست»).
> - ادمین، وقتی نوبت فعالی در ۳۶۵ روز آینده هست → `422` با تعداد آن نوبت‌ها. درخواست دوم با `force_mode_change: true` انجام می‌شود.
> - ادمین، بدون نوبت فعال آینده → بدون نیاز به `force_mode_change` انجام می‌شود.
>
> `force_mode_change` از سمت غیر ادمین بی‌اثر است.
> **نوبت‌دهی سرویسی:** با `meta.booking_mode = "service"` صاحبِ همان context باید حداقل یک سرویس با `bookable = true` داشته باشد؛ وگرنه `422 ERR_VALIDATION_001` روی فیلد `booking_mode`. پیام در محیط کلینیک به کلینیک اشاره می‌کند.
### Request Body (`application/json`)
@@ -715,6 +723,8 @@ Returns all locations a doctor can assign as `location_id` in their schedule ses
| `context` | `"personal" \| "clinic"` | **legacy**, always `"personal"` for the same reason |
| `locations` | `array` | every address of this doctor (personal + each clinic they belong to), for labelling shifts the caller may not edit — `GET` only |
| `selectable_location_ids` | `int[]` | the subset of those the **caller** may assign — `GET` only |
| `booking_mode_locked` | `bool` | a mode has already been stored |
| `booking_mode_changeable` | `bool` | this caller may unlock it — `ROLE_ADMIN` only, `GET` only |
`DateOverride.toArray()` returns the same two fields. `Holiday.toArray()` returns `clinic_uuid`
plus `scope` (`"global" | "clinic"`), and the list endpoint adds `editable` (see below).