Files
clinicpro/docs/api/appointment-settings.md
T
hamedandClaude Opus 4.8 0f7a8c1162 docs(api): document booking window meta and month-availability endpoint
- appointment-settings.md: weekly-schedule meta (online_booking_enabled,
  booking_window value/unit), defaults, and the slot-gating behavior.
- appointment.md: new public month-availability endpoint (Gregorian
  year/month, disabled/enabled dates) and the empty-slots conditions for
  appointment-slots.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 16:27:10 +03:30

16 KiB

Appointment Settings API

Prefix: /api/v1/appointment-settings
Permission: All write endpoints require AUTH — must be the doctor owner or ROLE_ADMIN

Doctors configure their availability via three resources: weekly schedule, date overrides, and holidays.


Weekly Schedule

Each doctor has one weekly schedule (upsert). The schedule is keyed by day index (0=Saturday ... 6=Friday), each day containing a sessions array.

Day Index Convention

Index Day (EN) Day (FA)
"0" Saturday شنبه
"1" Sunday یکشنبه
"2" Monday دوشنبه
"3" Tuesday سه‌شنبه
"4" Wednesday چهارشنبه
"5" Thursday پنجشنبه
"6" Friday جمعه

POST /api/v1/appointment-settings/weekly-schedule

Create or update the weekly schedule for a doctor (upsert).

Permission: AUTH — must be the doctor owner or ROLE_ADMIN

Request Body (application/json)

{
  "doctor_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "schedule": {
    "0": {
      "sessions": [
        {
          "active": true,
          "location_id": 1973,
          "start_time": "09:00",
          "end_time": "13:00",
          "duration_per_patient": 20,
          "has_rest": true,
          "rest_interval": 60,
          "time_to_rest": 10,
          "patient_limit": null
        },
        {
          "active": true,
          "location_id": 1973,
          "start_time": "15:00",
          "end_time": "18:00",
          "duration_per_patient": 20,
          "has_rest": false,
          "rest_interval": 0,
          "time_to_rest": 0,
          "patient_limit": 5
        }
      ]
    },
    "1": { "sessions": [] },
    "2": { "sessions": [] },
    "3": { "sessions": [] },
    "4": { "sessions": [] },
    "5": { "sessions": [] },
    "6": { "sessions": [] }
  }
}
Field Type Required Description
doctor_uuid string (UUID) Doctor UUID
schedule object Keys "0" through "6" (day indices)
schedule.{n}.sessions array Array of session config objects
meta object Online-booking settings (see below)

Online-booking meta object:

{
  "meta": {
    "online_booking_enabled": true,
    "booking_window_value": 2,
    "booking_window_unit": "month"
  }
}
Field Type Required Description
online_booking_enabled boolean false = no online booking; the public slot/month endpoints return no availability
booking_window_value integer How far ahead patients may book (≥ 1)
booking_window_unit string "week" or "month"

Defaults when meta is absent: { online_booking_enabled: true, booking_window_value: 1, booking_window_unit: "month" }. meta is stored inside the schedule setting JSON (no DB migration) and is preserved when only schedule is sent. SlotCalculatorService rejects any date in the past, beyond today + value unit, or when online booking is disabled — for the weekly schedule, date overrides, and appointment-slots alike.

Session Config Object:

Field Type Required Description
active boolean Whether this session is active
location_id integer|null Doctor address/location ID
start_time string Session start "HH:MM"
end_time string Session end "HH:MM"
duration_per_patient integer Minutes per appointment slot
has_rest boolean Whether to insert rest breaks
rest_interval integer Work minutes before taking a rest break
time_to_rest integer Duration of each rest break (minutes)
patient_limit integer|null Max patients per session (null = unlimited)

Multiple sessions per day are supported (e.g., morning + afternoon). Sessions are sorted by start_time and overlapping ones are skipped.

Response 201

{
  "success": true,
  "data": {
    "data": {
      "uuid": "sched-uuid-...",
      "doctor_uuid": "550e8400-...",
      "schedule": {
        "0": { "sessions": [ { "active": true, "start_time": "09:00", ... } ] },
        "1": { "sessions": [] },
        "2": { "sessions": [] },
        "3": { "sessions": [] },
        "4": { "sessions": [] },
        "5": { "sessions": [] },
        "6": { "sessions": [] }
      },
      "meta": {
        "online_booking_enabled": true,
        "booking_window_value": 1,
        "booking_window_unit": "month"
      },
      "created_at": 1717000000,
      "updated_at": 1717000000
    }
  }
}

⚠️ Double-nested: Frontend extracts with data?.data?.data
This is an upsert — if a schedule already exists for the doctor, it is overwritten.

Errors

Code HTTP Description
ERR_AUTH_001 401 Missing token
ERR_VALIDATION_002 404 Doctor not found
ERR_AUTH_006 403 Not the doctor owner

