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
+153
View File
@@ -0,0 +1,153 @@
# تسک ۱۶: ماژول Representation (نمایندگی + داشبورد)
## توضیح
این ماژول دو کارکرد دارد:
۱. **مدیریت نمایندگی‌ها (Multi-tenant)** — هر نماینده دامنه‌ای دارد؛ نوبت‌ها و دکترها به نماینده مرتبط می‌شوند
۲. **داشبورد دکتر/نماینده** — آمار نوبت‌ها، درآمد ماهانه/سالانه، بیماران
## Endpoint ها
| متد | مسیر | توضیح | نیاز به Auth |
|-----|------|-------|-------------|
| GET | `/api/v1/representation/{uuid}` | اطلاعات نمایندگی | بله (Admin) |
| POST | `/api/v1/representations/{id}/bank-accounts` | اضافه کردن کارت بانکی | بله (Admin) |
| GET | `/api/v1/representation/my-appointments/{representationId}` | نوبت‌های نماینده | بله |
| GET | `/api/v1/representation/my-doctor/{userId}` | دکترهای یک بیمار | بله |
| GET | `/api/v1/representation/filter/{representationId}` | آمار ماه جاری شمسی | بله |
| GET | `/api/v1/representation/yearly-income/{representationId}` | درآمد سالانه شمسی | بله |
## پیش‌نیازها
- تسک ۰۱، ۰۲، ۰۵ (Doctor)، ۱۰ (Appointment)، ۱۵ (Payment)
- پیاده‌سازی `JalaliDateService` (برای تبدیل تاریخ شمسی)
> **تسویه نماینده** در تسک ۱۸ پوشش داده می‌شود (کیف پول، درخواست برداشت، تأیید ادمین)
## زمان تخمینی
۸ تا ۱۰ ساعت
## Query Params
### GET /api/v1/representation/filter/{representationId}
```
?timestamp=1704067200 (اختیاری — Unix timestamp — برای تعیین ماه شمسی)
```
اگر timestamp نداده شود، ماه جاری شمسی استفاده می‌شود.
### GET /api/v1/representation/yearly-income/{representationId}
```
?timestamp=1704067200 (اختیاری — Unix timestamp — برای تعیین سال شمسی)
```
## نمونه Response‌ها
### GET /api/v1/representation/{uuid}
```json
{
"id": 1,
"uuid": "...",
"domain_name": "http://yasuj-nobat.localhost:3000/",
"city": { "id": 5, "uuid": "...", "label": "یاسوج" },
"active": true,
"commission_percent": 10.0,
"created": 1704067200,
"changed": 1716000000
}
```
### GET /api/v1/representation/filter/{id}
```json
{
"payments_total": { "total_price": 12500000, "count": 25 },
"total_patients": 142,
"today_appointments": 8
}
```
### GET /api/v1/representation/yearly-income/{id}
```json
{
"year": 1403,
"monthly_income": [
{ "month": 1, "income": 8500000 },
{ "month": 2, "income": 9200000 },
{ "month": 3, "income": 0 },
...
{ "month": 12, "income": 0 }
]
}
```
### GET /api/v1/representation/my-appointments/{id}
```json
{
"data": [
{
"id": 10, "uuid": "...",
"start_time": 1716000000, "end_time": 1716001800,
"status": "confirmed",
"slot": { "start": "09:00", "end": "09:30", "duration": 30, "location_id": 42 },
"doctor": { "id": 5, "uuid": "...", "label": "دکتر محمدی" },
"address": { "id": 42, "uuid": "...", "label": "مطب شیراز" },
"representation": { "id": 1, "uuid": "...", "label": "نمایندگی یاسوج" },
"owner": { "id": 20, "uuid": "...", "name": "علی رضایی" }
}
]
}
```
---
## POST /api/v1/representations/{id}/bank-accounts
```
ورودی:
Authorization: Bearer <token>
Content-Type: application/json
Body:
{
"card_number": "6037-9999-1234-5678",
"bank_name": "ملت",
"is_default": true
}
```
```json
// خروجی HTTP 201:
{
"success": true,
"bank_account": {
"card_number": "6037-9999-1234-5678",
"bank_name": "ملت",
"is_default": true
}
}
```
> **⚠ نکات مهم:**
> - فیلد `bank_account` در جدول Representation به‌صورت JSON Array ذخیره می‌شود
> - هر آیتم شامل: `card_number`, `bank_name`, `is_default`
> - اگر `is_default=true` باشد، `is_default` سایر کارت‌ها باید `false` شود
> - حداقل یک کارت باید `is_default=true` داشته باشد
---
## نمونه `bank_account` در GET /api/v1/representation/{uuid}
```json
{
"bank_account": [
{ "card_number": "6037-9999-1234-5678", "bank_name": "ملت", "is_default": true },
{ "card_number": "5859-3312-4455-6677", "bank_name": "صادرات", "is_default": false }
]
}
```
---
## فیلدهای Representation Entity
```
field_domain_name → دامنه (مثل: http://yasuj-nobat.localhost:3000/)
field_city → entity reference → category (شهر)
field_active → boolean
field_commission_percent → درصد کمیسیون
```