feat(holidays): one official calendar, inherited everywhere

The holiday model was already right — national holidays global, a per-tenant
override in both directions, per-doctor and per-resource exceptions — but
nothing could create a national holiday. The only writer was an import
command, so the calendar the whole product inherits from had no owner.

Three admin-only routes give it one. POST upserts, because `date` is unique
and re-sending a day should rename it rather than surface a raw database
error; PATCH takes only the title, because moving a date means a different
holiday. The system admin has no work environment, so the list endpoint now
returns the calendar with an empty `overrides` for that role instead of the
403 `pair()` would raise — the person who maintains the calendar has to be
able to read it.

Both holiday tabs — the doctor's and the resource's — now open with the
official calendar above their own exceptions, from one shared card rather
than two copies that would drift. Each row can be opted out of with a single
click, which is the existing holiday-override endpoint.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-08-02 15:39:48 +03:30
co-authored by Claude Opus 5
parent dd284ec622
commit f144401dd3
11 changed files with 505 additions and 9 deletions
+33
View File
@@ -179,6 +179,39 @@
`overridden_working` کنار خودِ تعطیلی می‌آید تا UI مجبور نباشد دو فهرست را تطبیق دهد:
`null` یعنی این محیط استثنایی ندارد، `true` یعنی آن روز باز است.
### مدیر سیستم — ساخت و ویرایش تقویم رسمی
`ROLE_ADMIN` فقط. تعطیل رسمی به هیچ محیطی تعلق ندارد، پس ساختنش هم کارِ هیچ کلینیکی
نیست؛ کلینیکی که آن روز باز است با `holiday-overrides` استثنا می‌زند.
| متد | مسیر | بدنه |
|---|---|---|
| POST | `/api/v1/admin/national-holidays` | `{ "jalali_date": "1405-01-13", "title": "سیزده‌بدر" }` |
| PATCH | `/api/v1/admin/national-holiday/{uuid}` | `{ "title": "…" }` |
| DELETE | `/api/v1/admin/national-holiday/{uuid}` | — |
**۲۰۱** (خروجی واقعی تست):
```json
{
"success": true,
"data": {
"uuid": "…", "date": 1774472400, "jalali_date": "1405-01-13",
"jalali_year": 1405, "title": "سیزده‌بدر"
}
}
```
`POST` روی روزی که از قبل هست **upsert** است و عنوان را عوض می‌کند — `date` کلید یکتا
دارد و خطای خام دیتابیس رفتار درستی نیست. `PATCH` فقط عنوان را می‌گیرد: جابه‌جا کردن
تاریخ یعنی یک تعطیلِ دیگر، پس حذف و ثبت دوباره.
**۴۰۳:** هر نقشی جز ادمین (تأیید زنده: کاربر کلینیک روی همین مسیر ۴۰۳ گرفت).
**۴۲۲:** `jalali_date` بدقالب · نبودِ `title`. **۴۰۴:** uuid ناشناخته.
> `GET /api/v1/national-holidays` برای ادمین هم کار می‌کند: او محیط کاری ندارد، پس
> بخش `overrides` برایش خالی برمی‌گردد و فقط تقویم را می‌بیند.
### `POST /api/v1/holiday-overrides`
| فیلد | نوع | توضیح |