- Introduced a `tag` field in the `SmsLog` entity to categorize SMS messages. - Updated the `SmsService` to handle the new `tag` parameter during SMS dispatch. - Implemented a `SmsTextResolver` service to resolve SMS message templates based on tags. - Created a new `SmsMessageTemplate` entity for editable SMS templates with placeholders. - Added endpoints for managing SMS message templates in the admin panel. - Enhanced existing SMS dispatching methods across various controllers to utilize the tagging system. - Migrated the database to include the new `tag` field and created a seeding command for default SMS templates. - Updated admin API to filter SMS logs by tag and include tag information in responses.
928 lines
21 KiB
Markdown
928 lines
21 KiB
Markdown
# 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
|
||
|
||
> **نماینده (ROLE_REPRESENTATION):** افزودن پزشک و کلینیک برای نماینده از طریق endpointهای جدا انجام میشود — `POST /api/v1/representation/doctor` و `POST /api/v1/representation/clinic` (به `docs/api/representation.md` مراجعه کنید). در نسخهی نماینده، `representation_id` پزشک خودکار روی نمایندهی کاربر جاری ست میشود. endpointهای `/api/v1/admin/*` همچنان فقط `ROLE_ADMIN` هستند.
|
||
|
||
### 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/today-stats`
|
||
|
||
Get appointment statistics for a specific date (defaults to today).
|
||
|
||
**Permission:** `ROLE_ADMIN`
|
||
|
||
### Query Parameters
|
||
| Param | Type | Required | Description |
|
||
|-------|------|----------|-------------|
|
||
| `date` | string (YYYY-MM-DD) | ❌ | Default: today |
|
||
|
||
### Response `200`
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"total": 47,
|
||
"completed": 20,
|
||
"waiting": 18,
|
||
"cancelled": 9
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### GET `/api/v1/admin/appointments`
|
||
|
||
List appointments filtered by date and/or doctor. Sorted by `slot_start ASC`.
|
||
|
||
**Permission:** `ROLE_ADMIN`
|
||
|
||
### Query Parameters
|
||
| Param | Type | Required | Description |
|
||
|-------|------|----------|-------------|
|
||
| `page` | integer | ❌ | Default: 1 |
|
||
| `limit` | integer | ❌ | Default: 15, max: 500 |
|
||
| `search` | string | ❌ | Search by patient name/mobile or doctor name |
|
||
| `status` | string | ❌ | Filter by status |
|
||
| `date` | string (YYYY-MM-DD) | ❌ | Filter by slot date |
|
||
| `doctor_uuid` | string | ❌ | Filter by doctor UUID |
|
||
|
||
### Response `200`
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": [
|
||
{
|
||
"uuid": "appt-uuid",
|
||
"patient_name": "محمد رضایی",
|
||
"patient_mobile": "09123456789",
|
||
"doctor_uuid": "doctor-uuid",
|
||
"doctor_name": "دکتر علی احمدی",
|
||
"slot_start": 1718438400,
|
||
"slot_end": 1718439600,
|
||
"appointment_date": "2025-06-15",
|
||
"appointment_time": "09:00",
|
||
"end_time": "09:20",
|
||
"status": "confirmed",
|
||
"version": 1,
|
||
"created_at": "2025-06-14T10:30:00+03:30"
|
||
}
|
||
],
|
||
"meta": { "totalRecords": 47, "totalPages": 1, "currentPage": 1 }
|
||
}
|
||
```
|
||
|
||
**Status values:** `pending` | `confirmed` | `completed` | `cancelled_by_doctor` | `cancelled_by_user` | `no_show` | `expired`
|
||
|
||
---
|
||
|
||
### POST `/api/v1/admin/appointment`
|
||
|
||
Create a new appointment for a patient. If no user exists with the given mobile, a new user account is created automatically.
|
||
|
||
**Permission:** `ROLE_ADMIN`
|
||
|
||
### Request Body
|
||
```json
|
||
{
|
||
"doctor_uuid": "doctor-uuid",
|
||
"slot_start": 1718438400,
|
||
"slot_end": 1718439600,
|
||
"patient_mobile": "09123456789",
|
||
"patient_name": "علی محمدی",
|
||
"note": "optional note"
|
||
}
|
||
```
|
||
|
||
> `patient_mobile` و `patient_name` هر دو اجباری هستند. اگر کاربری با این شماره موبایل نداشته باشیم، یک کاربر جدید با نقش `ROLE_USER` ساخته میشود.
|
||
|
||
### Response `201`
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"uuid": "appt-uuid",
|
||
"slot_start": 1718438400,
|
||
"slot_end": 1718439600,
|
||
"status": "pending"
|
||
}
|
||
}
|
||
```
|
||
|
||
### Error Responses
|
||
| Code | HTTP | Description |
|
||
|------|------|-------------|
|
||
| `VALIDATION` | 422 | Missing required fields (doctor_uuid, slot_start, slot_end, patient_mobile, patient_name) |
|
||
| `DOCTOR_NOT_FOUND` | 404 | Doctor UUID not found |
|
||
| `SLOT_TAKEN` | 409 | Slot already booked |
|
||
|
||
---
|
||
|
||
## 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: 15 |
|
||
| `search` | string | ❌ | Search by name or mobile (representation's or linked user's) |
|
||
| `city_id` | integer | ❌ | Filter by city |
|
||
|
||
### Response `200`
|
||
Paginated representation list. Each item:
|
||
```json
|
||
{
|
||
"id": 3,
|
||
"uuid": "...",
|
||
"full_name": "حامد حسینی",
|
||
"mobile_number": "09120671756",
|
||
"city_id": 132,
|
||
"city": "یزد",
|
||
"commission_percent": 10.0,
|
||
"wallet_balance": 0,
|
||
"is_active": true,
|
||
"created_at": "2026-06-18T..."
|
||
}
|
||
```
|
||
|
||
> `mobile_number` falls back to the linked user's mobile when the representation's own `mobile_number` column is empty.
|
||
|
||
> **Deactivation, not deletion:** the admin panel deactivates a representation via `PATCH /api/v1/representation/{uuid}` with `{ "active": false }` rather than calling `DELETE`.
|
||
|
||
---
|
||
|
||
## 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 |
|
||
|
||
> Ratings are multi-dimensional (five 0–100 dimensions). Each row's `overall` is the mean of the five dimensions (`0–100`) and `score` is that mean on a 0–5 scale (`overall / 20`).
|
||
|
||
---
|
||
|
||
### 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: 15, max 100 |
|
||
| `tag` | string | ❌ | فیلتر بر اساس تگ: `global` \| `otp` \| `payment` \| `clinic_invitation` \| `pre_registration` \| `notification_mobile` \| `user_template` |
|
||
|
||
### Response `200`
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": [
|
||
{
|
||
"uuid": "...",
|
||
"recipient": "09123456789",
|
||
"message": "کد تأیید: 123456",
|
||
"status": "sent",
|
||
"provider": "kavenegar",
|
||
"tag": "otp",
|
||
"sent_at": "2026-06-19T...",
|
||
"created_at": "2026-06-19T..."
|
||
}
|
||
],
|
||
"meta": { "totalRecords": 5000, "totalPages": 334, "currentPage": 1 }
|
||
}
|
||
```
|
||
> `tag` نوع پیامک را مشخص میکند؛ پیشفرض پیامکهای سیستمیِ بیبرچسب `global` است.
|
||
|
||
---
|
||
|
||
### 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 |
|
||
|
||
---
|
||
|
||
## Settings
|
||
|
||
### GET /api/v1/admin/settings
|
||
|
||
Returns all site configuration values.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"commission_enabled": "0",
|
||
"commission_percent": "0",
|
||
"site_name": "ClinicPro",
|
||
"support_phone": "",
|
||
"max_cancel_hours_before": "24",
|
||
"appointment_reminder_hours": "2",
|
||
"payment_test_mode": "0",
|
||
"mellat_terminal_id": "",
|
||
"mellat_username": "",
|
||
"mellat_password": "",
|
||
"sep_terminal_id": "",
|
||
"sms_provider": "kavenegar",
|
||
"kavenegar_api_key": "",
|
||
"kavenegar_sender": "",
|
||
"sms_price_rials": "500"
|
||
}
|
||
}
|
||
```
|
||
|
||
All values are strings. Missing keys return their default values.
|
||
|
||
### PATCH /api/v1/admin/settings
|
||
|
||
Update one or more settings. Unknown keys are silently ignored.
|
||
|
||
**Request body** (partial update — send only keys to change):
|
||
```json
|
||
{
|
||
"commission_enabled": "1",
|
||
"commission_percent": "5",
|
||
"site_name": "کلینیکپرو",
|
||
"payment_test_mode": "1",
|
||
"mellat_terminal_id": "12345678",
|
||
"mellat_username": "user",
|
||
"mellat_password": "pass",
|
||
"sep_terminal_id": "87654321",
|
||
"sms_provider": "kavenegar",
|
||
"kavenegar_api_key": "your-api-key",
|
||
"kavenegar_sender": "10008664",
|
||
"sms_price_rials": "500"
|
||
}
|
||
```
|
||
|
||
**Response `200`** — same shape as GET, returns all settings after save.
|
||
|
||
**Commission rules:**
|
||
- `commission_enabled` — `"1"` = active, `"0"` = inactive
|
||
- `commission_percent` — integer string, `0`–`100`
|
||
- Commission applies only to regular users (`booked_by = user`); secretaries are exempt
|
||
|
||
**Payment gateway rules:**
|
||
- `payment_test_mode` — `"1"` = all payments use MockGateway (no real bank calls), `"0"` = real gateways
|
||
- `payment_allowed_frontend_hosts` — comma-separated hosts allowed as a payment `frontend_address` (origin site to return to). Falls back to the `ALLOWED_FRONTEND_HOSTS` env var when empty. Add a consumer site's host here to permit its payments.
|
||
- Gateway credentials (mellat/sep) read from DB first, fallback to env vars if DB value is empty
|
||
- MockGateway callback: same URL pattern + `&mock=1&ResCode=0&RefId=MOCK-{orderId}` (add `&cancel=1` to simulate a user cancellation → `canceled`)
|
||
|
||
**SMS provider rules:**
|
||
- `sms_provider` — `"kavenegar"` or `"rangineh"`
|
||
- Kavenegar API key and sender read from DB first, fallback to env vars `KAVENEGAR_API_KEY`, `KAVENEGAR_SENDER`
|
||
|
||
---
|
||
|
||
## Pre-Registration Management
|
||
|
||
### GET `/api/v1/admin/pre-registrations`
|
||
|
||
List pre-registration requests. **Permission:** `ROLE_ADMIN`
|
||
|
||
**Query params:**
|
||
|
||
| Param | Default | Notes |
|
||
|-------|---------|-------|
|
||
| `page` | 1 | |
|
||
| `limit` | 20 | max 50 |
|
||
| `status` | `pending` | `pending` \| `approved` \| `rejected` \| `all` |
|
||
|
||
**Response `200`** (paginated):
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": [
|
||
{
|
||
"uuid": "...",
|
||
"type": "independent_doctor",
|
||
"name": "دکتر احمدی",
|
||
"mobile": "09121234567",
|
||
"info": "متخصص داخلی",
|
||
"status": "pending",
|
||
"admin_note": null,
|
||
"created_at": 1718000000
|
||
}
|
||
],
|
||
"meta": { "totalRecords": 5, "totalPages": 1, "currentPage": 1 }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### POST `/api/v1/admin/pre-registrations/{uuid}/approve`
|
||
|
||
Approve a pending request. Creates User + Doctor/Clinic entity based on `type`, resets password, sends SMS. **Permission:** `ROLE_ADMIN`
|
||
|
||
**Response `200`:**
|
||
```json
|
||
{ "success": true, "data": { "message": "تأیید شد و اطلاعات ورود ارسال گردید" } }
|
||
```
|
||
|
||
**Error Codes:**
|
||
|
||
| Code | HTTP | Meaning |
|
||
|------|------|---------|
|
||
| `NOT_FOUND` | 404 | UUID not found |
|
||
| `ALREADY_PROCESSED` | 409 | Status is not pending |
|
||
|
||
---
|
||
|
||
### POST `/api/v1/admin/pre-registrations/{uuid}/reject`
|
||
|
||
Reject a pending request. **Permission:** `ROLE_ADMIN`
|
||
|
||
**Request body** (optional):
|
||
```json
|
||
{ "note": "مدارک ناقص است" }
|
||
```
|
||
|
||
**Response `200`:**
|
||
```json
|
||
{ "success": true, "data": { "message": "درخواست رد شد" } }
|
||
```
|