GET /api/v1/appointment-settings/weekly-schedule/{uuid}

Get weekly schedule. {uuid} can be either the schedule UUID or the doctor UUID — the controller tries both.

Permission: AUTH (class-level IS_AUTHENTICATED_FULLY)

Response 200

Same structure as POST response.

Errors

Code HTTP Description
ERR_AUTH_001 401 Missing token
ERR_VALIDATION_002 404 Schedule not found

PATCH /api/v1/appointment-settings/weekly-schedule/{uuid}

Update weekly schedule. {uuid} can be schedule UUID or doctor UUID.

Permission: AUTH — must be the doctor owner or ROLE_ADMIN

Request Body

{
  "schedule": {
    "0": {
      "sessions": [
        {
          "active": true,
          "location_id": 1973,
          "start_time": "10:00",
          "end_time": "14:00",
          "duration_per_patient": 30,
          "has_rest": false,
          "rest_interval": 0,
          "time_to_rest": 0,
          "patient_limit": null
        }
      ]
    }
  }
}

Replaces the entire schedule object if provided.

Response 200

Updated schedule object (same structure as POST).

Errors

Code HTTP Description
ERR_AUTH_001 401 Missing token
ERR_AUTH_006 403 Not the doctor owner
ERR_VALIDATION_002 404 Schedule not found

DELETE /api/v1/booking-setting/{uuid}

Delete a weekly schedule.

Permission: AUTH — must be the doctor owner or ROLE_ADMIN

Note: route is /booking-setting/, not /appointment-settings/

Response 200

{ "success": true, "data": { "message": "برنامه هفتگی با موفقیت حذف شد" } }

Errors

Code HTTP Description
ERR_AUTH_001 401 Missing token
ERR_AUTH_006 403 Not the doctor owner
ERR_VALIDATION_002 404 Schedule not found

Date Overrides

Override a specific date — mark it inactive (day off) or give it custom sessions.

GET /api/v1/appointment-settings/date-override/list/{doctorUuid}

Get all date overrides for a doctor.

Permission: AUTH (class-level)

Response 200

{
  "success": true,
  "data": {
    "data": [
      {
        "uuid": "...",
        "doctor_uuid": "...",
        "date": 1718476800,
        "active": false,
        "reason": "تعطیل خاص",
        "custom_slots": [],
        "created_at": 1717000000
      }
    ]
  }
}

date is a Unix timestamp. active: false = entire day blocked. active: true with custom_slots = custom session schedule.


POST /api/v1/appointment-settings/date-override

Create a date override.

Permission: AUTH — must be the doctor owner or ROLE_ADMIN

Request Body

{
  "doctor_uuid": "550e8400-...",
  "date": "2024-06-20",
  "active": false,
  "reason": "تعطیل رسمی",
  "custom_slots": null
}
Field Type Required Description
doctor_uuid string (UUID) Doctor UUID
date string Date in Y-m-d format (e.g. "2024-06-20")
active boolean false = full day off (default); true = use custom_slots
reason string Reason for override
custom_slots array|null Custom sessions (same SessionConfig format as weekly schedule, see below)

custom_slots format when active: true:

{
  "custom_slots": [
    {
      "start_time": "14:00",
      "end_time": "18:00",
      "duration_per_patient": 20,
      "location_id": 1973,
      "has_rest": false,
      "rest_interval": 0,
      "time_to_rest": 0,
      "patient_limit": null
    }
  ]
}

Legacy format (backward compatible):

{
  "custom_slots": [
    { "start": "14:00", "end": "18:00", "duration": 20 }
  ]
}

Response 201

{
  "success": true,
  "data": {
    "data": {
      "uuid": "override-uuid-...",
      "doctor_uuid": "...",
      "date": 1718476800,
      "active": false,
      "reason": "تعطیل رسمی",
      "custom_slots": [],
      "created_at": 1717000000
    }
  }
}

Errors

Code HTTP Description
ERR_AUTH_001 401 Missing token
ERR_AUTH_006 403 Not the doctor owner
ERR_VALIDATION_002 404 Doctor not found
ERR_VALIDATION_001 422 Invalid date format

GET /api/v1/appointment-settings/date-override/{uuid}

Get a single date override.

Permission: AUTH (class-level)

Response 200

Override object (same structure as above).


PATCH /api/v1/appointment-settings/date-override/{uuid}

Update a date override.

Permission: AUTH — must be the doctor owner or ROLE_ADMIN

Request Body (all optional)

{
  "active": true,
  "reason": "جبران مرخصی",
  "custom_slots": [
    {
      "start_time": "14:00",
      "end_time": "18:00",
      "duration_per_patient": 20,
      "location_id": 1973,
      "has_rest": false,
      "rest_interval": 0,
      "time_to_rest": 0,
      "patient_limit": null
    }
  ],
  "date": "2024-06-21"
}

Response 200

Updated override object.

Errors

