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
@@ -14,6 +14,7 @@ import { formatNumber, digitsOnly, todayIso, unixToIso } from '../../lib/utils';
import Modal from '../ui/Modal';
import ConfirmDialog from '../ui/ConfirmDialog';
import GlobalSearchableSelect from '../ui/SearchableSelect';
import NationalHolidaysCard from '../holidays/NationalHolidaysCard';
/**
* ساختار واحد تنظیمات نوبت‌دهی یک پزشک — برنامه هفتگی، استثناهای تاریخ و تعطیلات.
@@ -1363,6 +1364,10 @@ function HolidaysTab({ doctorUuid, clinicUuid, readOnly = false }: { doctorUuid:
return (
<div className="space-y-3">
{/* تقویم رسمی بالای تعطیلی اختصاصی: پزشک اول می‌بیند چه چیزی از قبل بسته است،
بعد تعطیلی خودش را رویش اضافه می‌کند. */}
<NationalHolidaysCard canUpdate={!readOnly} />
{!readOnly && (
<div className="flex justify-end">
<button type="button" onClick={() => { setEditing(null); setModalOpen(true); }}