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:
@@ -0,0 +1,90 @@
|
||||
# پایگاه داده — تسک ۱۸: تسویه نماینده
|
||||
|
||||
## جدول: wallet_transactions (تراکنشهای کیف پول)
|
||||
|
||||
| ستون | نوع | توضیح |
|
||||
|------|-----|-------|
|
||||
| id | INT UNSIGNED AUTO_INCREMENT PK | |
|
||||
| uuid | CHAR(36) UNIQUE NOT NULL | |
|
||||
| representation_id | INT FK → representations.id NOT NULL | نماینده |
|
||||
| type | VARCHAR(10) NOT NULL | `credit` (واریز) یا `debit` (برداشت) |
|
||||
| amount | INT NOT NULL | مبلغ به ریال |
|
||||
| source | VARCHAR(20) NOT NULL | `appointment` یا `subscription` یا `settlement` |
|
||||
| source_id | INT NULL | FK به payments.id یا subscription_payments.id یا settlements.id |
|
||||
| description | VARCHAR(255) NULL | توضیح تراکنش |
|
||||
| created_at | INT NOT NULL | Unix timestamp |
|
||||
|
||||
```sql
|
||||
CREATE INDEX idx_wallet_representation ON wallet_transactions(representation_id);
|
||||
CREATE INDEX idx_wallet_type ON wallet_transactions(representation_id, type);
|
||||
CREATE INDEX idx_wallet_source ON wallet_transactions(source, source_id);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## جدول: settlements (درخواستهای تسویه)
|
||||
|
||||
| ستون | نوع | توضیح |
|
||||
|------|-----|-------|
|
||||
| id | INT UNSIGNED AUTO_INCREMENT PK | |
|
||||
| uuid | CHAR(36) UNIQUE NOT NULL | |
|
||||
| representation_id | INT FK → representations.id NOT NULL | نماینده درخواستدهنده |
|
||||
| amount | INT NOT NULL | مبلغ درخواستی (ریال) |
|
||||
| card_number | VARCHAR(25) NOT NULL | شماره کارت بانکی برداشت |
|
||||
| bank_name | VARCHAR(50) NOT NULL | نام بانک |
|
||||
| status | VARCHAR(10) DEFAULT 'pending' | `pending` / `approved` / `rejected` |
|
||||
| description | VARCHAR(255) NULL | توضیح نماینده |
|
||||
| admin_note | VARCHAR(255) NULL | یادداشت ادمین هنگام تأیید/رد |
|
||||
| requested_at | INT NOT NULL | Unix timestamp — زمان درخواست |
|
||||
| resolved_at | INT NULL | Unix timestamp — زمان تأیید/رد |
|
||||
| created_at | INT NOT NULL | Unix timestamp |
|
||||
| updated_at | INT NOT NULL | Unix timestamp |
|
||||
|
||||
```sql
|
||||
CREATE INDEX idx_settlements_representation ON settlements(representation_id);
|
||||
CREATE INDEX idx_settlements_status ON settlements(status);
|
||||
CREATE UNIQUE INDEX idx_settlements_pending ON settlements(representation_id, status)
|
||||
WHERE status = 'pending';
|
||||
-- این index یکتایی یک pending در هر زمان را enforce میکند
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## فیلد balance در representations
|
||||
|
||||
جدول `representations` باید یک فیلد `balance` داشته باشد:
|
||||
|
||||
| ستون | نوع | توضیح |
|
||||
|------|-----|-------|
|
||||
| balance | INT DEFAULT 0 | موجودی کیف پول (ریال) — همیشه sync با wallet_transactions |
|
||||
|
||||
> **نکته:** `balance` باید همزمان با هر تراکنش در wallet_transactions آپدیت شود
|
||||
> تا query موجودی سریع باشد (بدون SUM روی wallet_transactions).
|
||||
|
||||
---
|
||||
|
||||
## فلوی واریز کمیسیون (هنگام پرداخت موفق)
|
||||
|
||||
```
|
||||
payments.status → 'received'
|
||||
↓
|
||||
commission = payments.amount × (representation.commission_percent / 100)
|
||||
↓
|
||||
INSERT INTO wallet_transactions (representation_id, type='credit', amount=commission, source='appointment', source_id=payment.id)
|
||||
↓
|
||||
UPDATE representations SET balance = balance + commission WHERE id = representation_id
|
||||
```
|
||||
|
||||
## فلوی تسویه (هنگام تأیید ادمین)
|
||||
|
||||
```
|
||||
PATCH /api/v1/settlement/{uuid}/approve
|
||||
↓
|
||||
بررسی: representations.balance >= settlements.amount
|
||||
↓
|
||||
INSERT INTO wallet_transactions (type='debit', amount=settlement.amount, source='settlement', source_id=settlement.id)
|
||||
↓
|
||||
UPDATE representations SET balance = balance - settlement.amount
|
||||
↓
|
||||
UPDATE settlements SET status='approved', resolved_at=NOW(), admin_note=...
|
||||
```
|
||||
@@ -0,0 +1,117 @@
|
||||
# تسک ۱۸: ماژول تسویه نماینده (Settlement)
|
||||
|
||||
## توضیح
|
||||
سیستم تسویهحساب نمایندگان — هر بار که از طریق دامنه یک نماینده نوبت پرداخت میشود،
|
||||
کمیسیون مشخصی (طبق `commission_percent`) به کیف پول نماینده واریز میشود.
|
||||
نماینده میتواند درخواست تسویه (برداشت) بدهد و ادمین آن را تأیید/رد میکند.
|
||||
|
||||
## فلوی کمیسیون (از مستند)
|
||||
|
||||
```
|
||||
بیمار → پرداخت نوبت از دامنه نماینده
|
||||
→ commission = amount × (commission_percent / 100)
|
||||
→ واریز به کیف پول نماینده (wallet_transactions)
|
||||
|
||||
دکتر → خرید اشتراک از طریق نماینده
|
||||
→ کمیسیون اشتراک → واریز به کیف پول نماینده
|
||||
```
|
||||
|
||||
## Endpoint ها
|
||||
|
||||
| متد | مسیر | توضیح | نیاز به Auth |
|
||||
|-----|------|-------|-------------|
|
||||
| GET | `/api/v1/representation/{id}/wallet` | موجودی کیف پول نماینده | بله (Admin / Owner) |
|
||||
| GET | `/api/v1/representation/{id}/wallet/transactions` | تاریخچه تراکنشهای کیف پول | بله (Admin / Owner) |
|
||||
| POST | `/api/v1/representation/{id}/settlement` | درخواست تسویه توسط نماینده | بله (Owner) |
|
||||
| GET | `/api/v1/settlements` | لیست همه درخواستهای تسویه | بله (Admin) |
|
||||
| GET | `/api/v1/settlement/{uuid}` | جزئیات یک درخواست تسویه | بله (Admin / Owner) |
|
||||
| PATCH | `/api/v1/settlement/{uuid}/approve` | تأیید تسویه توسط ادمین | بله (Admin) |
|
||||
| PATCH | `/api/v1/settlement/{uuid}/reject` | رد تسویه توسط ادمین | بله (Admin) |
|
||||
|
||||
## پیشنیازها
|
||||
- تسک ۰۱، ۰۲، ۱۵ (Payment)، ۱۶ (Representation)
|
||||
|
||||
## زمان تخمینی
|
||||
۸ تا ۱۰ ساعت
|
||||
|
||||
---
|
||||
|
||||
## نمونه Request — POST /api/v1/representation/{id}/settlement
|
||||
|
||||
```json
|
||||
{
|
||||
"amount": 5000000,
|
||||
"card_number": "6037-9999-1234-5678",
|
||||
"bank_name": "ملت",
|
||||
"description": "تسویه اسفندماه ۱۴۰۳"
|
||||
}
|
||||
```
|
||||
|
||||
## نمونه Response — GET /api/v1/representation/{id}/wallet
|
||||
|
||||
```json
|
||||
{
|
||||
"representation_id": 3,
|
||||
"balance": 12500000,
|
||||
"total_earned": 35000000,
|
||||
"total_settled": 22500000,
|
||||
"pending_settlement": 0
|
||||
}
|
||||
```
|
||||
|
||||
## نمونه Response — GET /api/v1/representation/{id}/wallet/transactions
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"type": "credit",
|
||||
"amount": 50000,
|
||||
"source": "appointment",
|
||||
"source_id": 142,
|
||||
"description": "کمیسیون نوبت #142",
|
||||
"created_at": 1748000000
|
||||
},
|
||||
{
|
||||
"uuid": "...",
|
||||
"type": "debit",
|
||||
"amount": 5000000,
|
||||
"source": "settlement",
|
||||
"source_id": 7,
|
||||
"description": "تسویه #7",
|
||||
"created_at": 1747000000
|
||||
}
|
||||
],
|
||||
"page": {
|
||||
"totalRecords": 48,
|
||||
"totalPages": 5,
|
||||
"currentPage": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## نمونه Response — GET /api/v1/settlement/{uuid}
|
||||
|
||||
```json
|
||||
{
|
||||
"uuid": "...",
|
||||
"representation": { "id": 3, "uuid": "...", "label": "نمایندگی یاسوج" },
|
||||
"amount": 5000000,
|
||||
"card_number": "6037-9999-1234-5678",
|
||||
"bank_name": "ملت",
|
||||
"status": "pending",
|
||||
"description": "تسویه اسفندماه ۱۴۰۳",
|
||||
"admin_note": null,
|
||||
"requested_at": 1748000000,
|
||||
"resolved_at": null
|
||||
}
|
||||
```
|
||||
|
||||
## نکات مهم
|
||||
|
||||
- **موجودی کافی:** قبل از ثبت درخواست تسویه، موجودی کیف پول نماینده بررسی شود
|
||||
- **یک درخواست pending:** نماینده نمیتواند همزمان دو درخواست `pending` داشته باشد
|
||||
- **کارت بانکی:** شماره کارت از لیست `bank_account` نماینده باشد (نه کارت دلخواه)
|
||||
- **مبلغ حداقل:** حداقل مبلغ تسویه باید تعریف شود (مثلاً ۱۰۰,۰۰۰ ریال)
|
||||
- **واریز کمیسیون:** هنگام `payments.status = 'received'` → کمیسیون محاسبه و به wallet واریز شود
|
||||
Reference in New Issue
Block a user