Code HTTP Description
ERR_AUTH_001 401 Missing token
ERR_AUTH_006 403 Not the doctor owner
ERR_VALIDATION_002 404 Override not found

DELETE /api/v1/appointment-settings/date-override/{uuid}

Delete a date override.

Permission: AUTH — must be the doctor owner or ROLE_ADMIN

Response 200

{ "success": true, "data": { "message": "Override با موفقیت حذف شد" } }

Holidays

Mark a date range as holiday — all slots blocked, no overrides apply.

GET /api/v1/appointment-settings/holidays/list/{doctorUuid}

Get all holidays for a doctor.

Permission: AUTH (class-level)

Response 200

{
  "success": true,
  "data": {
    "data": [
      {
        "uuid": "...",
        "doctor_uuid": "...",
        "start_date": 1719792000,
        "end_date": 1720656000,
        "reason": "تعطیلات تابستانی",
        "active": true,
        "created_at": 1717000000
      }
    ]
  }
}

start_date and end_date are Unix timestamps. Holidays take highest priority — they block the day even if a date override exists.


POST /api/v1/appointment-settings/holidays

Create a holiday range.

Permission: AUTH — must be the doctor owner or ROLE_ADMIN

Request Body

{
  "doctor_uuid": "550e8400-...",
  "start_date": "2024-07-01",
  "end_date": "2024-07-10",
  "reason": "تعطیلات تابستانی"
}
Field Type Required Description
doctor_uuid string (UUID) Doctor UUID
start_date string Start date Y-m-d
end_date string End date Y-m-d (must be ≥ start_date)
reason string Holiday reason

Response 201

{
  "success": true,
  "data": {
    "data": {
      "uuid": "holiday-uuid-...",
      "doctor_uuid": "...",
      "start_date": 1719792000,
      "end_date": 1720656000,
      "reason": "تعطیلات تابستانی",
      "active": true,
      "created_at": 1717000000
    }
  }
}

Errors

Code HTTP Description
ERR_AUTH_001 401 Missing token
ERR_AUTH_006 403 Not the doctor owner
ERR_VALIDATION_002 404 Doctor not found
ERR_VALIDATION_001 422 end_date before start_date or invalid format

GET /api/v1/appointment-settings/holidays/{uuid}

Get a single holiday.

Permission: AUTH (class-level)


PATCH /api/v1/appointment-settings/holidays/{uuid}

Update a holiday.

Permission: AUTH — must be the doctor owner or ROLE_ADMIN

Request Body (all optional)

{
  "start_date": "2024-07-02",
  "end_date": "2024-07-12",
  "reason": "تمدید تعطیلات",
  "active": false
}

Response 200

Updated holiday object.


DELETE /api/v1/appointment-settings/holidays/{uuid}

Delete a holiday.

Permission: AUTH — must be the doctor owner or ROLE_ADMIN

Response 200

{ "success": true, "data": { "message": "تعطیلات حذف شد" } }

Slot Calculation Logic (Reference)

The SlotCalculatorService calculates available slots in this priority order:

  1. Holiday — if date falls in a holiday range → return empty (no slots)
  2. Date Override — if a date override exists for this date:
    • active: false → return empty
    • active: true → use custom_slots sessions
  3. Weekly Schedule — use the day's sessions array (only active: true sessions, sorted by start_time, overlapping sessions skipped)

Slot output format:

[
  {
    "start": 1718438400,
    "end": 1718439600,
    "start_time": "09:00",
    "end_time": "09:20",
    "location_id": 1973
  }
]

Available Locations

GET /api/v1/appointment-settings/available-locations/{doctorUuid}

Permission: Public

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.

Response 200

{
  "success": true,
  "data": [
    {
      "id": "5",
      "uuid": "...",
      "type": "personal",
      "clinic_id": null,
      "clinic_name": null,
      "name": "مطب شخصی",
      "address": "تهران، ...",
      "telephone": "09121234567",
      "map": { "latitude": "35.7", "longitude": "51.4" },
      "city": { "id": "1", "name": "تهران" },
      "province": { "id": "8", "name": "تهران" }
    },
    {
      "id": "12",
      "uuid": "...",
      "type": "clinic",
      "clinic_id": "3",
      "clinic_name": "کلینیک نور",
      "name": "شعبه مرکزی",
      "address": "تهران، خیابان ولیعصر...",
      "telephone": "02112345678",
      "map": { "latitude": "35.699", "longitude": "51.337" },
      "city": { "id": "1", "name": "تهران" },
      "province": { "id": "8", "name": "تهران" }
    }
  ]
}
Field Description
type personal = doctor's own address; clinic = clinic address
clinic_id ID of the clinic (only for type=clinic)
clinic_name Name of the clinic (only for type=clinic)

Use id as the location_id value in weekly schedule sessions or date override sessions.

Errors

Code HTTP Description
ERR_VALIDATION_002 404 Doctor not found