feat(migrations): add clinic_id context to weekly_schedules, date_overrides, and holidays
- Introduced clinic_id to weekly_schedules, date_overrides, and holidays to differentiate between personal and clinic schedules. - Updated unique constraints and indexes to accommodate the new clinic context. feat(command): create AssignScheduleClinicCommand to move schedules - Added a command to move a doctor's personal weekly schedule into a clinic context. - Implemented checks to ensure sessions align with the target clinic. feat(context): implement EntityContext and EntityContextResolver - Created EntityContext to represent the effective working environment of a request (doctor or clinic). - Developed EntityContextResolver to determine the execution context based on user roles and active contexts. test: add ServiceModeContextTest for appointment scheduling - Implemented tests to ensure service booking respects clinic and personal contexts. - Verified that financial data is omitted in clinic contexts in InvitedDoctorDashboardScopeTest.
This commit is contained in:
@@ -685,3 +685,92 @@ Extra optional body fields: `service_section_uuid`, `service_item_uuid`, `staff_
|
||||
|
||||
### GET `/api/v1/my/appointments` (extended)
|
||||
New query param `reserve=1` → returns only reserve-list entries; without it only regular slot bookings are returned. Each row now also includes: `patient_uuid`, `is_reserve`, `deposit_required`, `deposit_amount_rials`, `note`, `service_section`, `service_item`, `staff` (each `{uuid, name|full_name}` or null).
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Booking context (2026-07)
|
||||
|
||||
A doctor may now hold several booking schedules — one for the personal practice and one per clinic.
|
||||
Every public booking endpoint therefore accepts an optional **`clinic_uuid`**:
|
||||
|
||||
| Endpoint | Where |
|
||||
|---|---|
|
||||
| `GET /api/v1/appointment-slots` | query |
|
||||
| `GET /api/v1/appointment-service-slots` | query |
|
||||
| `GET /api/v1/appointment-booking-services/{doctorUuid}` | query |
|
||||
| `GET /api/v1/appointment-settings/month-availability/{doctorUuid}` | query |
|
||||
| `POST /api/v1/appointment` | body |
|
||||
|
||||
Omitting it means the **personal practice** — it is never a wildcard. If the doctor is not a member
|
||||
of the given clinic → `404 ERR_VALIDATION_002` («محل نوبتدهی یافت نشد»). All four `GET`s echo back
|
||||
`clinic_uuid` so a client can tell which context answered.
|
||||
|
||||
On `POST /api/v1/appointment`, any `service_item_uuids` must belong to the same context, otherwise
|
||||
`422 ERR_VALIDATION_001` («سرویس انتخابشده به این محل نوبتدهی تعلق ندارد»). The appointment's
|
||||
`address_id` is resolved from that context's schedule.
|
||||
|
||||
> **Silent-failure warning:** before this change the location was inferred from the doctor's single
|
||||
> schedule. A client that does not send `clinic_uuid` will now book into the personal practice —
|
||||
> which is correct, but is a behaviour change for any doctor who also works in a clinic. Update
|
||||
> callers before relying on the default.
|
||||
|
||||
### GET `/api/v1/appointment-booking-locations/{doctorUuid}`
|
||||
|
||||
**Permission:** public.
|
||||
|
||||
Lists every place the doctor can be booked at. The site should show **all** of them, grouped by
|
||||
location — picking one and hiding the rest removes real capacity from the doctor.
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"doctor_uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"booking_locations": [
|
||||
{
|
||||
"location_uuid": "0f0b…",
|
||||
"type": "personal",
|
||||
"title": "مطب شخصی",
|
||||
"address": "یزد، خیابان …",
|
||||
"clinic_uuid": null,
|
||||
"booking_mode": "slot",
|
||||
"buffer_minutes": 0,
|
||||
"services": [],
|
||||
"next_available_at": 1755000000
|
||||
},
|
||||
{
|
||||
"location_uuid": "7c21…",
|
||||
"type": "clinic",
|
||||
"title": "کلینیک علی بهروزی",
|
||||
"address": "یزد، بلوار …",
|
||||
"clinic_uuid": "41e325c4-e825-4067-8438-5d828ecaee09",
|
||||
"booking_mode": "service",
|
||||
"buffer_minutes": 10,
|
||||
"services": [
|
||||
{ "uuid": "…", "name": "ویزیت", "duration_minutes": 20, "price_rials": 500000,
|
||||
"service_section": { "uuid": "…", "name": "عمومی" } }
|
||||
],
|
||||
"next_available_at": 1754900000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Notes |
|
||||
|---|---|---|
|
||||
| `location_uuid` | `string\|null` | the `DoctorAddress` uuid; `null` when the context has no address yet |
|
||||
| `type` | `"personal" \| "clinic"` | |
|
||||
| `booking_mode` | `"slot" \| "service"` | per-context — the same doctor can differ between locations |
|
||||
| `services` | `array` | populated only in `service` mode, scoped to that context's owner |
|
||||
| `next_available_at` | `int\|null` | Unix timestamp of the earliest free slot within 30 days |
|
||||
|
||||
Sorted by `next_available_at` ascending, so `booking_locations[0]` is the sensible default
|
||||
selection; locations with no capacity sort last. Deep links should carry the chosen location
|
||||
(`/doctor/{uuid}?location={location_uuid}`).
|
||||
|
||||
**Status codes:** `200`, `404 ERR_VALIDATION_002` (doctor not found).
|
||||
|
||||
**Consumer:** `nobat724_front` — the doctor page must render one booking block per entry and pass the
|
||||
matching `clinic_uuid` into the slot and booking calls.
|
||||
|
||||
Reference in New Issue
Block a user