feat: Implement financial engine for commission and tax calculations
- Added new configuration keys for appointment and upgrade commissions, tax settings, and SMS panel fee in SiteConfigController and SiteConfigRepository. - Introduced CommissionService to handle commission calculations for appointments and subscriptions, including tax deductions and SMS fees. - Created FinancialBreakdown entity and repository to log financial transactions. - Updated PaymentController to process commissions upon successful payments for appointments and subscriptions. - Developed FinancialReportPage in the admin panel to display financial breakdowns and summaries. - Added database migration for the new financial_breakdowns table.
This commit is contained in:
@@ -966,3 +966,72 @@ Reject a pending request. **Permission:** `ROLE_ADMIN`
|
||||
```json
|
||||
{ "success": true, "data": { "message": "درخواست رد شد" } }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## موتور مالی نمایندگی
|
||||
|
||||
تنظیمات مالی از طریق `GET`/`PATCH /api/v1/admin/settings` کنترل میشوند (کلیدها در whitelist `SiteConfigController::ALLOWED_KEYS`):
|
||||
|
||||
| کلید | پیشفرض | شرح |
|
||||
|------|---------|-----|
|
||||
| `appointment_commission_enabled` | `0` | فعالسازی پورسانت نوبت (درصد از `Representation.commission_percent` هر نماینده) |
|
||||
| `upgrade_commission_enabled` | `0` | فعالسازی پورسانت ارتقاء اشتراک |
|
||||
| `upgrade_commission_percent` | `20` | درصد پورسانت ارتقاء (سراسری) |
|
||||
| `tax_enabled` | `0` | فعالسازی مالیات بر ارزش افزوده |
|
||||
| `tax_percent` | `10` | درصد مالیات |
|
||||
| `sms_panel_fee_rials` | `1500000` | هزینه ثابت پنل پیامک به ریال (از نوبت و اشتراک کسر میشود) |
|
||||
|
||||
**ترتیب محاسبه** (در `CommissionService`): ۱) کسر `sms_panel_fee_rials` ۲) مالیاتِ استخراجی `afterSms × tax/(100+tax)` ۳) پورسانت = `netAfterTax × percent/100`. سهم نماینده به کیفپولش (`WalletTransaction` credit) واریز و یک ردیف `FinancialBreakdown` ثبت میشود (idempotent بر اساس `payment_id`).
|
||||
|
||||
### GET `/api/v1/admin/financial-breakdowns`
|
||||
|
||||
لیست تفکیک مالی تراکنشها (paginated). **Permission:** `ROLE_ADMIN`
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `page` | integer | پیشفرض 1 |
|
||||
| `limit` | integer | پیشفرض 15، حداکثر 100 |
|
||||
| `representation_id` | integer | فیلتر نماینده |
|
||||
| `source` | string | `appointment` یا `subscription` |
|
||||
| `from` | integer | Unix timestamp شروع بازه |
|
||||
| `to` | integer | Unix timestamp پایان بازه |
|
||||
|
||||
**Response `200` (paginated):**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"uuid": "…", "order_id": "ORD-…", "source": "appointment",
|
||||
"gross_rials": 2000000, "sms_fee_rials": 1500000,
|
||||
"tax_percent": 10, "tax_rials": 45455, "net_after_tax_rials": 454545,
|
||||
"commission_percent": 20, "representation_share_rials": 90909,
|
||||
"system_share_rials": 363636,
|
||||
"representation_id": 3, "representation_name": "نماینده یزد",
|
||||
"doctor_id": 12, "clinic_id": null, "created_at": "2026-06-24T…"
|
||||
}
|
||||
],
|
||||
"meta": { "totalRecords": 1, "totalPages": 1, "currentPage": 1 }
|
||||
}
|
||||
```
|
||||
|
||||
### GET `/api/v1/admin/financial-summary`
|
||||
|
||||
جمع کل مبالغ. **Permission:** `ROLE_ADMIN`
|
||||
|
||||
**Response `200`:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"total_gross": 2000000,
|
||||
"total_representation_income": 90909,
|
||||
"total_tax_collected": 45455,
|
||||
"total_sms_fee": 1500000,
|
||||
"total_system_share": 363636
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -106,6 +106,8 @@ Initiate payment for an appointment. Returns a redirect URL to the payment gatew
|
||||
```
|
||||
|
||||
> **On successful callback** for an appointment payment, the booking is transitioned `pending → confirmed` (its 15-minute `expires_at` is cleared) and a confirmation SMS is dispatched to the patient's mobile. If the booking already lapsed to `expired` before payment confirmed, it is **not** re-confirmed (the transition is rejected) — handle refund out of band.
|
||||
>
|
||||
> **پورسانت نماینده:** اگر پزشک نوبت `representation_id` داشته باشد و `appointment_commission_enabled=1` باشد، پس از confirm شدن `CommissionService` هزینه پنل پیامک و مالیات را کسر و سهم نماینده را به کیفپولش واریز میکند (ردیف `FinancialBreakdown` ثبت میشود). برای پرداخت اشتراک هم اگر `upgrade_commission_enabled=1` و پزشک/کلینیک `representation_id` داشته باشد همین منطق با درصد `upgrade_commission_percent` اعمال میشود. کلیدهای تنظیمات و ترتیب محاسبه در `docs/api/admin.md`.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|
||||
@@ -252,3 +252,9 @@ Updated settlement object with `status: "rejected"`.
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Settlement not found |
|
||||
| `ERR_VALIDATION_002` | 422 | Missing note |
|
||||
|
||||
---
|
||||
|
||||
## FinancialBreakdown (لاگ مالی)
|
||||
|
||||
علاوه بر تسویهحساب دستی، کیفپول نماینده بهصورت خودکار از طریق `CommissionService` هنگام پرداخت موفقِ نوبت/اشتراک شارژ میشود (`WalletTransaction` credit). هر واریز یک ردیف `FinancialBreakdown` ثبت میکند که تفکیک کامل تراکنش (ناخالص، هزینه پیامک، مالیات، خالص، درصد و سهم پورسانت، سهم سیستم) را نگه میدارد. ثبت idempotent است (بر اساس `payment_id`). گزارشها از طریق `GET /api/v1/admin/financial-breakdowns` و `GET /api/v1/admin/financial-summary` در دسترساند — جزئیات در `docs/api/admin.md`.
|
||||
|
||||
Reference in New Issue
Block a user