- Fix national code handling in staff creation and updates to support Persian digits. - Update ClinicStaff entity to allow longer national codes (up to 15 characters). - Implement support for clinic secretaries in SecretaryController, allowing creation without a doctor UUID. - Add a new endpoint to retrieve doctors associated with a clinic for secretary management. - Improve appointment management by ensuring doctors are selectable even when no appointments exist. - Extend PatientController to allow secretaries to create patient records if they have the appropriate permissions. - Introduce a PriceInput component for better price formatting in forms, supporting Persian digits. - Add a MockGateway for testing payment processes without real transactions. - Enhance SMS settings management with an approval flow for post-visit text messages, including new fields for pending text and status. - Update migrations to reflect changes in database schema for national codes and SMS settings.
279 lines
5.6 KiB
Markdown
279 lines
5.6 KiB
Markdown
# Patient Records & Sessions API
|
||
|
||
## Overview
|
||
|
||
Patient records track patients per entity (doctor or clinic). Each record holds multiple sessions (visits). Access requires an active subscription with the `patient_records` feature.
|
||
|
||
**Base path:** `/api/v1`
|
||
**Auth:** Bearer JWT (doctor, clinic, or secretary with `appointments.view` permission required)
|
||
|
||
---
|
||
|
||
## Endpoints
|
||
|
||
### List Patients
|
||
|
||
```
|
||
GET /api/v1/patients
|
||
```
|
||
|
||
Returns a paginated list of patient records belonging to the authenticated entity.
|
||
|
||
**Query params:**
|
||
|
||
| Param | Type | Default | Description |
|
||
|-------|------|---------|-------------|
|
||
| `page` | int | 1 | Page number |
|
||
| `limit` | int | 20 | Items per page (10–50) |
|
||
| `search` | string | — | Search by patient name or phone |
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": [
|
||
{
|
||
"uuid": "...",
|
||
"entity_type": "doctor",
|
||
"entity_id": 5,
|
||
"user": { "uuid": "...", "fullName": "علی رضایی", "phone": "09123456789" },
|
||
"created_by_type": "doctor",
|
||
"created_by_id": 5,
|
||
"created_at": 1718375000
|
||
}
|
||
],
|
||
"meta": {
|
||
"totalRecords": 42,
|
||
"totalPages": 3,
|
||
"currentPage": 1
|
||
}
|
||
}
|
||
```
|
||
|
||
**Errors:**
|
||
|
||
| Code | HTTP | Description |
|
||
|------|------|-------------|
|
||
| `ERR_SUBSCRIPTION_REQUIRED` | 403 | No active plan with `patient_records` feature |
|
||
|
||
---
|
||
|
||
### Create Patient Record
|
||
|
||
```
|
||
POST /api/v1/patient
|
||
```
|
||
|
||
Creates a patient record for a user under the current entity. If the record already exists, returns the existing record (idempotent).
|
||
|
||
**Request body:**
|
||
|
||
```json
|
||
{
|
||
"user_uuid": "string (required)"
|
||
}
|
||
```
|
||
|
||
**Response 201:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"uuid": "...",
|
||
"entity_type": "doctor",
|
||
"entity_id": 5,
|
||
"user": { "uuid": "...", "fullName": "...", "phone": "..." },
|
||
"created_by_type": "doctor",
|
||
"created_by_id": 5,
|
||
"created_at": 1718375000
|
||
}
|
||
}
|
||
```
|
||
|
||
**Errors:**
|
||
|
||
| Code | HTTP | Description |
|
||
|------|------|-------------|
|
||
| `ERR_VALIDATION_001` | 422 | `user_uuid` missing |
|
||
| `ERR_NOT_FOUND_001` | 404 | User not found |
|
||
| `ERR_SUBSCRIPTION_REQUIRED` | 403 | No `patient_records` feature |
|
||
|
||
---
|
||
|
||
### Get Patient Record
|
||
|
||
```
|
||
GET /api/v1/patient/{uuid}
|
||
```
|
||
|
||
Returns a single patient record.
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"uuid": "...",
|
||
"entity_type": "doctor",
|
||
"entity_id": 5,
|
||
"user": { "uuid": "...", "fullName": "...", "phone": "..." },
|
||
"created_by_type": "doctor",
|
||
"created_by_id": 5,
|
||
"created_at": 1718375000
|
||
}
|
||
}
|
||
```
|
||
|
||
**Errors:**
|
||
|
||
| Code | HTTP | Description |
|
||
|------|------|-------------|
|
||
| `ERR_PATIENT_NOT_FOUND` | 404 | Record not found or not owned by caller |
|
||
| `ERR_SUBSCRIPTION_REQUIRED` | 403 | No `patient_records` feature |
|
||
|
||
---
|
||
|
||
### List Patient Sessions
|
||
|
||
```
|
||
GET /api/v1/patient/{uuid}/sessions
|
||
```
|
||
|
||
Returns paginated sessions for a patient record.
|
||
|
||
**Query params:** `page`, `limit` (same as list)
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": [
|
||
{
|
||
"uuid": "...",
|
||
"record_uuid": "...",
|
||
"appointment_uuid": null,
|
||
"insurance_base_id": null,
|
||
"insurance_supplementary_id": null,
|
||
"visit_price_rials": 200000,
|
||
"base_insurance_discount_percent": "10.00",
|
||
"supplementary_discount_percent": "5.00",
|
||
"services_total_rials": 50000,
|
||
"final_price_rials": 230000,
|
||
"payment_method": "cash",
|
||
"notes": "...",
|
||
"created_at": 1718375000,
|
||
"updated_at": 1718375000
|
||
}
|
||
],
|
||
"meta": { "totalRecords": 8, "totalPages": 1, "currentPage": 1 }
|
||
}
|
||
```
|
||
|
||
**Errors:**
|
||
|
||
| Code | HTTP | Description |
|
||
|------|------|-------------|
|
||
| `ERR_PATIENT_NOT_FOUND` | 404 | Record not found or not owned by caller |
|
||
| `ERR_SUBSCRIPTION_REQUIRED` | 403 | No `patient_records` feature |
|
||
|
||
---
|
||
|
||
### Create Session
|
||
|
||
```
|
||
POST /api/v1/patient/{uuid}/session
|
||
```
|
||
|
||
Creates a new visit session for a patient record.
|
||
|
||
**Request body:**
|
||
|
||
```json
|
||
{
|
||
"visit_price_rials": 200000,
|
||
"base_insurance_discount_percent": 10,
|
||
"supplementary_discount_percent": 5,
|
||
"insurance_base_id": null,
|
||
"insurance_supplementary_id": null,
|
||
"payment_method": "cash",
|
||
"notes": "...",
|
||
"services": [
|
||
{
|
||
"service_item_uuid": "...",
|
||
"staff_uuid": null
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**Field notes:**
|
||
|
||
- `payment_method`: `cash` | `card` | `insurance` | `online` | `pending`
|
||
- `services`: array of service items to attach; `price_rials` is snapshot-copied from ServiceItem
|
||
- `final_price_rials` is computed: `(visit_price × (1 - base%) × (1 - supp%)) + services_total`
|
||
|
||
**Response 201:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": { ...session object... }
|
||
}
|
||
```
|
||
|
||
**Errors:**
|
||
|
||
| Code | HTTP | Description |
|
||
|------|------|-------------|
|
||
| `ERR_PATIENT_NOT_FOUND` | 404 | Record not found or not owned |
|
||
| `ERR_SUBSCRIPTION_REQUIRED` | 403 | No `patient_records` feature |
|
||
|
||
---
|
||
|
||
### Update Session
|
||
|
||
```
|
||
PATCH /api/v1/session/{uuid}
|
||
```
|
||
|
||
Updates mutable fields on a session.
|
||
|
||
**Request body (all optional):**
|
||
|
||
```json
|
||
{
|
||
"notes": "...",
|
||
"payment_method": "card"
|
||
}
|
||
```
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": { ...session object... }
|
||
}
|
||
```
|
||
|
||
**Errors:**
|
||
|
||
| Code | HTTP | Description |
|
||
|------|------|-------------|
|
||
| `ERR_SESSION_NOT_FOUND` | 404 | Session not found or not owned |
|
||
|
||
---
|
||
|
||
## Auto-Creation on Appointment Confirm
|
||
|
||
When an appointment's status changes to `confirmed` via `PATCH /api/v1/appointment/{uuid}/status`, the system automatically:
|
||
|
||
1. Creates a `PatientRecord` for the appointment's user (if not already existing) under the doctor entity
|
||
2. Creates a blank `PatientSession` linked to the appointment
|
||
|
||
This only runs if the doctor has the `patient_records` subscription feature active.
|