feat: Implement SMS sending functionality with KavehNegar and Rangineh providers

- Add SendSmsMessage class for encapsulating SMS message data.
- Create KavehNegarProvider and RanginehProvider classes implementing SmsProviderInterface for sending SMS.
- Implement SmsLogRepository and SmsTemplateRepository for managing SMS logs and templates.
- Develop SendSmsHandler for handling SMS sending messages.
- Create SmsService to manage SMS dispatching and logging.
- Add UserProfileController for managing user profiles with CRUD operations.
- Implement UserProfile entity and repository for user profile data management.
- Update symfony.lock and bootstrap.php for project dependencies and environment setup.
This commit is contained in:
hamed
2026-06-09 22:00:34 +03:30
commit de1a78a235
222 changed files with 36388 additions and 0 deletions
+313
View File
@@ -0,0 +1,313 @@
# تسک ۱۴: ماژول منشی
## توضیح
مدیریت منشی‌های دکترها که می‌توانند نوبت‌ها و پرداخت‌ها را مدیریت کنند.
هر دکتر بسته به پلن اشتراک می‌تواند ۱ یا ۳ منشی فعال داشته باشد.
## Endpoint ها
| متد | مسیر | توضیح | نیاز به Auth |
|-----|------|-------|-------------|
| POST | `/api/v1/secretary` | ایجاد منشی | بله (Doctor/Admin) |
| PATCH | `/api/v1/secretary/{uuid}` | ویرایش منشی | بله (Doctor/Admin) |
| GET | `/api/v1/secretary/{uuid}` | دریافت اطلاعات منشی | بله |
| DELETE | `/api/v1/secretary/{uuid}` | حذف منشی | بله (Doctor/Admin) |
| GET | `/api/v1/secretaries/{doctorUuid}` | لیست منشی‌های دکتر | بله |
## پیش‌نیازها
- تسک ۰۱، ۰۲، ۰۵ (Doctor)
## زمان تخمینی
۵ تا ۶ ساعت
---
## سیستم مجوزها — Resource-Based Permissions (مقیاس‌پذیر)
فیلد `permissions` در جدول `doctor_secretaries` یک JSON ساختاریافته با نسخه‌بندی است.
طراحی به گونه‌ای است که در آینده بتوان منابع (`resources`) و عملیات (`actions`) جدید اضافه کرد بدون تغییر در ساختار جدول.
### ساختار JSON
```json
{
"version": 1,
"resources": {
"appointments": {
"view": true,
"create": true,
"cancel": false,
"update_status": true
},
"addresses": {
"view": true,
"create": true,
"update": true,
"delete": false
},
"clinic_info": {
"view": true,
"update": false
},
"insurances": {
"view": true,
"create": true,
"update": true,
"delete": false
}
}
}
```
### منابع و عملیات فعلی
| Resource | Actions | توضیح |
|----------|---------|-------|
| `appointments` | `view`, `create`, `cancel`, `update_status` | نوبت‌ها |
| `addresses` | `view`, `create`, `update`, `delete` | آدرس‌های مطب/کلینیک |
| `clinic_info` | `view`, `update` | اطلاعات مطب یا کلینیک |
| `insurances` | `view`, `create`, `update`, `delete` | بیمه‌ها |
### مقیاس‌پذیری — اضافه کردن Resource جدید در آینده
برای اضافه کردن Resource جدید (مثلاً `patients` یا `reports`) فقط کافی است:
1. کلید جدید به JSON اضافه شود — بدون migration جدید
2. کد Permission Checker به صورت خودکار آن را پشتیبانی می‌کند
3. منشی‌های موجود که کلید جدید را ندارند، به صورت پیش‌فرض `false` دارند
### پیاده‌سازی PHP — SecretaryPermissionChecker
```php
// src/Secretary/Security/SecretaryPermissionChecker.php
class SecretaryPermissionChecker
{
/**
* بررسی مجوز منشی برای یک عملیات روی یک منبع
* مثال: $checker->can($secretary, 'appointments', 'create')
*/
public function can(Secretary $secretary, string $resource, string $action): bool
{
if (!$secretary->isActive()) {
return false;
}
$permissions = $secretary->getPermissions();
return (bool) ($permissions['resources'][$resource][$action] ?? false);
}
/**
* بررسی دسترسی کامل به یک منبع (همه actions باید true باشند)
*/
public function canAll(Secretary $secretary, string $resource, array $actions): bool
{
return array_reduce(
$actions,
fn($carry, $action) => $carry && $this->can($secretary, $resource, $action),
true
);
}
}
```
**مثال استفاده در Controller:**
```php
// در AppointmentController
if (!$this->permissionChecker->can($secretary, 'appointments', 'create')) {
throw new AccessDeniedHttpException('منشی مجاز به ثبت نوبت نیست');
}
// در InsuranceController
if (!$this->permissionChecker->can($secretary, 'insurances', 'delete')) {
throw new AccessDeniedHttpException('منشی مجاز به حذف بیمه نیست');
}
```
### پیش‌فرض هنگام ایجاد منشی
```json
{
"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
}
}
}
```
---
## POST /api/v1/secretary
```json
// Request
{
"mobile_number": "09120671756",
"doctor_uuid": "61be915b-...",
"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
}
}
}
}
// Response 201
{
"success": true,
"data": {
"uuid": "...",
"user": { "uuid": "...", "realname": "فاطمه رضایی", "mobile": "09120671756" },
"doctor": { "uuid": "...", "name": "دکتر احمدی" },
"active": true,
"permissions": { ... },
"created_at": 1748000000
}
}
// Response 422 — حد مجاز منشی
{
"success": false,
"errors": [{ "code": "ERR_SECRETARY_001", "message": "پلن فعلی اجازه منشی بیشتر را نمی‌دهد" }]
}
```
**قانون بررسی پلن (سمت سرور):**
```
پلن بیسیک → max 1 منشی فعال
پلن پیشرفته → max 3 منشی فعال
هنگام POST /secretary:
activeCount = COUNT(*) WHERE doctor_id=X AND active=true
if activeCount >= maxAllowed → 422
```
---
## PATCH /api/v1/secretary/{uuid}
```json
// Request (فقط resources موردنظر — deep merge با پیش‌فرض‌ها)
{
"active": false,
"permissions": {
"resources": {
"insurances": {
"create": true,
"update": true
}
}
}
}
// نکته: فقط resources/actions ارسال‌شده تغییر می‌کنند — بقیه دست‌نخورده می‌مانند
```
// Response 200
{
"success": true,
"data": { ... }
}
```
---
## GET /api/v1/secretary/{uuid}
```json
{
"success": true,
"data": {
"uuid": "...",
"user": {
"uuid": "...",
"realname": "فاطمه رضایی",
"mobile": "09120671756",
"picture": null
},
"doctor": { "uuid": "...", "name": "دکتر احمدی" },
"active": true,
"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 }
}
},
"created_at": 1748000000
}
}
```
---
## GET /api/v1/secretaries/{doctorUuid}
```json
{
"success": true,
"data": [
{
"uuid": "...",
"user": { "uuid": "...", "realname": "فاطمه رضایی", "mobile": "09120671756" },
"active": true,
"permissions": { ... },
"created_at": 1748000000
}
]
}
```
---
## نکات مهم
- **کاربر منشی:** هنگام ایجاد منشی با mobile_number، ابتدا بررسی می‌شود آیا کاربر با این شماره وجود دارد — اگر نه، کاربر جدید ایجاد می‌شود
- **ROLE:** کاربر منشی باید role `doctor_s_secretary` داشته باشد
- **لاگین منشی:** منشی می‌تواند با username/password لاگین کند (تسک ۰۲)
- **بررسی پلن:** کاملاً سمت سرور انجام می‌شود، قابل دور زدن نیست
- **نوبت آفلاین:** نوبتی که منشی ثبت می‌کند (`appointments.create`) کمیسیون نماینده ندارد
- **PATCH permissions:** فقط resources/actions ارسال‌شده تغییر می‌کنند (deep merge) — بقیه دست‌نخورده
- **Resource ناشناخته:** اگر resource جدیدی در JSON باشد که سرور نمی‌شناسد، نادیده گرفته می‌شود (forward compat)
- **پیش‌فرض `false`:** اگر resource یا action در JSON وجود نداشته باشد → `false` (deny by default)