feat: enhance staff management and payment gateway features

- Fix national code handling in staff creation and updates to support Persian digits.
- Update ClinicStaff entity to allow longer national codes (up to 15 characters).
- Implement support for clinic secretaries in SecretaryController, allowing creation without a doctor UUID.
- Add a new endpoint to retrieve doctors associated with a clinic for secretary management.
- Improve appointment management by ensuring doctors are selectable even when no appointments exist.
- Extend PatientController to allow secretaries to create patient records if they have the appropriate permissions.
- Introduce a PriceInput component for better price formatting in forms, supporting Persian digits.
- Add a MockGateway for testing payment processes without real transactions.
- Enhance SMS settings management with an approval flow for post-visit text messages, including new fields for pending text and status.
- Update migrations to reflect changes in database schema for national codes and SMS settings.
This commit is contained in:
hamed
2026-06-15 11:03:56 +03:30
parent 55f646e2d4
commit 5cdcec23a9
32 changed files with 1487 additions and 128 deletions
+29 -2
View File
@@ -775,7 +775,16 @@ Returns all site configuration values.
"site_name": "ClinicPro",
"support_phone": "",
"max_cancel_hours_before": "24",
"appointment_reminder_hours": "2"
"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"
}
}
```
@@ -791,7 +800,16 @@ Update one or more settings. Unknown keys are silently ignored.
{
"commission_enabled": "1",
"commission_percent": "5",
"site_name": "کلینیک‌پرو"
"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"
}
```
@@ -802,6 +820,15 @@ Update one or more settings. Unknown keys are silently ignored.
- `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
- 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}`
**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
+1 -1
View File
@@ -5,7 +5,7 @@
Patient records track patients per entity (doctor or clinic). Each record holds multiple sessions (visits). Access requires an active subscription with the `patient_records` feature.
**Base path:** `/api/v1`
**Auth:** Bearer JWT (doctor or clinic role required)
**Auth:** Bearer JWT (doctor, clinic, or secretary with `appointments.view` permission required)
---
+56 -8
View File
@@ -10,7 +10,7 @@ Secretaries are linked to a doctor and have granular permissions controlling wha
Create a secretary for a doctor.
**Permission:** `ROLE_DOCTOR` must own the doctor profile
**Permission:** `ROLE_DOCTOR` (must own the doctor) | `ROLE_CLINIC` (must have the doctor in its clinic) | `ROLE_ADMIN`
### Request Body (`application/json`)
```json
@@ -74,10 +74,12 @@ Create a secretary for a doctor.
"success": true,
"data": {
"uuid": "sec-uuid-...",
"user_name": "علی محمدی",
"mobile_number": "09123456789",
"active": true,
"doctor_name": "دکتر احمد رضایی",
"doctor_uuid": "...",
"is_active": true,
"permissions": { ... },
"doctor": { "uuid": "...", "title": "دکتر علی احمدی" },
"created_at": 1717000000
}
}
@@ -87,9 +89,9 @@ Create a secretary for a doctor.
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_AUTH_006` | 403 | Not a doctor or not the doctor's owner |
| `ERR_AUTH_006` | 403 | Not the doctor owner / clinic owner / admin |
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |
| `ERR_CONFLICT_001` | 409 | Mobile number already in use |
| `ERR_CONFLICT_001` | 409 | Secretary already added for this doctor |
| `ERR_SECRETARY_001` | 422 | Plan limit for secretaries reached |
---
@@ -189,7 +191,7 @@ Delete a secretary.
Get all secretaries for a specific doctor.
**Permission:** `AUTH` must be the doctor or `ROLE_ADMIN`
**Permission:** `ROLE_DOCTOR` (must own doctor) | `ROLE_CLINIC` (must have doctor in clinic) | `ROLE_ADMIN`
### Path Parameters
| Param | Type | Description |
@@ -203,8 +205,11 @@ Get all secretaries for a specific doctor.
"data": [
{
"uuid": "...",
"user_name": "علی محمدی",
"mobile_number": "09...",
"active": true,
"doctor_name": "دکتر احمد رضایی",
"doctor_uuid": "...",
"is_active": true,
"permissions": { ... },
"created_at": 1717000000
}
@@ -216,11 +221,54 @@ Get all secretaries for a specific doctor.
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_FORBIDDEN_001` | 403 | Not the doctor |
| `ERR_FORBIDDEN_001` | 403 | Not authorized |
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |
---
## GET `/api/v1/secretaries/clinic/{clinicUuid}`
Get all secretaries across **all doctors** of a clinic.
**Permission:** `ROLE_CLINIC` (must own clinic) | `ROLE_ADMIN`
### Path Parameters
| Param | Type | Description |
|-------|------|-------------|
| `clinicUuid` | string (UUID) | Clinic UUID |
### Response `200`
```json
{
"success": true,
"data": [
{
"uuid": "...",
"user_name": "علی محمدی",
"mobile_number": "09...",
"doctor_name": "دکتر احمد رضایی",
"doctor_uuid": "...",
"is_active": true,
"permissions": { ... },
"created_at": 1717000000
}
]
}
```
### Notes
- یک منشی می‌تواند برای یک یا چند دکتر تعریف شود (جداگانه در جدول `doctor_secretaries`)
- این endpoint همه منشیان همه دکترهای کلینیک را یکجا برمی‌گرداند با ستون `doctor_name` برای تشخیص
### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_FORBIDDEN_001` | 403 | Not clinic owner |
| `ERR_NOT_FOUND_001` | 404 | Clinic not found |
---
## محدودیت پنل اشتراکی
تعداد منشی‌های مجاز بر اساس پنل فعال doctor تعیین می‌شود:
+43
View File
@@ -390,6 +390,9 @@ Updated template with `status: "rejected"`.
"reminder_hours_before": 2,
"post_visit_enabled": false,
"post_visit_text": null,
"post_visit_text_pending": null,
"post_visit_text_status": "none",
"post_visit_text_reject_reason": null,
"updated_at": 1718000000
}
}
@@ -408,10 +411,50 @@ Updated template with `status: "rejected"`.
}
```
**تغییر رفتار `post_visit_text`:** متن ارسال‌شده مستقیماً اعمال نمی‌شود — در فیلد `post_visit_text_pending` ذخیره می‌شود و وضعیت `post_visit_text_status` به `pending` تغییر می‌کند. پس از تأیید ادمین، به `post_visit_text` منتقل می‌شود.
**مقادیر `post_visit_text_status`:** `none` | `pending` | `approved` | `rejected`
---
## Admin Endpoints
### GET /api/v1/admin/sms/settings/review
**Permission:** `ROLE_ADMIN` — لیست همه تنظیمات SMS با وضعیت `pending`
```json
{
"success": true,
"data": {
"data": [
{
"id": 3,
"entity_type": "doctor",
"entity_id": 7,
"post_visit_text_pending": "متن در انتظار تأیید",
"post_visit_text_status": "pending",
...
}
]
}
}
```
### POST /api/v1/admin/sms/settings/{id}/approve
**Permission:** `ROLE_ADMIN` — تأیید متن پیامک. `post_visit_text_pending` به `post_visit_text` منتقل می‌شود.
### POST /api/v1/admin/sms/settings/{id}/reject
**Permission:** `ROLE_ADMIN` — رد متن پیامک.
```json
{ "reason": "متن نامناسب است" }
```
---
### GET /api/v1/admin/sms/wallet-report
**Permission:** `ROLE_ADMIN` — لیست همه کیف‌های پیامکی (paginated)
+1 -1
View File
@@ -57,7 +57,7 @@
| phone | string | ❌ |
| job_title | string | ❌ |
| address | string | ❌ |
| national_code | string(10) | ❌ |
| national_code | string(15) — ارقام فارسی به لاتین تبدیل می‌شوند | ❌ |
**Response 201:**
```json