Files
clinicpro/docs/api/appointment.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

440 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
}
}