feat(appointment): log cancellation events and show them in a Timeline
Introduce the first per-appointment event system. On cancel (via the status
or general update endpoints) an AppointmentEvent (type=cancelled, «نوبت لغو
شد») is recorded with the actor, cancel time, and an optional cancel_reason,
plus a warning-level app_log entry. New GET /appointment/{uuid}/events
returns the ordered event list. The admin appointment detail page renders a
Timeline section and the cancel dialog now collects an optional reason.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+43
-3
@@ -417,8 +417,9 @@ Change appointment status.
|
||||
### Request Body
|
||||
```json
|
||||
{
|
||||
"status": "cancelled",
|
||||
"version": 3
|
||||
"status": "cancelled_by_doctor",
|
||||
"version": 3,
|
||||
"cancel_reason": "بیمار درخواست لغو داد"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -426,6 +427,7 @@ Change appointment status.
|
||||
|-------|------|----------|-------------|
|
||||
| `status` | string | ✅ | New status value |
|
||||
| `version` | integer | ❌ | Optimistic lock version (prevents double-submit) |
|
||||
| `cancel_reason` | string | ❌ | Only when transitioning to `cancelled_by_doctor` / `cancelled_by_user`. Stored on the recorded cancellation event (Timeline). Ignored for other statuses. |
|
||||
|
||||
**Allowed Transitions by Role:**
|
||||
| Actor | Allowed transitions |
|
||||
@@ -434,6 +436,8 @@ Change appointment status.
|
||||
| Doctor / Secretary | `pending → confirmed`, `confirmed → completed`, `confirmed → no_show` |
|
||||
| Admin | Any transition |
|
||||
|
||||
> **Cancellation is logged.** When the status becomes `cancelled_by_doctor` or `cancelled_by_user`, an `AppointmentEvent` (type `cancelled`, title «نوبت لغو شد») is recorded with the actor (user id + name), the optional `cancel_reason`, and the cancel time — surfaced via `GET /api/v1/appointment/{uuid}/events`. A `warning`-level entry is also written to `app_log`.
|
||||
|
||||
### Response `200`
|
||||
Updated appointment object.
|
||||
|
||||
@@ -448,6 +452,42 @@ Updated appointment object.
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/appointment/{uuid}/events`
|
||||
|
||||
Appointment Timeline — chronological event history for one appointment. Currently records cancellation events; the structure is generic for future event types.
|
||||
|
||||
**Permission:** `IS_AUTHENTICATED_FULLY` — caller must be able to manage the appointment (`canManage`).
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `uuid` | string (UUID) | Appointment UUID |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"type": "cancelled",
|
||||
"title": "نوبت لغو شد",
|
||||
"actor_name": "دکتر حامد حسینی",
|
||||
"reason": "بیمار درخواست لغو داد",
|
||||
"created_at": 1784273931
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
Events are ordered oldest → newest. `data` is a flat array (single nesting). `actor_name` and `reason` may be `null`. `created_at` is a Unix timestamp.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_006` | 403 | Not allowed to manage this appointment |
|
||||
| `ERR_VALIDATION_002` | 404 | Appointment not found |
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
@@ -619,7 +659,7 @@ General update (ویرایش / جا به جایی / انتقال به رزرو /
|
||||
|
||||
- `slot_start`/`slot_end` must be sent together; moving to an occupied slot → `409`.
|
||||
- Relation uuids: empty string clears; unknown uuid → `422`.
|
||||
- `status` follows the same transition rules as `PATCH /appointment/{uuid}/status`.
|
||||
- `status` follows the same transition rules as `PATCH /appointment/{uuid}/status`. A transition to `cancelled_by_doctor`/`cancelled_by_user` records a cancellation event (Timeline) + `app_log` warning; an optional `cancel_reason` body field is stored on the event.
|
||||
- Optimistic lock via `version` → `409` on concurrent edit.
|
||||
|
||||
Response `200`: `{ success, data: { data: <appointment.toArray()> } }`
|
||||
|
||||
Reference in New Issue
Block a user