feat(appointment-settings): let clinics manage each member doctor's booking
The API and React components were already parameterized by doctor uuid, but 14 copy-pasted identity checks limited every endpoint to "the doctor themselves or an admin", so a clinic owner could not touch a member doctor's booking setup. - Replaces those 14 checks with one denyDoctorAccess() that also admits the owner of a clinic the doctor belongs to, and a member doctor holding the clinic's appointment_settings permission (view for GET, update for writes). A doctor's own settings short-circuit before any permission lookup. - Moves ScheduleSection and its tabs out of DoctorDetailPage into components/schedule/ScheduleSection.tsx so the doctor panel and the new clinic page render the same module instead of one page importing another. Pure relocation — no logic changed. - Adds ClinicAppointmentSettingsPage: one tab per clinic doctor, each rendering that same section. The tab wrapper is keyed by doctor uuid so in-progress schedule edits cannot leak onto the wrong doctor. - insurance-pricing accepts an optional doctor_uuid (query on GET, body on PUT) under the same access rule, so the visit-price card works inside the clinic tabs. Fixes saveInsurancePricing calling getInsurancePricing with the wrong argument by extracting the shared pricingPayload(). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,23 @@
|
||||
# Appointment Settings API
|
||||
|
||||
> **Prefix:** `/api/v1/appointment-settings`
|
||||
> **Permission:** All write endpoints require `AUTH` — must be the doctor owner or `ROLE_ADMIN`
|
||||
> **Permission:** every endpoint requires `AUTH` and resolves access through one shared rule (below)
|
||||
|
||||
Doctors configure their availability via three resources: **weekly schedule**, **date overrides**, and **holidays**.
|
||||
|
||||
## Access rule
|
||||
|
||||
All 14 endpoints in this file share a single check. Given the target doctor (resolved from the path/body uuid, or from the parent schedule/override/holiday), access is granted when the caller is:
|
||||
|
||||
1. `ROLE_ADMIN`, **or**
|
||||
2. the doctor themselves, **or**
|
||||
3. the **owner of a clinic** the doctor belongs to, **or**
|
||||
4. a **doctor member of that clinic** holding the `appointment_settings` permission — `view` for `GET`, `update` for `POST`/`PATCH`/`DELETE` (see `docs/api/clinic.md` → *Clinic Doctor Permissions*)
|
||||
|
||||
Anything else → `403 ERR_AUTH_006`. This is what lets the clinic panel manage every member doctor's booking settings from `تنظیمات → نوبتدهی`, one tab per doctor, using the same endpoints the doctor's own panel calls.
|
||||
|
||||
A doctor's own settings are never affected by clinic permissions — rule 2 short-circuits before any permission lookup.
|
||||
|
||||
---
|
||||
|
||||
## Weekly Schedule
|
||||
@@ -29,7 +42,7 @@ Each doctor has **one** weekly schedule (upsert). The schedule is keyed by **day
|
||||
|
||||
Create or update the weekly schedule for a doctor (upsert).
|
||||
|
||||
**Permission:** `AUTH` — must be the doctor owner or `ROLE_ADMIN`
|
||||
**Permission:** `AUTH` — see [Access rule](#access-rule)
|
||||
|
||||
> **الزام آدرس:** هر session با `active=true` باید `location_id` (آدرس مطب/کلینیک) داشته باشد. در غیر این صورت `422 ERR_VALIDATION_001` («برای هر شیفت فعال باید آدرس انتخاب شود»). این آدرس هنگام رزرو خودکار روی نوبت ذخیره میشود.
|
||||
|
||||
@@ -190,7 +203,7 @@ Same structure as POST response.
|
||||
|
||||
Update weekly schedule. `{uuid}` can be schedule UUID or doctor UUID.
|
||||
|
||||
**Permission:** `AUTH` — must be the doctor owner or `ROLE_ADMIN`
|
||||
**Permission:** `AUTH` — see [Access rule](#access-rule)
|
||||
|
||||
### Request Body
|
||||
```json
|
||||
@@ -233,7 +246,7 @@ Updated schedule object (same structure as POST).
|
||||
|
||||
Delete a weekly schedule.
|
||||
|
||||
**Permission:** `AUTH` — must be the doctor owner or `ROLE_ADMIN`
|
||||
**Permission:** `AUTH` — see [Access rule](#access-rule)
|
||||
|
||||
> Note: route is `/booking-setting/`, not `/appointment-settings/`
|
||||
|
||||
@@ -259,7 +272,7 @@ Override a specific date — mark it inactive (day off) or give it custom sessio
|
||||
|
||||
Get all date overrides for a doctor.
|
||||
|
||||
**Permission:** `AUTH` — must be the owning doctor or `ROLE_ADMIN` (`403 ERR_AUTH_006` otherwise).
|
||||
**Permission:** `AUTH` — see [Access rule](#access-rule) (`403 ERR_AUTH_006` otherwise).
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
@@ -289,7 +302,7 @@ Get all date overrides for a doctor.
|
||||
|
||||
Create a date override.
|
||||
|
||||
**Permission:** `AUTH` — must be the doctor owner or `ROLE_ADMIN`
|
||||
**Permission:** `AUTH` — see [Access rule](#access-rule)
|
||||
|
||||
### Request Body
|
||||
```json
|
||||
@@ -380,7 +393,7 @@ Override object (same structure as above).
|
||||
|
||||
Update a date override.
|
||||
|
||||
**Permission:** `AUTH` — must be the doctor owner or `ROLE_ADMIN`
|
||||
**Permission:** `AUTH` — see [Access rule](#access-rule)
|
||||
|
||||
### Request Body (all optional)
|
||||
```json
|
||||
@@ -419,7 +432,7 @@ Updated override object.
|
||||
|
||||
Delete a date override.
|
||||
|
||||
**Permission:** `AUTH` — must be the doctor owner or `ROLE_ADMIN`
|
||||
**Permission:** `AUTH` — see [Access rule](#access-rule)
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
@@ -436,7 +449,7 @@ Mark a date range as holiday — all slots blocked, no overrides apply.
|
||||
|
||||
Get all holidays for a doctor.
|
||||
|
||||
**Permission:** `AUTH` — must be the owning doctor or `ROLE_ADMIN` (`403 ERR_AUTH_006` otherwise).
|
||||
**Permission:** `AUTH` — see [Access rule](#access-rule) (`403 ERR_AUTH_006` otherwise).
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
@@ -466,7 +479,7 @@ Get all holidays for a doctor.
|
||||
|
||||
Create a holiday range.
|
||||
|
||||
**Permission:** `AUTH` — must be the doctor owner or `ROLE_ADMIN`
|
||||
**Permission:** `AUTH` — see [Access rule](#access-rule)
|
||||
|
||||
### Request Body
|
||||
```json
|
||||
@@ -525,7 +538,7 @@ Get a single holiday.
|
||||
|
||||
Update a holiday.
|
||||
|
||||
**Permission:** `AUTH` — must be the doctor owner or `ROLE_ADMIN`
|
||||
**Permission:** `AUTH` — see [Access rule](#access-rule)
|
||||
|
||||
### Request Body (all optional)
|
||||
```json
|
||||
@@ -546,7 +559,7 @@ Updated holiday object.
|
||||
|
||||
Delete a holiday.
|
||||
|
||||
**Permission:** `AUTH` — must be the doctor owner or `ROLE_ADMIN`
|
||||
**Permission:** `AUTH` — see [Access rule](#access-rule)
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
@@ -584,7 +597,7 @@ The `SlotCalculatorService` calculates available slots in this priority order:
|
||||
|
||||
### `GET /api/v1/appointment-settings/available-locations/{doctorUuid}`
|
||||
|
||||
**Permission:** `AUTH` — must be the owning doctor or `ROLE_ADMIN` (`403 ERR_AUTH_006` otherwise).
|
||||
**Permission:** `AUTH` — see [Access rule](#access-rule) (`403 ERR_AUTH_006` otherwise).
|
||||
|
||||
Returns all locations a doctor can assign as `location_id` in their schedule sessions. Includes both the doctor's personal addresses and the addresses of all clinics they belong to.
|
||||
|
||||
|
||||
@@ -284,6 +284,13 @@ entity جاری از `#[CurrentUser]` resolve میشود: نقش `ROLE_DOCTOR
|
||||
|
||||
**Permission:** `AUTH` (`ROLE_DOCTOR` یا `ROLE_CLINIC`)
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `doctor_uuid` | string (UUID) | ❌ | قیمتگذاری همان پزشک را برمیگرداند بهجای موجودیت کاربر جاری. برای تبهای نوبتدهی پنل کلینیک. |
|
||||
|
||||
با `doctor_uuid`، دسترسی اینگونه بررسی میشود: `ROLE_ADMIN`، خودِ پزشک، مالک کلینیکی که پزشک عضو آن است، یا پزشکِ عضو همان کلینیک با مجوز `services.view` (برای `PUT`: `services.update`). در غیر این صورت `403 ERR_ACCESS_DENIED`؛ پزشکِ ناموجود `404 ERR_NOT_FOUND_001`. بدون این پارامتر رفتار قبلی (موجودیت کاربر جاری) دستنخورده است.
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
@@ -326,8 +333,11 @@ entity جاری از `#[CurrentUser]` resolve میشود: نقش `ROLE_DOCTOR
|
||||
**Permission:** `AUTH` (`ROLE_DOCTOR` یا `ROLE_CLINIC`)
|
||||
|
||||
### Request Body
|
||||
> `doctor_uuid` (اختیاری) در بدنه پذیرفته میشود و مثل نسخهٔ `GET` عمل میکند — همان قواعد دسترسی، با اکشن `services.update`.
|
||||
|
||||
```json
|
||||
{
|
||||
"doctor_uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"free_visit_price_rials": 5000000,
|
||||
"require_visit_price": true,
|
||||
"insurances": [
|
||||
|
||||
Reference in New Issue
Block a user