Add API documentation for Representation, Secretary, Settlement, SMS, Specialty, Tag, and User Profile endpoints
This commit is contained in:
@@ -134,3 +134,26 @@ City IDs (integer FK to `categories.id` where `bundle='city'`) are stored on ent
|
||||
5. Add UI components: `<DataTable>`, `<Pagination>`, `<Modal>`, `<ConfirmDialog>`
|
||||
|
||||
**Category API endpoint pattern:** `GET /api/v1/categorys/{bundle}` (note: typo `categorys` is intentional — existing route). Response is double-nested: extract array with `data?.data?.data ?? []`.
|
||||
|
||||
---
|
||||
|
||||
## Standing Rule — API Documentation
|
||||
|
||||
**Whenever any API endpoint is created or modified** (controller file, route, request/response structure, error code, permission), the corresponding file in `docs/api/` **must be updated in the same session**.
|
||||
|
||||
| Changed file | Doc to update |
|
||||
|---|---|
|
||||
| `src/Auth/*` | `docs/api/auth.md` |
|
||||
| `src/Doctor/*` | `docs/api/doctor.md` |
|
||||
| `src/Clinic/*` | `docs/api/clinic.md` + `docs/api/clinic-invitation.md` |
|
||||
| `src/Appointment/Controller/AppointmentController.php` | `docs/api/appointment.md` |
|
||||
| `src/Appointment/Controller/AppointmentSettings*` | `docs/api/appointment-settings.md` |
|
||||
| `src/Payment/*` | `docs/api/payment.md` |
|
||||
| `src/Settlement/*` | `docs/api/settlement.md` |
|
||||
| `src/Rating/*` | `docs/api/rating.md` |
|
||||
| `src/Secretary/*` | `docs/api/secretary.md` |
|
||||
| `src/Representation/*` | `docs/api/representation.md` |
|
||||
| `src/Sms/*` | `docs/api/sms.md` |
|
||||
| `src/Blog/*` | `docs/api/blog.md` |
|
||||
| `src/Admin/*` | `docs/api/admin.md` |
|
||||
| Category/Province/City controllers | `docs/api/location.md`, `docs/api/specialty.md`, `docs/api/insurance.md`, `docs/api/doctor-service.md`, `docs/api/tag.md` |
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
# ClinicPro — API Documentation Index
|
||||
|
||||
> **Base URL:** `https://clinic-pro.ddev.site`
|
||||
> **API Prefix:** `/api/v1`
|
||||
> **Swagger UI:** `https://clinic-pro.ddev.site/api/doc` — user: `admin` / pass: `clinic123`
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
All protected endpoints require:
|
||||
```
|
||||
Authorization: Bearer <JWT_TOKEN>
|
||||
```
|
||||
|
||||
| Role | Description |
|
||||
|------|-------------|
|
||||
| `PUBLIC` | No token required |
|
||||
| `AUTH` | Any valid JWT |
|
||||
| `ROLE_ADMIN` | Admin user |
|
||||
| `ROLE_DOCTOR` | Doctor user |
|
||||
| `ROLE_CLINIC` | Clinic owner |
|
||||
| `ROLE_SECRETARY` | Secretary |
|
||||
|
||||
---
|
||||
|
||||
## Standard Response Envelope
|
||||
|
||||
```json
|
||||
// Success
|
||||
{ "success": true, "data": { ... } }
|
||||
|
||||
// Paginated
|
||||
{ "success": true, "data": [...], "meta": { "totalRecords": 100, "totalPages": 5, "currentPage": 1 } }
|
||||
|
||||
// Error
|
||||
{ "success": false, "data": null, "errors": [{ "code": "ERR_XXX_000", "message": "..." }] }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Modules
|
||||
|
||||
| File | Domain | Endpoints |
|
||||
|------|--------|-----------|
|
||||
| [auth.md](auth.md) | Authentication — OTP, Login, JWT | 8 |
|
||||
| [doctor.md](doctor.md) | Doctor profile & addresses | 11 |
|
||||
| [clinic.md](clinic.md) | Clinics | 7 |
|
||||
| [clinic-invitation.md](clinic-invitation.md) | Doctor invitations to clinics | 8 |
|
||||
| [appointment.md](appointment.md) | Appointments & slot booking | 6 |
|
||||
| [appointment-settings.md](appointment-settings.md) | Weekly schedule, date overrides, holidays | 14 |
|
||||
| [payment.md](payment.md) | Payments (Mellat / Sep) | 5 |
|
||||
| [settlement.md](settlement.md) | Wallet & settlement requests | 7 |
|
||||
| [rating.md](rating.md) | Ratings, comments, likes | 9 |
|
||||
| [secretary.md](secretary.md) | Doctor secretaries | 5 |
|
||||
| [representation.md](representation.md) | Representations (agents) | 6 |
|
||||
| [sms.md](sms.md) | SMS send & templates | 10 |
|
||||
| [blog.md](blog.md) | Blog posts | 6 |
|
||||
| [specialty.md](specialty.md) | Medical specialties | 5 |
|
||||
| [insurance.md](insurance.md) | Insurances & doctor-insurance links | 10 |
|
||||
| [doctor-service.md](doctor-service.md) | Doctor services | 5 |
|
||||
| [tag.md](tag.md) | Blog tags | 5 |
|
||||
| [location.md](location.md) | Provinces & cities | 10 |
|
||||
| [user-profile.md](user-profile.md) | User medical profile | 4 |
|
||||
| [admin.md](admin.md) | Admin dashboard & management | 25+ |
|
||||
|
||||
---
|
||||
|
||||
## Error Code Reference
|
||||
|
||||
| Code | Message (FA) | HTTP |
|
||||
|------|--------------|------|
|
||||
| `ERR_AUTH_001` | توکن JWT منقضی یا نامعتبر | 401 |
|
||||
| `ERR_AUTH_002` | کد OTP نامعتبر | 401 |
|
||||
| `ERR_AUTH_003` | کد OTP منقضی شده | 401 |
|
||||
| `ERR_AUTH_004` | تعداد تلاشهای OTP به حد مجاز رسیده | 429 |
|
||||
| `ERR_AUTH_005` | نام کاربری یا رمز عبور اشتباه | 401 |
|
||||
| `ERR_AUTH_006` | دسترسی ممنوع | 403 |
|
||||
| `ERR_VALIDATION_001` | ورودی نامعتبر | 422 |
|
||||
| `ERR_VALIDATION_002` | فیلد الزامی وارد نشده | 422 |
|
||||
| `ERR_NOT_FOUND_001` | منبع درخواستی یافت نشد | 404 |
|
||||
| `ERR_CONFLICT_001` | تداخل: منبع در حال استفاده | 409 |
|
||||
| `ERR_FORBIDDEN_001` | دسترسی به این منبع مجاز نیست | 403 |
|
||||
| `ERR_PAYMENT_001` | درگاه پرداخت در دسترس نیست | 503 |
|
||||
| `ERR_PAYMENT_002` | مبلغ پرداخت نامعتبر | 422 |
|
||||
| `ERR_PAYMENT_003` | وضعیت نوبت برای پرداخت مناسب نیست | 422 |
|
||||
| `ERR_FILE_001` | فرمت فایل مجاز نیست | 422 |
|
||||
| `ERR_SMS_003` | تمپلیت قبلاً ارسال شده | 422 |
|
||||
| `ERR_SECRETARY_001` | پلن فعلی اجازه منشی بیشتر نمیدهد | 422 |
|
||||
| `ERR_RATE_LIMIT_001` | درخواستهای زیاد، بعداً تلاش کنید | 429 |
|
||||
@@ -0,0 +1,679 @@
|
||||
# Admin API
|
||||
|
||||
> **Prefix:** `/api/v1/admin`
|
||||
> **Permission:** ALL endpoints in this file require `ROLE_ADMIN`
|
||||
> **Headers:** `Authorization: Bearer <admin_jwt_token>`
|
||||
|
||||
---
|
||||
|
||||
## Dashboard
|
||||
|
||||
### GET `/api/v1/admin/dashboard/stats`
|
||||
|
||||
Get key performance indicators (KPIs) for the dashboard.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"total_users": 1200,
|
||||
"active_doctors": 85,
|
||||
"total_doctors": 92,
|
||||
"total_clinics": 34,
|
||||
"today_appointments": 47,
|
||||
"total_appointments": 8540,
|
||||
"today_payments_count": 30,
|
||||
"today_payments_amount": 15000000,
|
||||
"total_payments_amount": 425000000,
|
||||
"pending_comments": 12,
|
||||
"pending_settlements": 5,
|
||||
"this_month_revenue": 52000000,
|
||||
"this_month_appointments": 620
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET `/api/v1/admin/dashboard/charts`
|
||||
|
||||
Get chart data for the last 30 days.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"appointments_30d": [
|
||||
{ "date": "2024-06-01", "count": 42 }
|
||||
],
|
||||
"revenue_30d": [
|
||||
{ "date": "2024-06-01", "amount_rials": 21000000 }
|
||||
],
|
||||
"appointment_status": {
|
||||
"confirmed": 350,
|
||||
"completed": 180,
|
||||
"cancelled": 45,
|
||||
"pending": 20,
|
||||
"no_show": 25
|
||||
},
|
||||
"top_specialties": [
|
||||
{ "name": "قلب و عروق", "count": 120 }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET `/api/v1/admin/dashboard/recent`
|
||||
|
||||
Get recent activity (last 10 of each type).
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"appointments": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"doctor_title": "دکتر علی احمدی",
|
||||
"patient_name": "محمد رضایی",
|
||||
"slot_start": 1718438400,
|
||||
"status": "confirmed"
|
||||
}
|
||||
],
|
||||
"payments": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"amount_rials": 500000,
|
||||
"gateway": "mellat",
|
||||
"status": "paid",
|
||||
"created_at": 1717000000
|
||||
}
|
||||
],
|
||||
"users": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"real_name": "محمد رضایی",
|
||||
"mobile_number": "09...",
|
||||
"roles": ["ROLE_USER"],
|
||||
"created_at": 1717000000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## User Management
|
||||
|
||||
### GET `/api/v1/admin/users`
|
||||
|
||||
List all users with pagination and filters.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `page` | integer | ❌ | Default: 1 |
|
||||
| `limit` | integer | ❌ | Default: 20 |
|
||||
| `search` | string | ❌ | Search by name or mobile |
|
||||
| `role` | string | ❌ | Filter: `ROLE_USER`, `ROLE_DOCTOR`, `ROLE_ADMIN`, etc. |
|
||||
| `status` | string | ❌ | `"active"` or `"inactive"` |
|
||||
| `sort` | string | ❌ | `"created_at"` (default desc) |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"real_name": "علی احمدی",
|
||||
"mobile_number": "09123456789",
|
||||
"roles": ["ROLE_USER"],
|
||||
"status": "active",
|
||||
"created_at": 1717000000
|
||||
}
|
||||
],
|
||||
"meta": { "totalRecords": 1200, "totalPages": 60, "currentPage": 1 }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET `/api/v1/admin/users/{uuid}`
|
||||
|
||||
Get detailed user info.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"uuid": "...",
|
||||
"real_name": "علی احمدی",
|
||||
"mobile_number": "09123456789",
|
||||
"roles": ["ROLE_USER"],
|
||||
"status": "active",
|
||||
"wallet_balance_rials": 500000,
|
||||
"appointments_count": 5,
|
||||
"created_at": 1717000000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_NOT_FOUND_001` | 404 | User not found |
|
||||
|
||||
---
|
||||
|
||||
### GET `/api/v1/admin/users/stats`
|
||||
|
||||
Get user statistics.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"total": 1200,
|
||||
"active": 1150,
|
||||
"inactive": 50,
|
||||
"admins": 3,
|
||||
"doctors": 92,
|
||||
"patients": 1100
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### PUT `/api/v1/admin/users/{uuid}`
|
||||
|
||||
Update user info (name, email, password).
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"real_name": "علی احمدی جدید",
|
||||
"password": "newPassword123"
|
||||
}
|
||||
```
|
||||
|
||||
### Response `200`
|
||||
Updated user object.
|
||||
|
||||
---
|
||||
|
||||
### PUT `/api/v1/admin/users/{uuid}/role`
|
||||
|
||||
Change a user's role.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"role": "ROLE_DOCTOR"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Allowed Values |
|
||||
|-------|------|----------|----------------|
|
||||
| `role` | string | ✅ | `ROLE_USER`, `ROLE_DOCTOR`, `ROLE_CLINIC`, `ROLE_SECRETARY`, `ROLE_ADMIN` |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{ "success": true, "data": { "message": "نقش کاربر تغییر کرد", "roles": ["ROLE_DOCTOR"] } }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### POST `/api/v1/admin/users/{uuid}/status`
|
||||
|
||||
Toggle user active/inactive status.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{ "success": true, "data": { "status": "inactive" } }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### DELETE `/api/v1/admin/users/{uuid}`
|
||||
|
||||
Delete a user.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{ "success": true, "data": { "message": "کاربر حذف شد" } }
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_NOT_FOUND_001` | 404 | User not found |
|
||||
|
||||
---
|
||||
|
||||
## Doctor Management
|
||||
|
||||
### GET `/api/v1/admin/doctors`
|
||||
|
||||
List all doctors with pagination.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `page` | integer | ❌ | Default: 1 |
|
||||
| `limit` | integer | ❌ | Default: 20 |
|
||||
| `search` | string | ❌ | Search in title |
|
||||
| `status` | string | ❌ | `"active"` or `"inactive"` |
|
||||
| `gender` | string | ❌ | `"male"` or `"female"` |
|
||||
| `specialty_id` | integer | ❌ | Filter by specialty |
|
||||
| `sort` | string | ❌ | Sort field |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"title": "دکتر علی احمدی",
|
||||
"degree": "متخصص",
|
||||
"gender": "male",
|
||||
"doctor_rate": 4.5,
|
||||
"active_doctor_appointment": true
|
||||
}
|
||||
],
|
||||
"meta": { "totalRecords": 92, "totalPages": 5, "currentPage": 1 }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET `/api/v1/admin/doctors/stats`
|
||||
|
||||
Get doctor statistics.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"total": 92,
|
||||
"active": 85,
|
||||
"inactive": 7,
|
||||
"male": 60,
|
||||
"female": 32,
|
||||
"top_specialty": "قلب و عروق"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### POST `/api/v1/admin/doctors/{uuid}/status`
|
||||
|
||||
Toggle doctor active status.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{ "success": true, "data": { "active": false } }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Clinic Management
|
||||
|
||||
### GET `/api/v1/admin/clinics`
|
||||
|
||||
List all clinics with pagination.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `page` | integer | ❌ | Default: 1 |
|
||||
| `limit` | integer | ❌ | Default: 20 |
|
||||
| `search` | string | ❌ | Search by clinic name |
|
||||
| `status` | string | ❌ | `"active"` or `"inactive"` |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"name": "کلینیک الوند",
|
||||
"city": "تهران",
|
||||
"telephone": "02112345678",
|
||||
"is_active": true,
|
||||
"created_at": 1717000000
|
||||
}
|
||||
],
|
||||
"meta": { "totalRecords": 34, "totalPages": 2, "currentPage": 1 }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### PATCH `/api/v1/admin/clinic/{uuid}/status`
|
||||
|
||||
Toggle clinic active/inactive.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{ "success": true, "data": { "is_active": false } }
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_NOT_FOUND_001` | 404 | Clinic not found |
|
||||
|
||||
---
|
||||
|
||||
### DELETE `/api/v1/admin/clinic/{uuid}`
|
||||
|
||||
Delete a clinic.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{ "success": true, "data": { "message": "کلینیک حذف شد" } }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Appointment Management
|
||||
|
||||
### GET `/api/v1/admin/appointments`
|
||||
|
||||
List all appointments.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `page` | integer | ❌ | Default: 1 |
|
||||
| `limit` | integer | ❌ | Default: 20 |
|
||||
| `search` | string | ❌ | Search by doctor/patient name |
|
||||
| `status` | string | ❌ | Filter by status |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"doctor_title": "دکتر علی احمدی",
|
||||
"patient_name": "محمد رضایی",
|
||||
"slot_start": 1718438400,
|
||||
"status": "confirmed",
|
||||
"price": 500000
|
||||
}
|
||||
],
|
||||
"meta": { "totalRecords": 8540, "totalPages": 427, "currentPage": 1 }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Payment Management
|
||||
|
||||
### GET `/api/v1/admin/payments`
|
||||
|
||||
List all payments.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `page` | integer | ❌ | Default: 1 |
|
||||
| `limit` | integer | ❌ | Default: 20 |
|
||||
| `status` | string | ❌ | `"pending"`, `"paid"`, `"failed"`, `"cancelled"` |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"order_id": "CLINICPRO-...",
|
||||
"amount_rials": 500000,
|
||||
"status": "paid",
|
||||
"gateway": "mellat",
|
||||
"created_at": 1717000000
|
||||
}
|
||||
],
|
||||
"meta": { "totalRecords": 7800, "totalPages": 390, "currentPage": 1 }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Settlement Management
|
||||
|
||||
### GET `/api/v1/admin/settlements`
|
||||
|
||||
List all settlement requests.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `page` | integer | ❌ | Default: 1 |
|
||||
| `limit` | integer | ❌ | Default: 20 |
|
||||
| `status` | string | ❌ | `"pending"`, `"approved"`, `"rejected"` |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"user": { "uuid": "...", "real_name": "دکتر علی احمدی" },
|
||||
"amount_rials": 1000000,
|
||||
"status": "pending",
|
||||
"bank_account": { "bank_name": "بانک ملت", "owner_name": "..." },
|
||||
"created_at": 1717000000
|
||||
}
|
||||
],
|
||||
"meta": { "totalRecords": 45, "totalPages": 3, "currentPage": 1 }
|
||||
}
|
||||
```
|
||||
|
||||
> To approve or reject, use the Settlement API: `POST /api/v1/settlement/{uuid}/approve` or `/reject`
|
||||
|
||||
---
|
||||
|
||||
## Representation Management
|
||||
|
||||
### GET `/api/v1/admin/representations`
|
||||
|
||||
List all representations.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `page` | integer | ❌ | Default: 1 |
|
||||
| `limit` | integer | ❌ | Default: 20 |
|
||||
| `search` | string | ❌ | Search by name |
|
||||
| `city_id` | integer | ❌ | Filter by city |
|
||||
|
||||
### Response `200`
|
||||
Paginated representation list.
|
||||
|
||||
---
|
||||
|
||||
## Secretary Management
|
||||
|
||||
### GET `/api/v1/admin/secretaries`
|
||||
|
||||
List all secretaries.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `page` | integer | ❌ | Default: 1 |
|
||||
| `limit` | integer | ❌ | Default: 20 |
|
||||
| `search` | string | ❌ | Search by mobile |
|
||||
|
||||
### Response `200`
|
||||
Paginated secretary list with linked doctor info.
|
||||
|
||||
---
|
||||
|
||||
## Rating & Comment Management
|
||||
|
||||
### GET `/api/v1/admin/rates`
|
||||
|
||||
List all ratings.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `page` | integer | ❌ | Default: 1 |
|
||||
| `limit` | integer | ❌ | Default: 20 |
|
||||
| `search` | string | ❌ | Search by doctor/patient |
|
||||
|
||||
---
|
||||
|
||||
### GET `/api/v1/admin/comments`
|
||||
|
||||
List all comments (all statuses).
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `page` | integer | ❌ | Default: 1 |
|
||||
| `limit` | integer | ❌ | Default: 20 |
|
||||
| `search` | string | ❌ | Search in body |
|
||||
| `status` | string | ❌ | `"pending"`, `"approved"`, `"rejected"` |
|
||||
|
||||
> To approve/reject comments, use the Rating API: `POST /api/v1/admin/comment/{uuid}/approve` or `/reject`
|
||||
|
||||
---
|
||||
|
||||
## SMS Management (Admin)
|
||||
|
||||
### GET `/api/v1/admin/sms/logs`
|
||||
|
||||
List SMS send logs.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `page` | integer | ❌ | Default: 1 |
|
||||
| `limit` | integer | ❌ | Default: 20 |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"mobile": "09123456789",
|
||||
"message": "کد تأیید: 123456",
|
||||
"provider": "kavenegar",
|
||||
"success": true,
|
||||
"created_at": 1717000000
|
||||
}
|
||||
],
|
||||
"meta": { "totalRecords": 5000, "totalPages": 250, "currentPage": 1 }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET `/api/v1/admin/sms/templates`
|
||||
|
||||
List all SMS templates.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"name": "تأیید نوبت",
|
||||
"status": "approved",
|
||||
"provider_code": "verify_appointment",
|
||||
"created_at": 1717000000
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> To create/approve/reject templates, see [sms.md](sms.md)
|
||||
|
||||
---
|
||||
|
||||
## Clinic Invitation Management
|
||||
|
||||
> See [clinic-invitation.md](clinic-invitation.md) for full endpoint details.
|
||||
|
||||
| Endpoint | Description |
|
||||
|----------|-------------|
|
||||
| `POST /api/v1/admin/clinic/{uuid}/invite-doctor` | Send invitation |
|
||||
| `GET /api/v1/admin/clinic/{uuid}/invitations` | List invitations |
|
||||
| `POST /api/v1/admin/clinic/invitation/{invUuid}/resend` | Resend SMS |
|
||||
| `PATCH /api/v1/admin/clinic/invitation/{invUuid}/status` | Change status |
|
||||
| `DELETE /api/v1/admin/clinic/invitation/{invUuid}` | Delete |
|
||||
@@ -0,0 +1,540 @@
|
||||
# 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`)
|
||||
```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 |
|
||||
|
||||
**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`
|
||||
```json
|
||||
{
|
||||
"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": [] }
|
||||
},
|
||||
"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
|
||||
```json
|
||||
{
|
||||
"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`
|
||||
```json
|
||||
{ "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`
|
||||
```json
|
||||
{
|
||||
"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
|
||||
```json
|
||||
{
|
||||
"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`:**
|
||||
```json
|
||||
{
|
||||
"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):**
|
||||
```json
|
||||
{
|
||||
"custom_slots": [
|
||||
{ "start": "14:00", "end": "18:00", "duration": 20 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Response `201`
|
||||
```json
|
||||
{
|
||||
"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)
|
||||
```json
|
||||
{
|
||||
"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`
|
||||
```json
|
||||
{ "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`
|
||||
```json
|
||||
{
|
||||
"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
|
||||
```json
|
||||
{
|
||||
"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`
|
||||
```json
|
||||
{
|
||||
"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)
|
||||
```json
|
||||
{
|
||||
"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`
|
||||
```json
|
||||
{ "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:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"start": 1718438400,
|
||||
"end": 1718439600,
|
||||
"start_time": "09:00",
|
||||
"end_time": "09:20",
|
||||
"location_id": 1973
|
||||
}
|
||||
]
|
||||
```
|
||||
@@ -0,0 +1,277 @@
|
||||
# Appointment API
|
||||
|
||||
> **Prefix:** `/api/v1/appointment*`
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/appointment-slots`
|
||||
|
||||
Get available appointment slots 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",
|
||||
"slots": [
|
||||
{
|
||||
"start": 1718438400,
|
||||
"end": 1718439600,
|
||||
"available": true
|
||||
},
|
||||
{
|
||||
"start": 1718439600,
|
||||
"end": 1718440800,
|
||||
"available": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> All times are **Unix timestamps**. Slots are calculated from `WeeklySchedule` minus booked appointments, date overrides, and holidays.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |
|
||||
| `ERR_VALIDATION_001` | 422 | Missing or invalid date/doctor_uuid |
|
||||
|
||||
---
|
||||
|
||||
## 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 |
|
||||
@@ -0,0 +1,284 @@
|
||||
# Authentication API
|
||||
|
||||
> **Prefix:** `/api/v1/user` and `/oauth`
|
||||
> **Permission:** All endpoints in this module are **PUBLIC** (no JWT required) except `userinfo` and `logout`
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/user/send-code`
|
||||
|
||||
Send OTP code to mobile number.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Request Body
|
||||
```json
|
||||
{
|
||||
"mobile": "09123456789"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Validation |
|
||||
|-------|------|----------|------------|
|
||||
| `mobile` | string | ✅ | Format: `09XXXXXXXXX` (11 digits) |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"message": "کد تأیید ارسال شد"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_VALIDATION_001` | 422 | Invalid mobile format |
|
||||
| `ERR_AUTH_004` | 429 | OTP rate limit exceeded |
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/user/verify-code`
|
||||
|
||||
Verify OTP code. Returns whether this is a new or existing user.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Request Body
|
||||
```json
|
||||
{
|
||||
"uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"code": "123456"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `uuid` | string | ✅ | UUID returned from `send-code` |
|
||||
| `code` | string | ✅ | 6-digit OTP |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"message": "کد تأیید شد",
|
||||
"is_new_user": false,
|
||||
"uuid": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_002` | 401 | Invalid OTP code |
|
||||
| `ERR_AUTH_003` | 401 | OTP expired |
|
||||
| `ERR_VALIDATION_002` | 422 | Missing required field |
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/user/register`
|
||||
|
||||
Complete registration for new users (called only when `is_new_user: true`).
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Request Body
|
||||
```json
|
||||
{
|
||||
"uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"real_name": "علی احمدی"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `uuid` | string | ✅ | Verified UUID from `verify-code` |
|
||||
| `real_name` | string | ❌ | User's full name |
|
||||
|
||||
### Response `201`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"message": "ثبتنام با موفقیت انجام شد",
|
||||
"uuid": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_VALIDATION_002` | 422 | Missing uuid |
|
||||
| `ERR_CONFLICT_001` | 409 | User already registered |
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/user/login`
|
||||
|
||||
Login with mobile number and password (for users who set a password).
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Request Body
|
||||
```json
|
||||
{
|
||||
"mobile_number": "09123456789",
|
||||
"password": "mypassword"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required |
|
||||
|-------|------|----------|
|
||||
| `mobile_number` | string | ✅ |
|
||||
| `password` | string | ✅ |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
|
||||
"refresh_token": "def50200..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_005` | 401 | Wrong credentials |
|
||||
| `ERR_AUTH_006` | 403 | Account suspended |
|
||||
| `ERR_AUTH_004` | 429 | Too many attempts |
|
||||
|
||||
---
|
||||
|
||||
## POST `/oauth/token`
|
||||
|
||||
Exchange verified UUID for JWT access token.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Request Body
|
||||
```json
|
||||
{
|
||||
"grant_type": "mobile",
|
||||
"uuid": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `grant_type` | string | ✅ | Must be `"mobile"` |
|
||||
| `uuid` | string | ✅ | UUID from verified OTP flow |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
|
||||
"refresh_token": "def50200..."
|
||||
}
|
||||
```
|
||||
|
||||
> JWT payload: `{ username: mobile_number, roles: [...], iat, exp }`
|
||||
> Access token TTL: **1 hour** | Refresh token TTL: **30 days** (stored in Redis)
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_002` | 400 | Invalid or expired UUID |
|
||||
|
||||
---
|
||||
|
||||
## POST `/oauth/token/refresh`
|
||||
|
||||
Refresh expired JWT using refresh token.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Request Body
|
||||
```json
|
||||
{
|
||||
"refresh_token": "def50200..."
|
||||
}
|
||||
```
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
|
||||
"refresh_token": "def50200..."
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Invalid or expired refresh token |
|
||||
|
||||
---
|
||||
|
||||
## GET `/oauth/userinfo`
|
||||
|
||||
Get authenticated user info.
|
||||
|
||||
**Permission:** `AUTH` — requires valid JWT
|
||||
|
||||
### Headers
|
||||
```
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"mobile_number": "09123456789",
|
||||
"real_name": "علی احمدی",
|
||||
"roles": ["ROLE_USER"],
|
||||
"status": "active",
|
||||
"created_at": 1717000000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Invalid or missing token |
|
||||
|
||||
---
|
||||
|
||||
## POST `/oauth/logout`
|
||||
|
||||
Invalidate the refresh token (stored in Redis).
|
||||
|
||||
**Permission:** `AUTH`
|
||||
|
||||
### Request Body
|
||||
```json
|
||||
{
|
||||
"refresh_token": "def50200..."
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required |
|
||||
|-------|------|----------|
|
||||
| `refresh_token` | string | ❌ |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"message": "با موفقیت خارج شدید"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,232 @@
|
||||
# Blog API
|
||||
|
||||
> **Prefix:** `/api/v1/blog`, `/api/v1/blogs`
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/blogs`
|
||||
|
||||
List published blog posts.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `page` | integer | ❌ | 1 | Page number |
|
||||
| `limit` | integer | ❌ | 20 | Items per page |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"title": "آشنایی با بیماری دیابت",
|
||||
"slug": "ashnayi-ba-bimari-diabat",
|
||||
"summary": "خلاصه مطلب...",
|
||||
"image": "https://...",
|
||||
"author": { "uuid": "...", "real_name": "دکتر احمدی" },
|
||||
"tags": [{ "id": 1, "name": "دیابت" }],
|
||||
"status": "published",
|
||||
"created_at": 1717000000
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"totalRecords": 25,
|
||||
"totalPages": 2,
|
||||
"currentPage": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/blog/{slug}`
|
||||
|
||||
Get a single blog post by slug.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `slug` | string | URL slug (e.g. `ashnayi-ba-bimari-diabat`) |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"uuid": "...",
|
||||
"title": "آشنایی با بیماری دیابت",
|
||||
"slug": "ashnayi-ba-bimari-diabat",
|
||||
"summary": "خلاصه...",
|
||||
"body": "<p>محتوای کامل...</p>",
|
||||
"image": "https://...",
|
||||
"author": { "uuid": "...", "real_name": "دکتر احمدی" },
|
||||
"tags": [{ "id": 1, "name": "دیابت" }],
|
||||
"status": "published",
|
||||
"created_at": 1717000000,
|
||||
"updated_at": 1717100000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_NOT_FOUND_001` | 404 | Blog not found or not published |
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/blog`
|
||||
|
||||
Create a new blog post.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"title": "آشنایی با بیماری دیابت",
|
||||
"body": "<p>محتوای کامل مقاله...</p>",
|
||||
"summary": "خلاصه کوتاه از مقاله",
|
||||
"tags": [1, 2],
|
||||
"status": "draft"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `title` | string | ✅ | Post title (slug auto-generated) |
|
||||
| `body` | string | ✅ | Full HTML body |
|
||||
| `summary` | string | ❌ | Short excerpt |
|
||||
| `tags` | integer[] | ❌ | Array of tag IDs |
|
||||
| `status` | string | ❌ | `"draft"` (default) or `"published"` |
|
||||
|
||||
### Response `201`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"uuid": "blog-uuid-...",
|
||||
"title": "آشنایی با بیماری دیابت",
|
||||
"slug": "ashnayi-ba-bimari-diabat",
|
||||
"summary": "...",
|
||||
"body": "...",
|
||||
"image": null,
|
||||
"tags": [],
|
||||
"status": "draft",
|
||||
"created_at": 1717000000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Blog Status Values:**
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `draft` | Not visible to public |
|
||||
| `published` | Visible in public listing |
|
||||
| `archived` | Hidden from listing |
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_VALIDATION_002` | 422 | Missing required fields |
|
||||
|
||||
---
|
||||
|
||||
## PATCH `/api/v1/blog/{uuid}`
|
||||
|
||||
Update a blog post.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `uuid` | string (UUID) | Blog UUID |
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"title": "عنوان جدید",
|
||||
"body": "<p>محتوای جدید</p>",
|
||||
"summary": "خلاصه جدید",
|
||||
"tags": [1, 3],
|
||||
"status": "published"
|
||||
}
|
||||
```
|
||||
|
||||
All fields optional.
|
||||
|
||||
### Response `200`
|
||||
Updated blog object.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Blog not found |
|
||||
|
||||
---
|
||||
|
||||
## DELETE `/api/v1/blog/{uuid}`
|
||||
|
||||
Delete a blog post.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{ "success": true, "data": { "message": "مقاله حذف شد" } }
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Blog not found |
|
||||
|
||||
---
|
||||
|
||||
## POST `/file/upload/clinic_pro/blog/field_image`
|
||||
|
||||
Upload blog post header image.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Request
|
||||
`Content-Type: multipart/form-data`
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `file` | binary | ✅ | Image (max 5MB) |
|
||||
| `blog_uuid` | string (UUID) | ❌ | Auto-associate with blog post |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"url": "https://clinic-pro.ddev.site/uploads/blog/post_abc.jpg",
|
||||
"uuid": "...",
|
||||
"filename": "post_abc.jpg",
|
||||
"filemime": "image/jpeg",
|
||||
"filesize": 204800
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_FILE_001` | 422 | Invalid file type |
|
||||
@@ -0,0 +1,312 @@
|
||||
# Clinic Doctor Invitation API
|
||||
|
||||
> **Prefix:** `/api/v1/admin/clinic/...` (admin) and `/api/v1/clinic-invitation/...` (public)
|
||||
|
||||
Admins invite doctors to clinics via SMS. The doctor receives a secure 96-char token link valid for 72 hours.
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/admin/clinic/{uuid}/invite-doctor`
|
||||
|
||||
Send an invitation to a doctor (by mobile number) to join a clinic.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `uuid` | string (UUID) | Clinic UUID |
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"mobile": "09123456789",
|
||||
"name": "دکتر علی احمدی",
|
||||
"specialty": "قلب و عروق"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Validation |
|
||||
|-------|------|----------|------------|
|
||||
| `mobile` | string | ✅ | Format: `09XXXXXXXXX` |
|
||||
| `name` | string | ❌ | Doctor's display name |
|
||||
| `specialty` | string | ❌ | Specialty label for SMS |
|
||||
|
||||
### Response `201`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"uuid": "inv-uuid-...",
|
||||
"mobile": "09123456789",
|
||||
"invited_name": "دکتر علی احمدی",
|
||||
"invited_specialty": "قلب و عروق",
|
||||
"status": "pending",
|
||||
"invited_at": 1717000000,
|
||||
"expires_at": 1717259200,
|
||||
"token_used": false,
|
||||
"clinic": { "uuid": "...", "name": "کلینیک الوند" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> SMS is dispatched **asynchronously** via Symfony Messenger → Redis queue.
|
||||
> SMS text: `"دکتر گرامی، کلینیک {name} شما را برای همکاری دعوت کرده است.\nبرای بررسی: {link}\nاین لینک تا ۷۲ ساعت معتبر است."`
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing or invalid token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Clinic not found |
|
||||
| `ERR_VALIDATION_001` | 422 | Invalid mobile format |
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/admin/clinic/{uuid}/invitations`
|
||||
|
||||
List all invitations for a clinic.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `uuid` | string (UUID) | Clinic UUID |
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Default |
|
||||
|-------|------|----------|---------|
|
||||
| `page` | integer | ❌ | 1 |
|
||||
| `limit` | integer | ❌ | 50 |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"mobile": "09123456789",
|
||||
"invited_name": "دکتر علی احمدی",
|
||||
"invited_specialty": "قلب و عروق",
|
||||
"status": "pending",
|
||||
"invited_at": 1717000000,
|
||||
"expires_at": 1717259200,
|
||||
"responded_at": null,
|
||||
"token_used": false,
|
||||
"doctor": null
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"totalRecords": 5,
|
||||
"totalPages": 1,
|
||||
"currentPage": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Invitation Status Values:**
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `pending` | Sent, awaiting response |
|
||||
| `accepted` | Doctor accepted |
|
||||
| `rejected` | Doctor rejected |
|
||||
| `suspended` | Suspended by admin |
|
||||
| `removed` | Removed |
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Clinic not found |
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/admin/clinic/invitation/{invUuid}/resend`
|
||||
|
||||
Resend the invitation SMS with a fresh token and reset expiry to +72 hours.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `invUuid` | string (UUID) | Invitation UUID |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": { "message": "دعوتنامه مجدداً ارسال شد" }
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Invitation not found |
|
||||
|
||||
---
|
||||
|
||||
## PATCH `/api/v1/admin/clinic/invitation/{invUuid}/status`
|
||||
|
||||
Change the status of an invitation (e.g., suspend or remove).
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `invUuid` | string (UUID) | Invitation UUID |
|
||||
|
||||
### Request Body
|
||||
```json
|
||||
{
|
||||
"status": "suspended"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Allowed Values |
|
||||
|-------|------|----------|----------------|
|
||||
| `status` | string | ✅ | `pending`, `suspended`, `removed` |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": { "status": "suspended" }
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Invitation not found |
|
||||
| `ERR_VALIDATION_001` | 422 | Invalid status value |
|
||||
|
||||
---
|
||||
|
||||
## DELETE `/api/v1/admin/clinic/invitation/{invUuid}`
|
||||
|
||||
Delete an invitation.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `invUuid` | string (UUID) | Invitation UUID |
|
||||
|
||||
### Response `204`
|
||||
Empty body.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Invitation not found |
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/clinic-invitation/{token}`
|
||||
|
||||
View invitation details by token (used on the doctor-facing landing page).
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `token` | string | 96-char hex token from SMS link |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"invitation": {
|
||||
"uuid": "...",
|
||||
"mobile": "09123456789",
|
||||
"invited_name": "دکتر علی احمدی",
|
||||
"invited_specialty": "قلب و عروق",
|
||||
"status": "pending",
|
||||
"expires_at": 1717259200
|
||||
},
|
||||
"clinic": {
|
||||
"uuid": "...",
|
||||
"name": "کلینیک الوند",
|
||||
"city": "تهران",
|
||||
"clinic_logo": "https://..."
|
||||
},
|
||||
"is_usable": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> `is_usable: false` when: token already used, expired, or status is not `pending`
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_NOT_FOUND_001` | 404 | Token not found |
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/clinic-invitation/{token}/accept`
|
||||
|
||||
Doctor accepts the invitation. If a doctor profile exists for this mobile, they are automatically linked to the clinic.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `token` | string | 96-char hex token |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": { "message": "دعوتنامه پذیرفته شد" }
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_NOT_FOUND_001` | 404 | Token not found |
|
||||
| `ERR_VALIDATION_001` | 422 | Token expired or already used |
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/clinic-invitation/{token}/reject`
|
||||
|
||||
Doctor rejects the invitation.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `token` | string | 96-char hex token |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": { "message": "دعوتنامه رد شد" }
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_NOT_FOUND_001` | 404 | Token not found |
|
||||
| `ERR_VALIDATION_001` | 422 | Token expired or already used |
|
||||
@@ -0,0 +1,310 @@
|
||||
# Clinic API
|
||||
|
||||
> **Prefix:** `/api/v1/clinic`, `/api/v1/clinics`
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/clinic`
|
||||
|
||||
Create a new clinic.
|
||||
|
||||
**Permission:** `AUTH` — any authenticated user becomes the clinic owner
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"name": "کلینیک الوند",
|
||||
"info": "توضیحات کلینیک",
|
||||
"address": "تهران، خیابان ولیعصر",
|
||||
"telephone": "02112345678",
|
||||
"working_days": "شنبه تا چهارشنبه",
|
||||
"is_24_7": false,
|
||||
"latitude": 35.6892,
|
||||
"longitude": 51.3890,
|
||||
"state": "تهران",
|
||||
"city": "تهران",
|
||||
"image_clinic": [
|
||||
{ "url": "https://..." }
|
||||
],
|
||||
"clinic_logo": "https://...",
|
||||
"doctors": ["uuid1", "uuid2"],
|
||||
"specialties": [1, 2],
|
||||
"doctor_services": [3, 4],
|
||||
"insurance": [5, 6]
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `name` | string | ✅ | Clinic name |
|
||||
| `info` | string | ❌ | Description |
|
||||
| `address` | string | ❌ | Full address |
|
||||
| `telephone` | string | ❌ | Contact number |
|
||||
| `working_days` | string | ❌ | Working days description |
|
||||
| `is_24_7` | boolean | ❌ | Open 24/7 flag |
|
||||
| `latitude` | float | ❌ | Latitude for map |
|
||||
| `longitude` | float | ❌ | Longitude for map |
|
||||
| `state` | string | ❌ | Province name |
|
||||
| `city` | string | ❌ | City name |
|
||||
| `image_clinic` | object[] | ❌ | Gallery images `[{url: "..."}]` |
|
||||
| `clinic_logo` | string | ❌ | Logo URL |
|
||||
| `doctors` | string[] | ❌ | Doctor UUIDs to associate |
|
||||
| `specialties` | integer[] | ❌ | Specialty IDs |
|
||||
| `doctor_services` | integer[] | ❌ | Service IDs |
|
||||
| `insurance` | integer[] | ❌ | Insurance IDs |
|
||||
|
||||
### Response `201`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"data": {
|
||||
"uuid": "550e8400-...",
|
||||
"name": "کلینیک الوند",
|
||||
"info": "...",
|
||||
"address": "...",
|
||||
"telephone": "02112345678",
|
||||
"working_days": "...",
|
||||
"is_24_7": false,
|
||||
"latitude": 35.6892,
|
||||
"longitude": 51.3890,
|
||||
"state": "تهران",
|
||||
"city": "تهران",
|
||||
"images_clinic": [{ "url": "https://..." }],
|
||||
"clinic_logo": "https://...",
|
||||
"is_active": true,
|
||||
"doctors": [],
|
||||
"specialties": [],
|
||||
"doctor_services": [],
|
||||
"insurance": [],
|
||||
"tags": [],
|
||||
"created_at": 1717000000
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> ⚠️ **Double-nested:** Frontend extracts with `data?.data?.data`
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing or invalid token |
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/clinic/{uuid}`
|
||||
|
||||
Get clinic detail.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `uuid` | string (UUID) | Clinic UUID |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"data": {
|
||||
"uuid": "550e8400-...",
|
||||
"name": "کلینیک الوند",
|
||||
"info": "...",
|
||||
"address": "تهران، خیابان ولیعصر",
|
||||
"telephone": "02112345678",
|
||||
"working_days": "شنبه تا چهارشنبه",
|
||||
"is_24_7": false,
|
||||
"latitude": 35.6892,
|
||||
"longitude": 51.3890,
|
||||
"state": "تهران",
|
||||
"city": "تهران",
|
||||
"images_clinic": [{ "url": "https://..." }],
|
||||
"clinic_logo": "https://...",
|
||||
"is_active": true,
|
||||
"doctors": [{ "uuid": "...", "title": "دکتر..." }],
|
||||
"specialties": [{ "id": 1, "name": "قلب" }],
|
||||
"doctor_services": [],
|
||||
"insurance": [],
|
||||
"tags": [],
|
||||
"owner": { "uuid": "...", "real_name": "..." }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_NOT_FOUND_001` | 404 | Clinic not found |
|
||||
|
||||
---
|
||||
|
||||
## PATCH `/api/v1/clinic/{uuid}`
|
||||
|
||||
Update a clinic.
|
||||
|
||||
**Permission:** `AUTH` — must be the clinic owner or `ROLE_ADMIN`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `uuid` | string (UUID) | Clinic UUID |
|
||||
|
||||
### Request Body
|
||||
Same fields as POST — all optional.
|
||||
|
||||
### Response `200`
|
||||
Updated clinic object (same structure as GET).
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_FORBIDDEN_001` | 403 | Not the owner |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Clinic not found |
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/clinics`
|
||||
|
||||
List clinics with pagination.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `page` | integer | ❌ | Default: 1 |
|
||||
| `limit` | integer | ❌ | Default: 20 |
|
||||
| `name` | string | ❌ | Search by clinic name |
|
||||
| `city` | string | ❌ | Filter by city |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"name": "کلینیک الوند",
|
||||
"city": "تهران",
|
||||
"clinic_logo": "https://...",
|
||||
"is_active": true
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"totalRecords": 30,
|
||||
"totalPages": 2,
|
||||
"currentPage": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/clinic/doctor-list/{clinicUuid}`
|
||||
|
||||
Get doctors associated with a clinic.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `clinicUuid` | string (UUID) | Clinic UUID |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"title": "دکتر علی احمدی",
|
||||
"degree": "متخصص",
|
||||
"doctor_rate": 4.5,
|
||||
"image": "https://..."
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_NOT_FOUND_001` | 404 | Clinic not found |
|
||||
|
||||
---
|
||||
|
||||
## POST `/file/upload/clinic_pro/clinic/field_clinic_logo`
|
||||
|
||||
Upload clinic logo.
|
||||
|
||||
**Permission:** `AUTH`
|
||||
|
||||
### Request
|
||||
`Content-Type: multipart/form-data`
|
||||
|
||||
| Field | Type | Required | Max Size |
|
||||
|-------|------|----------|----------|
|
||||
| `file` | binary | ✅ | 5MB |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"url": "https://clinic-pro.ddev.site/uploads/clinic/logo_abc.jpg",
|
||||
"uuid": "...",
|
||||
"filename": "logo_abc.jpg",
|
||||
"filemime": "image/jpeg",
|
||||
"filesize": 102400
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_FILE_001` | 422 | Invalid file type |
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
|
||||
---
|
||||
|
||||
## POST `/file/upload/clinic_pro/clinic/field_image_clinic`
|
||||
|
||||
Upload clinic gallery image.
|
||||
|
||||
**Permission:** `AUTH`
|
||||
|
||||
### Request
|
||||
`Content-Type: multipart/form-data`
|
||||
|
||||
| Field | Type | Required | Max Size |
|
||||
|-------|------|----------|----------|
|
||||
| `file` | binary | ✅ | 5MB |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"url": "https://clinic-pro.ddev.site/uploads/clinic/gallery_abc.jpg",
|
||||
"uuid": "...",
|
||||
"filename": "gallery_abc.jpg",
|
||||
"filemime": "image/jpeg",
|
||||
"filesize": 307200
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> After uploading, use the returned `url` inside `image_clinic: [{ "url": "..." }]` when calling PATCH clinic.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_FILE_001` | 422 | Invalid file type |
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
@@ -0,0 +1,146 @@
|
||||
# Doctor Service API
|
||||
|
||||
> **Prefix:** `/api/v1/doctor-services`, `/api/v1/admin/doctor-service`
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/doctor-services`
|
||||
|
||||
List all active doctor services.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `specialty_id` | integer | ❌ | Filter by parent specialty |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "نوار قلب (ECG)",
|
||||
"slug": "navar-ghalb",
|
||||
"specialty_id": 1,
|
||||
"status": "active",
|
||||
"weight": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/admin/doctor-services`
|
||||
|
||||
List all services with pagination (admin view).
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `page` | integer | ❌ | Default: 1 |
|
||||
| `limit` | integer | ❌ | Default: 20 |
|
||||
| `search` | string | ❌ | Search in name |
|
||||
| `specialty_id` | integer | ❌ | Filter by specialty |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [ ... ],
|
||||
"meta": { "totalRecords": 30, "totalPages": 2, "currentPage": 1 }
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/admin/doctor-service`
|
||||
|
||||
Create a new doctor service.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"name": "نوار قلب (ECG)",
|
||||
"slug": "navar-ghalb",
|
||||
"specialty_id": 1,
|
||||
"status": "active",
|
||||
"weight": 5
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `name` | string | ✅ | Service name |
|
||||
| `slug` | string | ❌ | Auto-generated from name if omitted |
|
||||
| `specialty_id` | integer | ❌ | Link to specialty |
|
||||
| `status` | string | ❌ | `"active"` or `"inactive"` |
|
||||
| `weight` | integer | ❌ | Sort weight |
|
||||
|
||||
### Response `201`
|
||||
Service object.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_VALIDATION_002` | 422 | Missing name |
|
||||
|
||||
---
|
||||
|
||||
## PATCH `/api/v1/admin/doctor-service/{id}`
|
||||
|
||||
Update a doctor service.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `id` | integer | Service ID |
|
||||
|
||||
All body fields optional.
|
||||
|
||||
### Response `200`
|
||||
Updated service object.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Service not found |
|
||||
|
||||
---
|
||||
|
||||
## DELETE `/api/v1/admin/doctor-service/{id}`
|
||||
|
||||
Delete a doctor service.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{ "success": true, "data": { "message": "خدمت پزشکی حذف شد" } }
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Service not found |
|
||||
@@ -0,0 +1,380 @@
|
||||
# Doctor API
|
||||
|
||||
> **Prefix:** `/api/v1/doctor`, `/api/v1/doctors`, `/api/v1/clinic-pro/doctor-address*`
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/doctor`
|
||||
|
||||
Create a doctor profile for the authenticated user.
|
||||
|
||||
**Permission:** `AUTH` — any authenticated user
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"title": "دکتر علی احمدی",
|
||||
"gender": "male",
|
||||
"medical_system_code": "12345",
|
||||
"degree": "متخصص",
|
||||
"info": "توضیحات درباره پزشک",
|
||||
"specialties": [1, 2],
|
||||
"doctor_services": [3, 4]
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `title` | string | ✅ | Full name with title |
|
||||
| `gender` | string | ❌ | `"male"` or `"female"` |
|
||||
| `medical_system_code` | string | ❌ | Nظام پزشکی code |
|
||||
| `degree` | string | ❌ | Academic degree |
|
||||
| `info` | string | ❌ | Bio/description |
|
||||
| `specialties` | integer[] | ❌ | Array of specialty IDs |
|
||||
| `doctor_services` | integer[] | ❌ | Array of doctor service IDs |
|
||||
|
||||
### Response `201`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"title": "دکتر علی احمدی",
|
||||
"gender": "male",
|
||||
"medical_system_code": "12345",
|
||||
"degree": "متخصص",
|
||||
"info": "...",
|
||||
"doctor_rate": null,
|
||||
"active_doctor_appointment": false,
|
||||
"specialties": [],
|
||||
"doctor_services": [],
|
||||
"created_at": 1717000000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing or invalid token |
|
||||
| `ERR_CONFLICT_001` | 409 | Doctor profile already exists for this user |
|
||||
| `ERR_VALIDATION_002` | 422 | Missing required field |
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/doctor/{uuid}`
|
||||
|
||||
Get doctor detail with clinics.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `uuid` | string (UUID) | Doctor UUID |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"data": {
|
||||
"uuid": "550e8400-...",
|
||||
"title": "دکتر علی احمدی",
|
||||
"gender": "male",
|
||||
"medical_system_code": "12345",
|
||||
"degree": "متخصص",
|
||||
"info": "...",
|
||||
"image": "https://...",
|
||||
"doctor_rate": 4.5,
|
||||
"active_doctor_appointment": true,
|
||||
"specialties": [{ "id": 1, "name": "قلب و عروق" }],
|
||||
"doctor_services": [{ "id": 3, "name": "نوار قلب" }],
|
||||
"clinics": [{ "uuid": "...", "name": "کلینیک الوند" }],
|
||||
"created_at": 1717000000
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> ⚠️ **Double-nested:** Frontend extracts with `data?.data?.data`
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/doctors`
|
||||
|
||||
List doctors with pagination and filters.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `page` | integer | ❌ | Default: 1 |
|
||||
| `limit` | integer | ❌ | Default: 20 |
|
||||
| `search` | string | ❌ | Search in title |
|
||||
| `specialty_id` | integer | ❌ | Filter by specialty |
|
||||
| `city_id` | integer | ❌ | Filter by city |
|
||||
| `state_id` | integer | ❌ | Filter by province |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"title": "دکتر علی احمدی",
|
||||
"degree": "متخصص",
|
||||
"doctor_rate": 4.5,
|
||||
"image": "https://..."
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"totalRecords": 50,
|
||||
"totalPages": 3,
|
||||
"currentPage": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PATCH `/api/v1/doctor/{uuid}`
|
||||
|
||||
Update doctor profile.
|
||||
|
||||
**Permission:** `AUTH` — must be the owner (or `ROLE_ADMIN`)
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `uuid` | string (UUID) | Doctor UUID |
|
||||
|
||||
### Request Body (`application/json`)
|
||||
Same fields as POST — all optional.
|
||||
|
||||
### Response `200`
|
||||
Updated doctor object (same structure as GET single).
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_FORBIDDEN_001` | 403 | Not the owner |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |
|
||||
|
||||
---
|
||||
|
||||
## DELETE `/api/v1/doctor/{uuid}`
|
||||
|
||||
Delete a doctor profile.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `uuid` | string (UUID) | Doctor UUID |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": { "message": "پزشک با موفقیت حذف شد" }
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |
|
||||
|
||||
---
|
||||
|
||||
## POST `/file/upload/clinic_pro/doctor/field_image`
|
||||
|
||||
Upload doctor profile image.
|
||||
|
||||
**Permission:** `AUTH`
|
||||
|
||||
### Request
|
||||
`Content-Type: multipart/form-data`
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `file` | binary | ✅ | Image file (max 5MB) |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"url": "https://clinic-pro.ddev.site/uploads/doctor/abc123.jpg",
|
||||
"uuid": "...",
|
||||
"filename": "abc123.jpg",
|
||||
"filemime": "image/jpeg",
|
||||
"filesize": 204800
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_FILE_001` | 422 | Invalid file type |
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/clinic-pro/doctor-addresses/{doctorId}`
|
||||
|
||||
Get all practice addresses for a doctor.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `doctorId` | integer | Doctor's numeric ID |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "مطب تهران",
|
||||
"address": "تهران، خیابان...",
|
||||
"telephone": "02112345678",
|
||||
"latitude": 35.6892,
|
||||
"longitude": 51.3890
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/clinic-pro/doctor-address`
|
||||
|
||||
Add a new practice address.
|
||||
|
||||
**Permission:** `AUTH` — must own the doctor profile
|
||||
|
||||
### Request Body
|
||||
```json
|
||||
{
|
||||
"name": "مطب تهران",
|
||||
"address": "تهران، خیابان ولیعصر",
|
||||
"telephone": "02112345678",
|
||||
"latitude": 35.6892,
|
||||
"longitude": 51.3890
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required |
|
||||
|-------|------|----------|
|
||||
| `name` | string | ❌ |
|
||||
| `address` | string | ❌ |
|
||||
| `telephone` | string | ❌ |
|
||||
| `latitude` | float | ❌ |
|
||||
| `longitude` | float | ❌ |
|
||||
|
||||
### Response `201`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": 1,
|
||||
"name": "مطب تهران",
|
||||
"address": "تهران، خیابان ولیعصر",
|
||||
"telephone": "02112345678",
|
||||
"latitude": 35.6892,
|
||||
"longitude": 51.3890
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_FORBIDDEN_001` | 403 | Not the doctor owner |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |
|
||||
|
||||
---
|
||||
|
||||
## PATCH `/api/v1/clinic-pro/doctor-address/{id}`
|
||||
|
||||
Update a practice address.
|
||||
|
||||
**Permission:** `AUTH` — must own the doctor profile
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `id` | integer | Address ID |
|
||||
|
||||
### Request Body
|
||||
Same fields as POST — all optional.
|
||||
|
||||
### Response `200`
|
||||
Updated address object.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_FORBIDDEN_001` | 403 | Not the owner |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Address not found |
|
||||
|
||||
---
|
||||
|
||||
## DELETE `/api/v1/clinic-pro/doctor-address/{id}`
|
||||
|
||||
Delete a practice address.
|
||||
|
||||
**Permission:** `AUTH` — must own the doctor profile
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{ "success": true, "data": { "message": "آدرس حذف شد" } }
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_FORBIDDEN_001` | 403 | Not the owner |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Address not found |
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/clinic-pro/doctor-address/from-clinic/{clinicUuid}`
|
||||
|
||||
Create a doctor address automatically from a clinic's location.
|
||||
|
||||
**Permission:** `AUTH` — must own the doctor profile and be associated with the clinic
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `clinicUuid` | string (UUID) | Clinic UUID |
|
||||
|
||||
### Response `201`
|
||||
Address object created from clinic data.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_FORBIDDEN_001` | 403 | Not associated with this clinic |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Clinic not found |
|
||||
@@ -0,0 +1,267 @@
|
||||
# Insurance API
|
||||
|
||||
> **Prefix:** `/api/v1/insurances`, `/api/v1/insurance`, `/api/v1/admin/insurance`
|
||||
|
||||
Two resource types:
|
||||
1. **Insurance** — master list of insurance companies managed by admin
|
||||
2. **DoctorInsurance** — a doctor's acceptance of a specific insurance (with optional price)
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/insurances`
|
||||
|
||||
List all active insurances.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `type` | string | ❌ | `"basic"` or `"supplementary"` |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "بیمه تأمین اجتماعی",
|
||||
"type": "basic",
|
||||
"logo_url": "https://...",
|
||||
"status": "active"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "بیمه ایران",
|
||||
"type": "supplementary",
|
||||
"logo_url": "https://...",
|
||||
"status": "active"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/admin/insurances`
|
||||
|
||||
List all insurances with pagination (admin view — includes inactive).
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `page` | integer | ❌ | Default: 1 |
|
||||
| `limit` | integer | ❌ | Default: 20 |
|
||||
| `search` | string | ❌ | Search in name |
|
||||
| `type` | string | ❌ | `"basic"` or `"supplementary"` |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [ ... ],
|
||||
"meta": { "totalRecords": 15, "totalPages": 1, "currentPage": 1 }
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/admin/insurance`
|
||||
|
||||
Create a new insurance.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"name": "بیمه تأمین اجتماعی",
|
||||
"type": "basic",
|
||||
"logo_url": "https://...",
|
||||
"status": "active"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `name` | string | ✅ | Insurance name |
|
||||
| `type` | string | ✅ | `"basic"` or `"supplementary"` |
|
||||
| `logo_url` | string | ❌ | Logo image URL |
|
||||
| `status` | string | ❌ | `"active"` (default) or `"inactive"` |
|
||||
|
||||
### Response `201`
|
||||
Insurance object.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_VALIDATION_002` | 422 | Missing required field |
|
||||
|
||||
---
|
||||
|
||||
## PATCH `/api/v1/admin/insurance/{id}`
|
||||
|
||||
Update an insurance.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `id` | integer | Insurance ID |
|
||||
|
||||
All body fields optional.
|
||||
|
||||
### Response `200`
|
||||
Updated insurance object.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Insurance not found |
|
||||
|
||||
---
|
||||
|
||||
## DELETE `/api/v1/admin/insurance/{id}`
|
||||
|
||||
Delete an insurance.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{ "success": true, "data": { "message": "بیمه حذف شد" } }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/admin/insurance/{id}/upload-logo`
|
||||
|
||||
Upload insurance logo.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Request
|
||||
`Content-Type: multipart/form-data`
|
||||
|
||||
| Field | Type | Required |
|
||||
|-------|------|----------|
|
||||
| `file` | binary | ✅ |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"url": "https://...",
|
||||
"uuid": "...",
|
||||
"filename": "insurance_logo.png",
|
||||
"filemime": "image/png",
|
||||
"filesize": 51200
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/insurance/`
|
||||
|
||||
Add an insurance to a doctor's accepted list.
|
||||
|
||||
**Permission:** `AUTH` — must be the doctor (or their secretary with `insurances.create` permission)
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"doctor_id": 42,
|
||||
"insurance_id": 1,
|
||||
"price": 150000
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `doctor_id` | integer | ✅ | Doctor's numeric ID |
|
||||
| `insurance_id` | integer | ✅ | Insurance ID |
|
||||
| `price` | integer | ❌ | Visit price for this insurance (Rials) |
|
||||
|
||||
### Response `201`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": 10,
|
||||
"doctor_id": 42,
|
||||
"insurance": { "id": 1, "name": "بیمه تأمین اجتماعی", "type": "basic" },
|
||||
"price": 150000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_FORBIDDEN_001` | 403 | Not the doctor |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Doctor or insurance not found |
|
||||
| `ERR_CONFLICT_001` | 409 | Insurance already added to doctor |
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/insurance/{id}`
|
||||
|
||||
Get a doctor-insurance link.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Response `200`
|
||||
DoctorInsurance object.
|
||||
|
||||
---
|
||||
|
||||
## PATCH `/api/v1/insurance/{id}`
|
||||
|
||||
Update a doctor-insurance (e.g., change price).
|
||||
|
||||
**Permission:** `AUTH` — must be the doctor (or their secretary with `insurances.update` permission)
|
||||
|
||||
### Request Body
|
||||
```json
|
||||
{ "price": 200000 }
|
||||
```
|
||||
|
||||
### Response `200`
|
||||
Updated DoctorInsurance object.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_FORBIDDEN_001` | 403 | Not the doctor |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Link not found |
|
||||
|
||||
---
|
||||
|
||||
## DELETE `/api/v1/insurance/{id}`
|
||||
|
||||
Remove an insurance from a doctor's list.
|
||||
|
||||
**Permission:** `AUTH` — must be the doctor (or their secretary with `insurances.delete` permission)
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{ "success": true, "data": { "message": "بیمه از لیست حذف شد" } }
|
||||
```
|
||||
@@ -0,0 +1,249 @@
|
||||
# Location API (Province & City)
|
||||
|
||||
> **Prefix:** `/api/v1/provinces`, `/api/v1/cities`, `/api/v1/admin/province`, `/api/v1/admin/city`
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/provinces`
|
||||
|
||||
List all active provinces.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{ "id": 1, "name": "تهران", "status": "active", "weight": 1 },
|
||||
{ "id": 2, "name": "اصفهان", "status": "active", "weight": 2 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/cities`
|
||||
|
||||
List cities, optionally filtered by province.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `province_id` | integer | ❌ | Filter cities by province |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{ "id": 10, "name": "تهران", "province_id": 1, "status": "active", "weight": 1 },
|
||||
{ "id": 11, "name": "کرج", "province_id": 1, "status": "active", "weight": 2 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/admin/provinces`
|
||||
|
||||
List all provinces with pagination (admin view — includes inactive).
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `page` | integer | ❌ | Default: 1 |
|
||||
| `limit` | integer | ❌ | Default: 20 |
|
||||
| `search` | string | ❌ | Search in name |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [ ... ],
|
||||
"meta": { "totalRecords": 31, "totalPages": 2, "currentPage": 1 }
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/admin/cities`
|
||||
|
||||
List all cities with pagination (admin view).
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `page` | integer | ❌ | Default: 1 |
|
||||
| `limit` | integer | ❌ | Default: 20 |
|
||||
| `search` | string | ❌ | Search in name |
|
||||
| `province_id` | integer | ❌ | Filter by province |
|
||||
|
||||
### Response `200`
|
||||
Paginated city list.
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/admin/province`
|
||||
|
||||
Create a new province.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"name": "تهران",
|
||||
"status": "active",
|
||||
"weight": 1
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `name` | string | ✅ | Province name |
|
||||
| `status` | string | ❌ | `"active"` (default) or `"inactive"` |
|
||||
| `weight` | integer | ❌ | Sort weight |
|
||||
|
||||
### Response `201`
|
||||
Province object.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_VALIDATION_002` | 422 | Missing name |
|
||||
|
||||
---
|
||||
|
||||
## PATCH `/api/v1/admin/province/{id}`
|
||||
|
||||
Update a province.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `id` | integer | Province ID |
|
||||
|
||||
All fields optional.
|
||||
|
||||
### Response `200`
|
||||
Updated province object.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Province not found |
|
||||
|
||||
---
|
||||
|
||||
## DELETE `/api/v1/admin/province/{id}`
|
||||
|
||||
Delete a province.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{ "success": true, "data": { "message": "استان حذف شد" } }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/admin/city`
|
||||
|
||||
Create a new city.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"name": "تهران",
|
||||
"province_id": 1,
|
||||
"status": "active",
|
||||
"weight": 1
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `name` | string | ✅ | City name |
|
||||
| `province_id` | integer | ❌ | Parent province ID |
|
||||
| `status` | string | ❌ | `"active"` or `"inactive"` |
|
||||
| `weight` | integer | ❌ | Sort weight |
|
||||
|
||||
### Response `201`
|
||||
City object.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_VALIDATION_002` | 422 | Missing name |
|
||||
|
||||
---
|
||||
|
||||
## PATCH `/api/v1/admin/city/{id}`
|
||||
|
||||
Update a city.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
All body fields optional.
|
||||
|
||||
### Response `200`
|
||||
Updated city object.
|
||||
|
||||
---
|
||||
|
||||
## DELETE `/api/v1/admin/city/{id}`
|
||||
|
||||
Delete a city.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{ "success": true, "data": { "message": "شهر حذف شد" } }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/categorys/{bundle}` *(Legacy)*
|
||||
|
||||
Legacy endpoint that proxies to the new endpoints.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Path Parameters
|
||||
| Bundle | Maps to |
|
||||
|--------|---------|
|
||||
| `state` | `/api/v1/provinces` |
|
||||
| `city` | `/api/v1/cities` |
|
||||
| `specially_doctor` | `/api/v1/specialties` |
|
||||
| `doctor_services` | `/api/v1/doctor-services` |
|
||||
| `insurance_type` | `/api/v1/insurances?type=basic` |
|
||||
| `supplementary_insurance` | `/api/v1/insurances?type=supplementary` |
|
||||
| `tag` | `/api/v1/tags` |
|
||||
|
||||
> ⚠️ Response is **triple-nested**: `data?.data?.data ?? []`
|
||||
> Note: `categorys` (not `categories`) is intentional — legacy route name.
|
||||
@@ -0,0 +1,178 @@
|
||||
# Payment API
|
||||
|
||||
> **Prefix:** `/api/v1/payment`, `/api/v1/subscription-payment`
|
||||
> **Supported Gateways:** `mellat` (Mellat Bank SOAP) | `sep` (SEP REST)
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/payment/appointment`
|
||||
|
||||
Initiate payment for an appointment. Returns a redirect URL to the payment gateway.
|
||||
|
||||
**Permission:** `AUTH` — must be the appointment owner (patient)
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"appointment_uuid": "appt-uuid-...",
|
||||
"gateway": "mellat",
|
||||
"frontend_address": "https://myapp.com/payment/result"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `appointment_uuid` | string (UUID) | ✅ | Appointment to pay for |
|
||||
| `gateway` | string | ✅ | `"mellat"` or `"sep"` |
|
||||
| `frontend_address` | string | ❌ | Redirect URL after payment (overrides default) |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"payment_uuid": "pay-uuid-...",
|
||||
"redirect_url": "https://bpm.shaparak.ir/pgwchannel/...",
|
||||
"order_id": "CLINICPRO-1717000000-ABC123"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_FORBIDDEN_001` | 403 | Not the patient |
|
||||
| `ERR_PAYMENT_003` | 422 | Appointment not in payable state |
|
||||
| `ERR_PAYMENT_002` | 422 | Invalid amount |
|
||||
| `ERR_PAYMENT_001` | 503 | Payment gateway unavailable |
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/payment/callback/{gateway}`
|
||||
## GET `/api/v1/payment/callback/{gateway}`
|
||||
|
||||
Payment gateway callback. Called by the bank after user completes (or cancels) payment.
|
||||
|
||||
**Permission:** `PUBLIC` — called by the gateway, not the user
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `gateway` | string | `mellat` or `sep` |
|
||||
|
||||
### Request (varies by gateway)
|
||||
**Mellat POST fields:**
|
||||
```
|
||||
ResCode=0&SaleOrderId=...&SaleReferenceId=...&RefId=...
|
||||
```
|
||||
|
||||
**SEP POST fields:**
|
||||
```
|
||||
Status=2&RRN=...&RefNum=...&TerminalId=...&TraceNo=...
|
||||
```
|
||||
|
||||
### Response
|
||||
- If `ResCode=0` (success): appointment confirmed, redirect to `frontend_address?success=1&uuid=...`
|
||||
- If failed: redirect to `frontend_address?success=0&error=...`
|
||||
|
||||
### Notes
|
||||
- On success: appointment status → `confirmed`, wallet credited with doctor's share
|
||||
- Payment record stored with: `order_id`, `amount_rials`, `status`, `gateway`, `ref_id`
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/subscription-payment`
|
||||
|
||||
Initiate a subscription / wallet top-up payment (not tied to a specific appointment).
|
||||
|
||||
**Permission:** `AUTH`
|
||||
|
||||
### Request Body
|
||||
```json
|
||||
{
|
||||
"gateway": "mellat",
|
||||
"amount_rials": 1000000,
|
||||
"frontend_address": "https://myapp.com/wallet/result"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `gateway` | string | ✅ | `"mellat"` or `"sep"` |
|
||||
| `amount_rials` | integer | ✅ | Amount in Rials (min: 10,000) |
|
||||
| `frontend_address` | string | ❌ | Redirect URL after payment |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"payment_uuid": "pay-uuid-...",
|
||||
"redirect_url": "https://bpm.shaparak.ir/pgwchannel/...",
|
||||
"order_id": "CLINICPRO-SUB-1717000000-XYZ"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_PAYMENT_002` | 422 | Invalid amount |
|
||||
| `ERR_PAYMENT_001` | 503 | Gateway unavailable |
|
||||
|
||||
---
|
||||
|
||||
## POST/GET `/api/v1/subscription-payment/callback/{gateway}`
|
||||
|
||||
Callback for subscription payments. Same behavior as appointment callback but credits wallet instead.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/payment/{uuid}`
|
||||
|
||||
Get payment status and details.
|
||||
|
||||
**Permission:** `AUTH` — must be the payment owner or `ROLE_ADMIN`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `uuid` | string (UUID) | Payment UUID |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"uuid": "pay-uuid-...",
|
||||
"order_id": "CLINICPRO-1717000000-ABC123",
|
||||
"amount_rials": 500000,
|
||||
"status": "paid",
|
||||
"gateway": "mellat",
|
||||
"ref_id": "123456789",
|
||||
"appointment_uuid": "appt-uuid-...",
|
||||
"created_at": 1717000000,
|
||||
"paid_at": 1717000120
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Payment Status Values:**
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `pending` | Created, not paid yet |
|
||||
| `paid` | Successfully paid |
|
||||
| `failed` | Gateway returned failure |
|
||||
| `cancelled` | User cancelled at gateway |
|
||||
| `refunded` | Refunded |
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_FORBIDDEN_001` | 403 | Not the owner |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Payment not found |
|
||||
@@ -0,0 +1,275 @@
|
||||
# Rating & Comments API
|
||||
|
||||
> **Prefix:** `/api/v1/rate`, `/api/v1/comment`, `/api/v1/like`
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/rate`
|
||||
|
||||
Submit a rating for a doctor.
|
||||
|
||||
**Permission:** `AUTH` — any authenticated user (typically after a completed appointment)
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"doctor_uuid": "550e8400-...",
|
||||
"score": 5
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Validation |
|
||||
|-------|------|----------|------------|
|
||||
| `doctor_uuid` | string (UUID) | ✅ | Must exist |
|
||||
| `score` | integer | ✅ | 1–5 |
|
||||
|
||||
### Response `201`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"uuid": "rate-uuid-...",
|
||||
"doctor_uuid": "...",
|
||||
"score": 5,
|
||||
"created_at": 1717000000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |
|
||||
| `ERR_VALIDATION_001` | 422 | Score out of range |
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/rate/{doctorUuid}`
|
||||
|
||||
Get average rating for a doctor.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `doctorUuid` | string (UUID) | Doctor UUID |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"average": 4.3,
|
||||
"total": 47
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/comment`
|
||||
|
||||
Submit a comment/review for a doctor.
|
||||
|
||||
**Permission:** `AUTH`
|
||||
|
||||
> Comments require admin approval before appearing publicly.
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"doctor_uuid": "550e8400-...",
|
||||
"body": "پزشک بسیار مؤدب و متخصص بودند"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Validation |
|
||||
|-------|------|----------|------------|
|
||||
| `doctor_uuid` | string (UUID) | ✅ | Must exist |
|
||||
| `body` | string | ✅ | Min 10 chars |
|
||||
|
||||
### Response `201`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"uuid": "comment-uuid-...",
|
||||
"body": "پزشک بسیار مؤدب و متخصص بودند",
|
||||
"status": "pending",
|
||||
"created_at": 1717000000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Comment Status Values:**
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `pending` | Awaiting admin review |
|
||||
| `approved` | Visible to public |
|
||||
| `rejected` | Not visible |
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |
|
||||
| `ERR_VALIDATION_001` | 422 | Body too short |
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/comments/{doctorUuid}`
|
||||
|
||||
Get approved comments for a doctor.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `doctorUuid` | string (UUID) | Doctor UUID |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"body": "پزشک بسیار مؤدب...",
|
||||
"user": { "uuid": "...", "real_name": "علی" },
|
||||
"likes": 3,
|
||||
"status": "approved",
|
||||
"created_at": 1717000000
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |
|
||||
|
||||
---
|
||||
|
||||
## DELETE `/api/v1/comment/{uuid}`
|
||||
|
||||
Delete a comment.
|
||||
|
||||
**Permission:** `AUTH` — must be the comment author or `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{ "success": true, "data": { "message": "نظر حذف شد" } }
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_FORBIDDEN_001` | 403 | Not the author |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Comment not found |
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/admin/comments/pending`
|
||||
|
||||
Get all pending comments waiting for review.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"body": "...",
|
||||
"doctor": { "uuid": "...", "title": "دکتر علی احمدی" },
|
||||
"user": { "uuid": "...", "real_name": "..." },
|
||||
"status": "pending",
|
||||
"created_at": 1717000000
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/admin/comment/{uuid}/approve`
|
||||
|
||||
Approve a pending comment (makes it public).
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
Updated comment object with `status: "approved"`.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Comment not found |
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/admin/comment/{uuid}/reject`
|
||||
|
||||
Reject a pending comment.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
Updated comment object with `status: "rejected"`.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Comment not found |
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/like/{commentUuid}`
|
||||
|
||||
Toggle like on a comment (like if not liked, unlike if already liked).
|
||||
|
||||
**Permission:** `AUTH`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `commentUuid` | string (UUID) | Comment UUID |
|
||||
|
||||
### Response `200` (unlike) or `201` (new like)
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"liked": true,
|
||||
"likes": 4
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Comment not found |
|
||||
@@ -0,0 +1,227 @@
|
||||
# Representation (Agent) API
|
||||
|
||||
> **Prefix:** `/api/v1/representation`
|
||||
|
||||
Representations are sales agents who earn commission on appointments booked through their referral.
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/representation`
|
||||
|
||||
Create a new representation.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"full_name": "علی احمدی",
|
||||
"mobile_number": "09123456789",
|
||||
"city_id": 42,
|
||||
"commission_percent": 10,
|
||||
"bank_account": {
|
||||
"iban": "IR...",
|
||||
"account_number": "1234567890",
|
||||
"bank_name": "بانک ملت",
|
||||
"owner_name": "علی احمدی"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `full_name` | string | ✅ | Agent full name |
|
||||
| `mobile_number` | string | ✅ | Login mobile (creates a User account) |
|
||||
| `city_id` | integer | ❌ | City ID (FK to categories where bundle=city) |
|
||||
| `commission_percent` | float | ❌ | Commission rate (0–100) |
|
||||
| `bank_account` | object | ❌ | Bank details for settlements |
|
||||
|
||||
### Response `201`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"uuid": "rep-uuid-...",
|
||||
"full_name": "علی احمدی",
|
||||
"mobile_number": "09123456789",
|
||||
"city_id": 42,
|
||||
"commission_percent": 10,
|
||||
"active": true,
|
||||
"bank_account": { ... },
|
||||
"created_at": 1717000000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_CONFLICT_001` | 409 | Mobile number already in use |
|
||||
| `ERR_VALIDATION_001` | 422 | Invalid input |
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/representation/{uuid}`
|
||||
|
||||
Get representation detail.
|
||||
|
||||
**Permission:** `AUTH` — must be the representation's user or `ROLE_ADMIN`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `uuid` | string (UUID) | Representation UUID |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"uuid": "...",
|
||||
"full_name": "علی احمدی",
|
||||
"mobile_number": "09123456789",
|
||||
"city_id": 42,
|
||||
"city_name": "تهران",
|
||||
"commission_percent": 10,
|
||||
"active": true,
|
||||
"bank_account": { ... },
|
||||
"created_at": 1717000000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_FORBIDDEN_001` | 403 | Not owner or admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Representation not found |
|
||||
|
||||
---
|
||||
|
||||
## PATCH `/api/v1/representation/{uuid}`
|
||||
|
||||
Update representation.
|
||||
|
||||
**Permission:** `AUTH` — must be the representation's user or `ROLE_ADMIN`
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"full_name": "علی احمدی جدید",
|
||||
"city_id": 50,
|
||||
"commission_percent": 12,
|
||||
"bank_account": { ... },
|
||||
"active": true
|
||||
}
|
||||
```
|
||||
|
||||
All fields optional.
|
||||
|
||||
### Response `200`
|
||||
Updated representation object.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_FORBIDDEN_001` | 403 | Not owner or admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Representation not found |
|
||||
|
||||
---
|
||||
|
||||
## DELETE `/api/v1/representation/{uuid}`
|
||||
|
||||
Delete a representation.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{ "success": true, "data": { "message": "نماینده حذف شد" } }
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Representation not found |
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/representation/{uuid}/dashboard/monthly`
|
||||
|
||||
Get monthly earnings dashboard for a representation.
|
||||
|
||||
**Permission:** `AUTH` — must be the representation's user or `ROLE_ADMIN`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `uuid` | string (UUID) | Representation UUID |
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `year` | integer | ✅ | e.g. `2024` |
|
||||
| `month` | integer | ✅ | 1–12 |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"period": { "year": 2024, "month": 6 },
|
||||
"stats": {
|
||||
"total_appointments": 15,
|
||||
"total_revenue_rials": 7500000,
|
||||
"commission_rials": 750000,
|
||||
"daily": [
|
||||
{ "date": "2024-06-01", "appointments": 2, "revenue": 1000000, "commission": 100000 }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_FORBIDDEN_001` | 403 | Not owner or admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Representation not found |
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/representation/{uuid}/dashboard/yearly`
|
||||
|
||||
Get yearly earnings dashboard for a representation.
|
||||
|
||||
**Permission:** `AUTH` — must be the representation's user or `ROLE_ADMIN`
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `year` | integer | ✅ | e.g. `2024` |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"period": { "year": 2024 },
|
||||
"months": [
|
||||
{ "month": 1, "appointments": 10, "revenue_rials": 5000000, "commission_rials": 500000 },
|
||||
{ "month": 2, "appointments": 8, "revenue_rials": 4000000, "commission_rials": 400000 }
|
||||
],
|
||||
"totals": {
|
||||
"appointments": 97,
|
||||
"revenue_rials": 48500000,
|
||||
"commission_rials": 4850000
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,220 @@
|
||||
# Secretary API
|
||||
|
||||
> **Prefix:** `/api/v1/secretary`, `/api/v1/secretaries`
|
||||
|
||||
Secretaries are linked to a doctor and have granular permissions controlling what they can do on behalf of the doctor.
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/secretary`
|
||||
|
||||
Create a secretary for a doctor.
|
||||
|
||||
**Permission:** `ROLE_DOCTOR` — must own the doctor profile
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"doctor_uuid": "550e8400-...",
|
||||
"mobile_number": "09123456789",
|
||||
"password": "secretaryPass123",
|
||||
"permissions": {
|
||||
"version": 1,
|
||||
"resources": {
|
||||
"appointments": { "view": true, "create": true, "cancel": false, "update_status": true },
|
||||
"addresses": { "view": true, "create": false, "update": false, "delete": false },
|
||||
"clinic_info": { "view": true, "update": false },
|
||||
"insurances": { "view": true, "create": false, "update": false, "delete": false }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `doctor_uuid` | string (UUID) | ✅ | Doctor to assign secretary to |
|
||||
| `mobile_number` | string | ✅ | Secretary's login mobile |
|
||||
| `password` | string | ❌ | Initial password (auto-generated if omitted) |
|
||||
| `permissions` | object | ❌ | Permission set (see structure below) |
|
||||
|
||||
**Permissions Structure:**
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"resources": {
|
||||
"appointments": {
|
||||
"view": true, // Can view appointments list
|
||||
"create": true, // Can book appointments
|
||||
"cancel": false, // Can cancel appointments
|
||||
"update_status": true // Can mark as completed/no_show
|
||||
},
|
||||
"addresses": {
|
||||
"view": true,
|
||||
"create": false,
|
||||
"update": false,
|
||||
"delete": false
|
||||
},
|
||||
"clinic_info": {
|
||||
"view": true,
|
||||
"update": false
|
||||
},
|
||||
"insurances": {
|
||||
"view": true,
|
||||
"create": false,
|
||||
"update": false,
|
||||
"delete": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response `201`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"uuid": "sec-uuid-...",
|
||||
"mobile_number": "09123456789",
|
||||
"active": true,
|
||||
"permissions": { ... },
|
||||
"doctor": { "uuid": "...", "title": "دکتر علی احمدی" },
|
||||
"created_at": 1717000000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not a doctor or not the doctor's owner |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |
|
||||
| `ERR_CONFLICT_001` | 409 | Mobile number already in use |
|
||||
| `ERR_SECRETARY_001` | 422 | Plan limit for secretaries reached |
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/secretary/{uuid}`
|
||||
|
||||
Get secretary detail.
|
||||
|
||||
**Permission:** `AUTH` — must be the linked doctor or `ROLE_ADMIN`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `uuid` | string (UUID) | Secretary UUID |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"uuid": "...",
|
||||
"mobile_number": "09123456789",
|
||||
"active": true,
|
||||
"permissions": { ... },
|
||||
"doctor": { "uuid": "...", "title": "دکتر علی احمدی" },
|
||||
"created_at": 1717000000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_FORBIDDEN_001` | 403 | Not authorized |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Secretary not found |
|
||||
|
||||
---
|
||||
|
||||
## PATCH `/api/v1/secretary/{uuid}`
|
||||
|
||||
Update secretary active status or permissions.
|
||||
|
||||
**Permission:** `ROLE_DOCTOR` — must be the linked doctor
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"active": false,
|
||||
"permissions": {
|
||||
"version": 1,
|
||||
"resources": {
|
||||
"appointments": { "view": true, "create": false, "cancel": false, "update_status": false }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `active` | boolean | ❌ | Enable/disable secretary |
|
||||
| `permissions` | object | ❌ | New permissions object |
|
||||
|
||||
### Response `200`
|
||||
Updated secretary object.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_FORBIDDEN_001` | 403 | Not the linked doctor |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Secretary not found |
|
||||
|
||||
---
|
||||
|
||||
## DELETE `/api/v1/secretary/{uuid}`
|
||||
|
||||
Delete a secretary.
|
||||
|
||||
**Permission:** `ROLE_DOCTOR` — must be the linked doctor
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{ "success": true, "data": { "message": "منشی حذف شد" } }
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_FORBIDDEN_001` | 403 | Not the linked doctor |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Secretary not found |
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/secretaries/{doctorUuid}`
|
||||
|
||||
Get all secretaries for a specific doctor.
|
||||
|
||||
**Permission:** `AUTH` — must be the doctor or `ROLE_ADMIN`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `doctorUuid` | string (UUID) | Doctor UUID |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"mobile_number": "09...",
|
||||
"active": true,
|
||||
"permissions": { ... },
|
||||
"created_at": 1717000000
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_FORBIDDEN_001` | 403 | Not the doctor |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |
|
||||
@@ -0,0 +1,254 @@
|
||||
# Settlement & Wallet API
|
||||
|
||||
> **Prefix:** `/api/v1/wallet`, `/api/v1/settlement`
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/wallet/balance`
|
||||
|
||||
Get authenticated user's wallet balance.
|
||||
|
||||
**Permission:** `AUTH`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"balance_rials": 2500000,
|
||||
"recent_transactions": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"type": "credit",
|
||||
"amount_rials": 500000,
|
||||
"balance_after": 2500000,
|
||||
"description": "دریافت از نوبت شماره ...",
|
||||
"created_at": 1717000000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/wallet/transactions`
|
||||
|
||||
Get wallet transaction history for the authenticated user.
|
||||
|
||||
**Permission:** `AUTH`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"type": "credit",
|
||||
"amount_rials": 500000,
|
||||
"balance_after": 2500000,
|
||||
"description": "دریافت از نوبت",
|
||||
"created_at": 1717000000
|
||||
},
|
||||
{
|
||||
"uuid": "...",
|
||||
"type": "debit",
|
||||
"amount_rials": 200000,
|
||||
"balance_after": 2300000,
|
||||
"description": "تسویهحساب",
|
||||
"created_at": 1716900000
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Transaction Type Values:**
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `credit` | Money added to wallet |
|
||||
| `debit` | Money removed from wallet |
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/settlement`
|
||||
|
||||
Request a settlement (withdrawal from wallet to bank account).
|
||||
|
||||
**Permission:** `AUTH`
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"amount_rials": 1000000,
|
||||
"bank_account": {
|
||||
"iban": "IR...",
|
||||
"account_number": "1234567890",
|
||||
"bank_name": "بانک ملت",
|
||||
"owner_name": "علی احمدی"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `amount_rials` | integer | ✅ | Amount to withdraw (must be ≤ wallet balance) |
|
||||
| `bank_account` | object | ❌ | Bank account details (saved if not previously set) |
|
||||
| `bank_account.iban` | string | ❌ | IBAN (شبا) |
|
||||
| `bank_account.account_number` | string | ❌ | Account number |
|
||||
| `bank_account.bank_name` | string | ❌ | Bank name |
|
||||
| `bank_account.owner_name` | string | ❌ | Account owner name |
|
||||
|
||||
### Response `201`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"uuid": "settle-uuid-...",
|
||||
"amount_rials": 1000000,
|
||||
"status": "pending",
|
||||
"bank_account": {
|
||||
"iban": "IR...",
|
||||
"bank_name": "بانک ملت",
|
||||
"owner_name": "علی احمدی"
|
||||
},
|
||||
"created_at": 1717000000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_VALIDATION_001` | 422 | Amount exceeds balance or invalid amount |
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/settlement`
|
||||
|
||||
Get authenticated user's settlement requests.
|
||||
|
||||
**Permission:** `AUTH`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"amount_rials": 1000000,
|
||||
"status": "pending",
|
||||
"note": null,
|
||||
"created_at": 1717000000,
|
||||
"processed_at": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Settlement Status Values:**
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `pending` | Awaiting admin review |
|
||||
| `approved` | Approved, payment sent |
|
||||
| `rejected` | Rejected by admin |
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/settlement/{uuid}`
|
||||
|
||||
Get a single settlement.
|
||||
|
||||
**Permission:** `AUTH` — must be the owner or `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
Settlement object with full details including `bank_account`.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_FORBIDDEN_001` | 403 | Not the owner |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Settlement not found |
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/settlement/{uuid}/approve`
|
||||
|
||||
Approve a settlement request. Marks it as paid and debits the wallet.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `uuid` | string (UUID) | Settlement UUID |
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"note": "پرداخت شد — شناسه پیگیری: 123456"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required |
|
||||
|-------|------|----------|
|
||||
| `note` | string | ❌ |
|
||||
|
||||
### Response `200`
|
||||
Updated settlement object with `status: "approved"`.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Settlement not found |
|
||||
| `ERR_VALIDATION_001` | 422 | Already processed |
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/settlement/{uuid}/reject`
|
||||
|
||||
Reject a settlement request. Returns the amount back to wallet.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Request Body
|
||||
```json
|
||||
{
|
||||
"note": "حساب بانکی نادرست است"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required |
|
||||
|-------|------|----------|
|
||||
| `note` | string | ✅ | Rejection reason (required) |
|
||||
|
||||
### Response `200`
|
||||
Updated settlement object with `status: "rejected"`.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Settlement not found |
|
||||
| `ERR_VALIDATION_002` | 422 | Missing note |
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
# SMS API
|
||||
|
||||
> **Prefix:** `/api/v1/sms`
|
||||
> **Providers:** `kavenegar` (default) | `rangineh`
|
||||
> All send operations are dispatched **asynchronously** via Symfony Messenger → Redis queue.
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/sms/send`
|
||||
|
||||
Send a direct SMS message (free text).
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"mobile": "09123456789",
|
||||
"message": "سلام، پیام آزمایشی",
|
||||
"provider": "kavenegar"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `mobile` | string | ✅ | Recipient mobile (`09XXXXXXXXX`) |
|
||||
| `message` | string | ✅ | Message text |
|
||||
| `provider` | string | ❌ | `"kavenegar"` or `"rangineh"` (default from env) |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": { "message": "پیامک با موفقیت ارسال شد" }
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_VALIDATION_001` | 422 | Invalid mobile format |
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/sms/send-template`
|
||||
|
||||
Send an SMS using an approved template.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"mobile": "09123456789",
|
||||
"template_uuid": "tmpl-uuid-...",
|
||||
"vars": {
|
||||
"name": "دکتر علی احمدی",
|
||||
"date": "۱۵ خرداد ۱۴۰۴"
|
||||
},
|
||||
"provider": "kavenegar"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `mobile` | string | ✅ | Recipient mobile |
|
||||
| `template_uuid` | string (UUID) | ✅ | UUID of an approved template |
|
||||
| `vars` | object | ❌ | Key-value substitutions for template placeholders |
|
||||
| `provider` | string | ❌ | Override provider |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": { "message": "پیامک با موفقیت ارسال شد" }
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Template not found |
|
||||
| `ERR_VALIDATION_001` | 422 | Template not approved |
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/sms/template`
|
||||
|
||||
Create a new SMS template.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"name": "تأیید نوبت",
|
||||
"body": "دکتر گرامی ${name}، نوبت شما در تاریخ ${date} تأیید شد.",
|
||||
"variables": ["name", "date"]
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `name` | string | ✅ | Template display name |
|
||||
| `body` | string | ✅ | Template text with `${variable}` placeholders |
|
||||
| `variables` | string[] | ❌ | List of expected variable names |
|
||||
|
||||
### Response `201`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"uuid": "tmpl-uuid-...",
|
||||
"name": "تأیید نوبت",
|
||||
"body": "دکتر گرامی ${name}...",
|
||||
"variables": ["name", "date"],
|
||||
"status": "draft",
|
||||
"created_at": 1717000000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Template Status Values:**
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `draft` | Created, not submitted |
|
||||
| `pending` | Submitted for review |
|
||||
| `approved` | Ready to use |
|
||||
| `rejected` | Rejected |
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_VALIDATION_002` | 422 | Missing required field |
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/sms/template/{uuid}`
|
||||
|
||||
Get template detail.
|
||||
|
||||
**Permission:** `AUTH`
|
||||
|
||||
### Response `200`
|
||||
Template object.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Template not found |
|
||||
|
||||
---
|
||||
|
||||
## PATCH `/api/v1/sms/template/{uuid}`
|
||||
|
||||
Update a template (only allowed in `draft` or `rejected` status).
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"name": "تأیید نوبت - ویرایش",
|
||||
"body": "نوبت شما در ${date} تأیید شد.",
|
||||
"variables": ["date"]
|
||||
}
|
||||
```
|
||||
|
||||
All fields optional.
|
||||
|
||||
### Response `200`
|
||||
Updated template object.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Template not found |
|
||||
| `ERR_SMS_003` | 422 | Template already submitted/approved |
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/sms/template/{uuid}/submit`
|
||||
|
||||
Submit template for admin review (moves status from `draft` to `pending`).
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
Updated template with `status: "pending"`.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Template not found |
|
||||
| `ERR_SMS_003` | 422 | Already submitted |
|
||||
|
||||
---
|
||||
|
||||
## DELETE `/api/v1/sms/template/{uuid}`
|
||||
|
||||
Delete a template.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{ "success": true, "data": { "message": "قالب پیامک حذف شد" } }
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Template not found |
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/admin/sms/templates`
|
||||
|
||||
List all templates (admin view with all statuses).
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"name": "تأیید نوبت",
|
||||
"status": "approved",
|
||||
"created_at": 1717000000
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/admin/sms/template/{uuid}/approve`
|
||||
|
||||
Approve a pending template.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"note": "تأیید شد",
|
||||
"provider_code": "verify_appointment"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `note` | string | ❌ | Admin note |
|
||||
| `provider_code` | string | ❌ | Provider-side template code |
|
||||
|
||||
### Response `200`
|
||||
Updated template with `status: "approved"`.
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/admin/sms/template/{uuid}/reject`
|
||||
|
||||
Reject a pending template.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Request Body
|
||||
```json
|
||||
{
|
||||
"note": "متن قالب نامناسب است"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required |
|
||||
|-------|------|----------|
|
||||
| `note` | string | ✅ |
|
||||
|
||||
### Response `200`
|
||||
Updated template with `status: "rejected"`.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_VALIDATION_002` | 422 | Missing note |
|
||||
@@ -0,0 +1,162 @@
|
||||
# Specialty API
|
||||
|
||||
> **Prefix:** `/api/v1/specialties`, `/api/v1/admin/specialty`
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/specialties`
|
||||
|
||||
List all medical specialties.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `parent_id` | integer | ❌ | Filter to sub-specialties of a parent |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "قلب و عروق",
|
||||
"slug": "ghalb-va-oroug",
|
||||
"parent_id": null,
|
||||
"status": "active",
|
||||
"weight": 10
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "فوق تخصص قلب",
|
||||
"slug": "fowgh-takhassos-ghalb",
|
||||
"parent_id": 1,
|
||||
"status": "active",
|
||||
"weight": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/admin/specialties`
|
||||
|
||||
List all specialties with pagination (admin view — includes inactive).
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `page` | integer | ❌ | Default: 1 |
|
||||
| `limit` | integer | ❌ | Default: 20 |
|
||||
| `search` | string | ❌ | Search in name |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [ ... ],
|
||||
"meta": { "totalRecords": 50, "totalPages": 3, "currentPage": 1 }
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/admin/specialty`
|
||||
|
||||
Create a new specialty.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"name": "قلب و عروق",
|
||||
"slug": "ghalb-va-oroug",
|
||||
"parent_id": null,
|
||||
"status": "active",
|
||||
"weight": 10
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `name` | string | ✅ | Specialty name |
|
||||
| `slug` | string | ❌ | Auto-generated from name if omitted |
|
||||
| `parent_id` | integer | ❌ | Parent specialty ID (for sub-specialties) |
|
||||
| `status` | string | ❌ | `"active"` (default) or `"inactive"` |
|
||||
| `weight` | integer | ❌ | Sort weight |
|
||||
|
||||
### Response `201`
|
||||
Specialty object.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_VALIDATION_002` | 422 | Missing name |
|
||||
|
||||
---
|
||||
|
||||
## PATCH `/api/v1/admin/specialty/{id}`
|
||||
|
||||
Update a specialty.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `id` | integer | Specialty ID |
|
||||
|
||||
### Request Body (all optional)
|
||||
```json
|
||||
{
|
||||
"name": "قلب و عروق ویرایششده",
|
||||
"status": "inactive",
|
||||
"weight": 20,
|
||||
"parent_id": null,
|
||||
"slug": "new-slug"
|
||||
}
|
||||
```
|
||||
|
||||
### Response `200`
|
||||
Updated specialty object.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Specialty not found |
|
||||
|
||||
---
|
||||
|
||||
## DELETE `/api/v1/admin/specialty/{id}`
|
||||
|
||||
Delete a specialty.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{ "success": true, "data": { "message": "تخصص حذف شد" } }
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Specialty not found |
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
# Tag API
|
||||
|
||||
> **Prefix:** `/api/v1/tags`, `/api/v1/admin/tag`
|
||||
|
||||
Tags are used for categorizing blog posts.
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/tags`
|
||||
|
||||
List all active tags.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{ "id": 1, "name": "دیابت", "slug": "diabat", "status": "active" },
|
||||
{ "id": 2, "name": "قلب", "slug": "ghalb", "status": "active" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/admin/tags`
|
||||
|
||||
List all tags with pagination (admin view — includes inactive).
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Query Parameters
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `page` | integer | ❌ | Default: 1 |
|
||||
| `limit` | integer | ❌ | Default: 20 |
|
||||
| `search` | string | ❌ | Search in name |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [ ... ],
|
||||
"meta": { "totalRecords": 10, "totalPages": 1, "currentPage": 1 }
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/admin/tag`
|
||||
|
||||
Create a new tag.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"name": "دیابت",
|
||||
"slug": "diabat",
|
||||
"status": "active"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `name` | string | ✅ | Tag name |
|
||||
| `slug` | string | ❌ | Auto-generated from name if omitted |
|
||||
| `status` | string | ❌ | `"active"` (default) or `"inactive"` |
|
||||
|
||||
### Response `201`
|
||||
Tag object.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_VALIDATION_002` | 422 | Missing name |
|
||||
|
||||
---
|
||||
|
||||
## PATCH `/api/v1/admin/tag/{id}`
|
||||
|
||||
Update a tag.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `id` | integer | Tag ID |
|
||||
|
||||
All fields optional.
|
||||
|
||||
### Response `200`
|
||||
Updated tag object.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Tag not found |
|
||||
|
||||
---
|
||||
|
||||
## DELETE `/api/v1/admin/tag/{id}`
|
||||
|
||||
Delete a tag.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{ "success": true, "data": { "message": "تگ حذف شد" } }
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Tag not found |
|
||||
@@ -0,0 +1,154 @@
|
||||
# User Profile API
|
||||
|
||||
> **Prefix:** `/api/v1/user-profile`
|
||||
|
||||
User medical profiles store health information that can be shared with doctors.
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/user-profile`
|
||||
|
||||
Create a medical profile for the authenticated user.
|
||||
|
||||
**Permission:** `AUTH`
|
||||
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"name": "علی",
|
||||
"family": "احمدی",
|
||||
"fathers_name": "محمد",
|
||||
"national_code": "0012345678",
|
||||
"gender": "male",
|
||||
"blood_type": "A+",
|
||||
"marital_status": "married",
|
||||
"education": "لیسانس",
|
||||
"job": "مهندس",
|
||||
"address": "تهران، خیابان ولیعصر",
|
||||
"home_phone": "02112345678",
|
||||
"work_phone": "02198765432",
|
||||
"insurance_id": 1,
|
||||
"description": "توضیحات بیشتر",
|
||||
"sharing_with_user": true,
|
||||
"birthday": "1370-06-15",
|
||||
"date_of_birth": 648172800,
|
||||
"basic_insurance": "بیمه تأمین اجتماعی",
|
||||
"supplementary_insurance": "بیمه ایران",
|
||||
"other": {}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `name` | string | ❌ | First name |
|
||||
| `family` | string | ❌ | Last name |
|
||||
| `fathers_name` | string | ❌ | Father's name |
|
||||
| `national_code` | string | ❌ | National ID (10 digits) |
|
||||
| `gender` | string | ❌ | `"male"` or `"female"` |
|
||||
| `blood_type` | string | ❌ | `"A+"`, `"A-"`, `"B+"`, `"B-"`, `"AB+"`, `"AB-"`, `"O+"`, `"O-"` |
|
||||
| `marital_status` | string | ❌ | `"single"` or `"married"` |
|
||||
| `education` | string | ❌ | Education level |
|
||||
| `job` | string | ❌ | Occupation |
|
||||
| `address` | string | ❌ | Home address |
|
||||
| `home_phone` | string | ❌ | Home phone number |
|
||||
| `work_phone` | string | ❌ | Work phone number |
|
||||
| `insurance_id` | integer | ❌ | Insurance ID |
|
||||
| `description` | string | ❌ | Additional notes |
|
||||
| `sharing_with_user` | boolean | ❌ | Allow doctors to view this profile |
|
||||
| `birthday` | string | ❌ | Birthday in Jalali format `YYYY-MM-DD` |
|
||||
| `date_of_birth` | integer | ❌ | Birthday as Unix timestamp |
|
||||
| `basic_insurance` | string | ❌ | Basic insurance name |
|
||||
| `supplementary_insurance` | string | ❌ | Supplementary insurance name |
|
||||
| `other` | object | ❌ | Extra metadata |
|
||||
|
||||
### Response `201`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"uuid": "profile-uuid-...",
|
||||
"name": "علی",
|
||||
"family": "احمدی",
|
||||
"gender": "male",
|
||||
"blood_type": "A+",
|
||||
"national_code": "0012345678",
|
||||
"sharing_with_user": true,
|
||||
"created_at": 1717000000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_CONFLICT_001` | 409 | Profile already exists for this user |
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/user-profile/{uuid}`
|
||||
|
||||
Get a user profile.
|
||||
|
||||
**Permission:** `AUTH` — must be the profile owner, or a doctor if `sharing_with_user: true`, or `ROLE_ADMIN`
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `uuid` | string (UUID) | Profile UUID |
|
||||
|
||||
### Response `200`
|
||||
Full profile object including all fields.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_FORBIDDEN_001` | 403 | Not authorized to view this profile |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Profile not found |
|
||||
|
||||
---
|
||||
|
||||
## PATCH `/api/v1/user-profile/{uuid}`
|
||||
|
||||
Update a user profile.
|
||||
|
||||
**Permission:** `AUTH` — must be the profile owner
|
||||
|
||||
### Path Parameters
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `uuid` | string (UUID) | Profile UUID |
|
||||
|
||||
### Request Body
|
||||
Same fields as POST — all optional.
|
||||
|
||||
### Response `200`
|
||||
Updated profile object.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_FORBIDDEN_001` | 403 | Not the profile owner |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Profile not found |
|
||||
|
||||
---
|
||||
|
||||
## DELETE `/api/v1/user-profile/{uuid}`
|
||||
|
||||
Delete a user profile.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{ "success": true, "data": { "message": "پروفایل حذف شد" } }
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Profile not found |
|
||||
Reference in New Issue
Block a user