- 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>
440 lines
11 KiB
Markdown
440 lines
11 KiB
Markdown
# Appointment API
|
||
|
||
> **Prefix:** `/api/v1/appointment*`
|
||
|
||
---
|
||
|
||
## GET `/api/v1/appointment-slots`
|
||
|
||
Get all appointment slots (available and booked) for a doctor on a specific date.
|
||
|
||
**Permission:** `PUBLIC`
|
||
|
||
### Query Parameters
|
||
| Param | Type | Required | Description |
|
||
|-------|------|----------|-------------|
|
||
| `doctor_uuid` | string (UUID) | ✅ | Doctor UUID |
|
||
| `date` | string | ✅ | Date in `Y-m-d` format (e.g. `2024-06-15`) |
|
||
|
||
### Response `200`
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"doctor_uuid": "550e8400-...",
|
||
"date": "2024-06-15",
|
||
"sessions": [
|
||
{
|
||
"start_time": "09:00",
|
||
"end_time": "13:00",
|
||
"slots": [
|
||
{
|
||
"start": 1718438400,
|
||
"end": 1718439600,
|
||
"start_time": "09:00",
|
||
"end_time": "09:20",
|
||
"location_id": null,
|
||
"is_available": true
|
||
},
|
||
{
|
||
"start": 1718439600,
|
||
"end": 1718440800,
|
||
"start_time": "09:20",
|
||
"end_time": "09:40",
|
||
"location_id": null,
|
||
"is_available": false
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"start_time": "15:00",
|
||
"end_time": "17:00",
|
||
"slots": [...]
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
> Returns **all** slots grouped by work shift. `is_available: false` means the slot has an active (pending/confirmed) appointment. Session boundaries match the doctor's `WeeklySchedule` or date override config.
|
||
>
|
||
> Returns an **empty** `sessions` array when the date is a holiday, a closed date override, in the past, beyond the doctor's booking window, or when online booking is disabled (see `meta` in `appointment-settings.md`).
|
||
|
||
### Errors
|
||
| Code | HTTP | Description |
|
||
|------|------|-------------|
|
||
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |
|
||
| `ERR_VALIDATION_001` | 422 | Missing or invalid date/doctor_uuid |
|
||
|
||
---
|
||
|
||
## GET `/api/v1/appointment-settings/month-availability/{doctorUuid}`
|
||
|
||
Which days of a month are bookable — used by the public calendar to grey out unavailable days.
|
||
|
||
**Permission:** `PUBLIC`
|
||
|
||
### Path Parameters
|
||
| Param | Type | Description |
|
||
|-------|------|-------------|
|
||
| `doctorUuid` | string (UUID) | Doctor UUID |
|
||
|
||
### Query Parameters
|
||
| Param | Type | Required | Description |
|
||
|-------|------|----------|-------------|
|
||
| `year` | integer | ✅ | **Gregorian** year (e.g. `2026`) |
|
||
| `month` | integer | ✅ | Gregorian month `1`–`12` |
|
||
|
||
> Input is Gregorian. A Jalali (Shamsi) front-end must convert the displayed month to the Gregorian month(s) it spans before calling.
|
||
|
||
### Response `200`
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"year": 2026,
|
||
"month": 6,
|
||
"disabled_dates": ["2026-06-01", "2026-06-17", "2026-06-26"],
|
||
"enabled_dates": ["2026-06-15", "2026-06-16", "2026-06-18"],
|
||
"online_booking_enabled": true,
|
||
"booking_window": { "value": 1, "unit": "month" }
|
||
}
|
||
}
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
|-------|------|-------------|
|
||
| `disabled_dates` | string[] | `Y-m-d` days with no bookable slot (holiday / closed override / non-working / past / out-of-window) |
|
||
| `enabled_dates` | string[] | `Y-m-d` days with at least one slot |
|
||
| `online_booking_enabled` | boolean | Doctor's online-booking flag |
|
||
| `booking_window` | object | `{ value, unit }` — `unit` is `week` or `month` |
|
||
|
||
### Errors
|
||
| Code | HTTP | Description |
|
||
|------|------|-------------|
|
||
| `ERR_VALIDATION_002` | 404 | Doctor not found |
|
||
| `ERR_VALIDATION_001` | 422 | Invalid year/month |
|
||
|
||
---
|
||
|
||
## POST `/api/v1/appointment`
|
||
|
||
Book an appointment slot.
|
||
|
||
**Permission:** `AUTH` — any authenticated user
|
||
|
||
### Request Body (`application/json`)
|
||
```json
|
||
{
|
||
"doctor_uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||
"slot_start": 1718438400,
|
||
"slot_end": 1718439600,
|
||
"note": "لطفاً سریع ویزیت شوم"
|
||
}
|
||
```
|
||
|
||
| Field | Type | Required | Description |
|
||
|-------|------|----------|-------------|
|
||
| `doctor_uuid` | string (UUID) | ✅ | Doctor UUID |
|
||
| `slot_start` | integer | ✅ | Slot start (Unix timestamp) |
|
||
| `slot_end` | integer | ✅ | Slot end (Unix timestamp) |
|
||
| `note` | string | ❌ | Patient note |
|
||
|
||
### Response `201`
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"uuid": "appt-uuid-...",
|
||
"doctor": { "uuid": "...", "title": "دکتر علی احمدی" },
|
||
"user": { "uuid": "...", "real_name": "..." },
|
||
"slot_start": 1718438400,
|
||
"slot_end": 1718439600,
|
||
"status": "pending",
|
||
"note": "...",
|
||
"price": 500000,
|
||
"created_at": 1717000000
|
||
}
|
||
}
|
||
```
|
||
|
||
**Appointment Status Values:**
|
||
| Value | Description |
|
||
|-------|-------------|
|
||
| `pending` | Awaiting payment |
|
||
| `confirmed` | Paid and confirmed |
|
||
| `cancelled` | Cancelled |
|
||
| `completed` | Visit completed |
|
||
| `no_show` | Patient did not show |
|
||
|
||
### Errors
|
||
| Code | HTTP | Description |
|
||
|------|------|-------------|
|
||
| `ERR_AUTH_001` | 401 | Missing token |
|
||
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |
|
||
| `ERR_CONFLICT_001` | 409 | Slot already booked |
|
||
| `ERR_VALIDATION_001` | 422 | Invalid slot times |
|
||
|
||
---
|
||
|
||
## GET `/api/v1/appointment/{uuid}`
|
||
|
||
Get appointment detail.
|
||
|
||
**Permission:** `AUTH` — must be the patient, the doctor, or `ROLE_ADMIN`
|
||
|
||
### Path Parameters
|
||
| Param | Type | Description |
|
||
|-------|------|-------------|
|
||
| `uuid` | string (UUID) | Appointment UUID |
|
||
|
||
### Response `200`
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"uuid": "appt-uuid-...",
|
||
"doctor": {
|
||
"uuid": "...",
|
||
"title": "دکتر علی احمدی",
|
||
"image": "https://..."
|
||
},
|
||
"user": {
|
||
"uuid": "...",
|
||
"real_name": "کاربر",
|
||
"mobile_number": "09..."
|
||
},
|
||
"slot_start": 1718438400,
|
||
"slot_end": 1718439600,
|
||
"status": "confirmed",
|
||
"note": "...",
|
||
"price": 500000,
|
||
"payment_uuid": "pay-uuid-...",
|
||
"created_at": 1717000000
|
||
}
|
||
}
|
||
```
|
||
|
||
### Errors
|
||
| Code | HTTP | Description |
|
||
|------|------|-------------|
|
||
| `ERR_AUTH_001` | 401 | Missing token |
|
||
| `ERR_FORBIDDEN_001` | 403 | Not the patient/doctor/admin |
|
||
| `ERR_NOT_FOUND_001` | 404 | Appointment not found |
|
||
|
||
---
|
||
|
||
## GET `/api/v1/appointments/doctor/{doctorUuid}`
|
||
|
||
Get all appointments for a specific doctor.
|
||
|
||
**Permission:** `AUTH` — must be the doctor, their secretary, or `ROLE_ADMIN`
|
||
|
||
### Path Parameters
|
||
| Param | Type | Description |
|
||
|-------|------|-------------|
|
||
| `doctorUuid` | string (UUID) | Doctor UUID |
|
||
|
||
### Query Parameters
|
||
| Param | Type | Required | Description |
|
||
|-------|------|----------|-------------|
|
||
| `status` | string | ❌ | Filter: `pending`, `confirmed`, `cancelled`, `completed`, `no_show` |
|
||
|
||
### Response `200`
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": [
|
||
{
|
||
"uuid": "...",
|
||
"user": { "uuid": "...", "real_name": "..." },
|
||
"slot_start": 1718438400,
|
||
"slot_end": 1718439600,
|
||
"status": "confirmed",
|
||
"price": 500000
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### Errors
|
||
| Code | HTTP | Description |
|
||
|------|------|-------------|
|
||
| `ERR_AUTH_001` | 401 | Missing token |
|
||
| `ERR_FORBIDDEN_001` | 403 | Not authorized to view this doctor's appointments |
|
||
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |
|
||
|
||
---
|
||
|
||
## GET `/api/v1/appointments/user`
|
||
|
||
Get all appointments for the authenticated user.
|
||
|
||
**Permission:** `AUTH`
|
||
|
||
### Query Parameters
|
||
| Param | Type | Required | Description |
|
||
|-------|------|----------|-------------|
|
||
| `status` | string | ❌ | Filter by status |
|
||
|
||
### Response `200`
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": [
|
||
{
|
||
"uuid": "...",
|
||
"doctor": { "uuid": "...", "title": "دکتر علی احمدی" },
|
||
"slot_start": 1718438400,
|
||
"slot_end": 1718439600,
|
||
"status": "confirmed",
|
||
"price": 500000
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### Errors
|
||
| Code | HTTP | Description |
|
||
|------|------|-------------|
|
||
| `ERR_AUTH_001` | 401 | Missing token |
|
||
|
||
---
|
||
|
||
## PATCH `/api/v1/appointment/{uuid}/status`
|
||
|
||
Change appointment status.
|
||
|
||
**Permission:** `AUTH` — patient can cancel; doctor/secretary can confirm/complete/no_show; admin can do all
|
||
|
||
### Path Parameters
|
||
| Param | Type | Description |
|
||
|-------|------|-------------|
|
||
| `uuid` | string (UUID) | Appointment UUID |
|
||
|
||
### Request Body
|
||
```json
|
||
{
|
||
"status": "cancelled",
|
||
"version": 3
|
||
}
|
||
```
|
||
|
||
| Field | Type | Required | Description |
|
||
|-------|------|----------|-------------|
|
||
| `status` | string | ✅ | New status value |
|
||
| `version` | integer | ❌ | Optimistic lock version (prevents double-submit) |
|
||
|
||
**Allowed Transitions by Role:**
|
||
| Actor | Allowed transitions |
|
||
|-------|---------------------|
|
||
| Patient | `pending → cancelled` |
|
||
| Doctor / Secretary | `pending → confirmed`, `confirmed → completed`, `confirmed → no_show` |
|
||
| Admin | Any transition |
|
||
|
||
### Response `200`
|
||
Updated appointment object.
|
||
|
||
### Errors
|
||
| Code | HTTP | Description |
|
||
|------|------|-------------|
|
||
| `ERR_AUTH_001` | 401 | Missing token |
|
||
| `ERR_FORBIDDEN_001` | 403 | Not authorized for this transition |
|
||
| `ERR_NOT_FOUND_001` | 404 | Appointment not found |
|
||
| `ERR_CONFLICT_001` | 409 | Version mismatch (optimistic lock) |
|
||
| `ERR_VALIDATION_001` | 422 | Invalid status value |
|
||
|
||
---
|
||
|
||
## POST `/api/v1/my/appointment`
|
||
|
||
Create a new appointment for a patient. Used by doctor/clinic/secretary to book appointments on behalf of patients. If no user exists with the given mobile, a new user account is created automatically.
|
||
|
||
**Auth:** `IS_AUTHENTICATED_FULLY` — Roles: `ROLE_DOCTOR`, `ROLE_CLINIC`, `ROLE_SECRETARY`, `ROLE_ADMIN`
|
||
|
||
### Request Body
|
||
```json
|
||
{
|
||
"doctor_uuid": "doctor-uuid",
|
||
"slot_start": 1718438400,
|
||
"slot_end": 1718439600,
|
||
"patient_mobile": "09123456789",
|
||
"patient_name": "علی محمدی",
|
||
"note": "optional note"
|
||
}
|
||
```
|
||
|
||
> اگر کاربری با این شماره موبایل وجود نداشته باشد، یک کاربر جدید با نقش `ROLE_USER` ساخته میشود.
|
||
|
||
### Response `201`
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"uuid": "appt-uuid",
|
||
"slot_start": 1718438400,
|
||
"slot_end": 1718439600,
|
||
"status": "pending"
|
||
}
|
||
}
|
||
```
|
||
|
||
### Error Responses
|
||
| Code | HTTP | Description |
|
||
|------|------|-------------|
|
||
| `FORBIDDEN` | 403 | Role not allowed |
|
||
| `VALIDATION` | 422 | Missing required fields |
|
||
| `DOCTOR_NOT_FOUND` | 404 | Doctor UUID not found |
|
||
| `SLOT_TAKEN` | 409 | Slot already booked |
|
||
|
||
---
|
||
|
||
## GET /api/v1/my/appointments
|
||
|
||
Role-aware paginated list of appointments. Returns only what the authenticated user is authorized to see.
|
||
|
||
**Auth:** `IS_AUTHENTICATED_FULLY` (any role)
|
||
|
||
**Role behavior:**
|
||
| Role | Scope |
|
||
|------|-------|
|
||
| `ROLE_ADMIN` | All appointments |
|
||
| `ROLE_CLINIC` | Appointments for doctors in this clinic |
|
||
| `ROLE_DOCTOR` | Appointments for this doctor |
|
||
| `ROLE_SECRETARY` | Appointments for the linked doctor (empty if `appointments.view` permission is false) |
|
||
|
||
### Query Parameters
|
||
| Param | Type | Default | Description |
|
||
|-------|------|---------|-------------|
|
||
| `page` | int | 1 | Page number |
|
||
| `limit` | int | 15 | Items per page (max 100) |
|
||
| `search` | string | — | Search by mobile, real name, or doctor name |
|
||
| `status` | string | — | Filter by appointment status |
|
||
| `date` | string | — | Filter by date in `Y-m-d` format |
|
||
|
||
### Response `200`
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": [
|
||
{
|
||
"uuid": "string",
|
||
"patient_name": "string",
|
||
"patient_mobile": "string",
|
||
"doctor_name": "string",
|
||
"clinic_name": "string | null",
|
||
"appointment_date": "2026-07-25",
|
||
"appointment_time": "14:30",
|
||
"slot_start": 1700000000,
|
||
"status": "reserved",
|
||
"amount": 0,
|
||
"created_at": "ISO 8601 string"
|
||
}
|
||
],
|
||
"meta": {
|
||
"totalRecords": 8000,
|
||
"totalPages": 533,
|
||
"currentPage": 1
|
||
}
|
||
}
|