- 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.
461 lines
9.7 KiB
Markdown
461 lines
9.7 KiB
Markdown
# 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 |
|
|
|
|
---
|
|
|
|
## SMS Wallet
|
|
|
|
کیف پیامکی — جدا از کیف مالی، فقط برای ارسال پیامک.
|
|
|
|
### GET /api/v1/sms/wallet/balance
|
|
|
|
**Permission:** `IS_AUTHENTICATED_FULLY`
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"balance_rials": 15000,
|
|
"sms_price_rials": 500,
|
|
"estimated_sms_count": 30
|
|
}
|
|
}
|
|
```
|
|
|
|
### POST /api/v1/sms/wallet/charge
|
|
|
|
شارژ کیف پیامکی از طریق درگاه پرداخت.
|
|
|
|
**Permission:** `IS_AUTHENTICATED_FULLY`
|
|
|
|
```json
|
|
{
|
|
"gateway": "mellat",
|
|
"amount_rials": 50000,
|
|
"frontend_address": "https://example.com/sms-wallet"
|
|
}
|
|
```
|
|
|
|
**Response 200:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"payment_uuid": "...",
|
|
"redirect_url": "https://gateway...",
|
|
"order_id": "ORD-..."
|
|
}
|
|
}
|
|
```
|
|
|
|
پس از پرداخت موفق، موجودی کیف خودکار شارژ میشود.
|
|
|
|
### GET /api/v1/sms/wallet/logs
|
|
|
|
تراکنشهای کیف پیامک (paginated).
|
|
|
|
**Query params:** `page`, `limit`
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": [
|
|
{
|
|
"uuid": "...",
|
|
"type": "credit",
|
|
"amount_rials": 50000,
|
|
"description": "شارژ کیف پیامک",
|
|
"created_at": 1718000000
|
|
}
|
|
],
|
|
"meta": { "totalRecords": 5, "totalPages": 1, "currentPage": 1 }
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## SMS Settings
|
|
|
|
### GET /api/v1/sms/settings
|
|
|
|
تنظیمات پیامک entity جاری.
|
|
|
|
**Permission:** `IS_AUTHENTICATED_FULLY`
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"entity_type": "clinic",
|
|
"entity_id": 5,
|
|
"reminder_enabled": true,
|
|
"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
|
|
}
|
|
}
|
|
```
|
|
|
|
### PATCH /api/v1/sms/settings
|
|
|
|
**Permission:** `IS_AUTHENTICATED_FULLY`
|
|
|
|
```json
|
|
{
|
|
"reminder_enabled": true,
|
|
"reminder_hours_before": 3,
|
|
"post_visit_enabled": true,
|
|
"post_visit_text": "از مراجعه شما سپاسگزاریم"
|
|
}
|
|
```
|
|
|
|
**تغییر رفتار `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)
|