Files
docs/01-BUSINESS/commission-system-refactoring.md
T
masoodafar-web 002e99f6bf Implement Persian Date Conversion and Enhance User Network Information Service
- Added PersianDateTimeService for converting Gregorian dates to Persian format in the BackOffice frontend.
- Updated multiple frontend pages (Dashboard, UserPayouts, WorkerControl, UserNetworkInfo) to utilize the new Persian date service.
- Enhanced GetUserNetworkPositionDto with 28+ new fields for comprehensive user network data.
- Updated GetUserNetworkPositionQueryHandler to include new methods for calculating network statistics.
- Modified Protobuf messages to accommodate the new fields, increasing from 14 to 42.
- Refined week number calculation algorithm to ensure consistency across C# and SQL implementations.
- Created new CSV and Excel files for binary plan calculations.
- Ensured all changes are tested and validated for accuracy and performance.
2025-12-20 06:15:59 +03:30

318 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# اصلاحات سیستم کمیسیون هفتگی
## 📋 خلاصه تغییرات
سیستم کمیسیون هفتگی از **3 مرحله به 2 مرحله** ساده‌سازی شد:
### ❌ قبل (3 مرحله):
1. `CalculateWeeklyBalances` - محاسبه تعادل‌ها
2. `CalculateWeeklyCommissionPool` - محاسبه استخر
3. `ProcessUserPayouts` - پردازش پرداخت‌ها (تکراری!)
### ✅ بعد (2 مرحله):
1. `CalculateWeeklyBalances` - محاسبه تعادل‌ها تا 15 لول
2. `CalculateWeeklyCommissionPool` - محاسبه استخر + پردازش پرداخت‌ها
---
## 🔧 تغییرات جزئی
### 1️⃣ اضافه شدن فیلدها به `NetworkWeeklyBalance`
**فیلدهای جدید:**
```csharp
/// <summary>
/// مقدار فلش هر طرف (بعد از اعمال Cap)
/// </summary>
public int FlushedPerSide { get; set; }
/// <summary>
/// مجموع فلش از دو طرف (از دست رفته)
/// </summary>
public int TotalFlushed { get; set; }
```
**Migration:** `AddFlushedFieldsToNetworkWeeklyBalance`
---
### 2️⃣ اصلاح `CalculateWeeklyBalances`
**تغییرات:**
- ✅ فیلدهای `FlushedPerSide` و `TotalFlushed` ذخیره می‌شوند
-`WeeklyPoolContribution = 0` (دیگر در این مرحله محاسبه نمیشه)
- ✅ محدودیت 15 لول قبلاً موجود بود و درست کار می‌کند
**کد:**
```csharp
// محاسبه فلش
var flushedPerSide = totalBalances - cappedBalances;
var totalFlushed = flushedPerSide * 2;
// ذخیره
balance.FlushedPerSide = flushedPerSide;
balance.TotalFlushed = totalFlushed;
balance.WeeklyPoolContribution = 0; // Pool در مرحله بعد محاسبه میشه
```
---
### 3️⃣ اصلاح کامل `CalculateWeeklyCommissionPool`
**منطق جدید Pool:**
```csharp
// 1. Pool از فعالسازی‌های باشگاه این هفته میاد (نه از تعادل‌ها)
var newClubMembersCount = await _context.ClubMemberships
.Where(c => c.ActivatedAt >= startDate && c.ActivatedAt <= endDate)
.CountAsync();
var totalPoolAmount = newClubMembersCount * activationFee;
// 2. ارزش هر امتیاز
var totalBalancesInNetwork = weeklyBalances.Sum(x => x.TotalBalances);
var valuePerBalance = totalPoolAmount / totalBalancesInNetwork;
```
**افزوده شدن محاسبه تعادل زیرمجموعه:**
```csharp
// برای هر کاربر:
// 1. تعادل خودش
var directBalances = balance.TotalBalances;
// 2. تعادل زیرمجموعه (تا 15 لول)
var subordinateBalances = await CalculateSubordinateBalancesAsync(
balance.UserId,
request.WeekNumber,
maxLevels: 15
);
var totalBalancesForUser = directBalances + subordinateBalances;
```
**ایجاد UserCommissionPayout:**
```csharp
var payout = new UserCommissionPayout
{
UserId = balance.UserId,
WeekNumber = request.WeekNumber,
WeeklyPoolId = existingPool.Id,
BalancesEarned = totalBalancesForUser,
ValuePerBalance = valuePerBalance,
TotalAmount = totalBalancesForUser * valuePerBalance,
Status = CommissionPayoutStatus.Pending,
// ... subordinate fields
};
```
**ثبت تاریخچه:**
```csharp
var history = new CommissionPayoutHistory
{
UserId = payout.UserId,
PayoutId = payout.Id,
Amount = payout.TotalAmount,
Status = CommissionPayoutStatus.Pending,
ChangeReason = "محاسبه اولیه کمیسیون هفتگی"
};
```
---
### 4️⃣ ساده‌سازی `TriggerWeeklyCalculation`
**قبل:**
```csharp
// Step 1
await _mediator.Send(new CalculateWeeklyBalancesCommand { ... });
// Step 2
await _mediator.Send(new CalculateWeeklyCommissionPoolCommand { ... });
// Step 3
await _mediator.Send(new ProcessUserPayoutsCommand { ... });
```
**بعد:**
```csharp
// Step 1: محاسبه تعادل‌ها
if (!request.SkipBalances)
{
await _mediator.Send(new CalculateWeeklyBalancesCommand { ... });
}
// Step 2: محاسبه Pool و پرداخت‌ها
if (!request.SkipPayouts)
{
await _mediator.Send(new CalculateWeeklyCommissionPoolCommand { ... });
}
```
**حذف شد:**
-`SkipPool` flag
- ❌ Step 3 کاملاً حذف شد
---
## 🎯 فرآیند نهایی
### مرحله 1: محاسبه تعادل‌ها
```
1. برای هر کاربر در شبکه
2. تا 15 لول پایین‌تر شمارش کن
3. محاسبه تعادل (MIN of left/right)
4. محاسبه باقیمانده
5. محاسبه فلش
6. ذخیره در NetworkWeeklyBalance
```
### مرحله 2: محاسبه Pool و توزیع
```
1. شمارش فعالسازی‌های باشگاه این هفته
2. Pool = تعداد × ActivationFee
3. ارزش هر امتیاز = Pool ÷ مجموع تعادل‌ها
4. برای هر کاربر:
a. تعادل خودش + تعادل زیرمجموعه (تا 15 لول)
b. سهم = تعادل × ارزش
c. ثبت در UserCommissionPayout
d. ثبت تاریخچه
```
---
## 📊 جداول درگیر
### `NetworkWeeklyBalance` (فیلدهای جدید)
```sql
ALTER TABLE [Network].[NetworkWeeklyBalances]
ADD [FlushedPerSide] INT NOT NULL DEFAULT 0,
[TotalFlushed] INT NOT NULL DEFAULT 0;
```
### `WeeklyCommissionPool`
```
- TotalPoolAmount: از فعالسازی‌های باشگاه
- TotalBalances: مجموع تعادل‌های شبکه
- ValuePerBalance: Pool ÷ TotalBalances
```
### `UserCommissionPayout`
```
- BalancesEarned: تعادل خودش + زیرمجموعه
- DirectBalances: فقط تعادل خودش
- SubordinateBalances: فقط زیرمجموعه
- TotalAmount: BalancesEarned × ValuePerBalance
- Status: Pending
```
### `CommissionPayoutHistory`
```
- PayoutId: شناسه UserCommissionPayout
- Status: Pending (در این مرحله)
- ChangeReason: "محاسبه اولیه کمیسیون هفتگی"
```
---
## ✅ مزایا
1. **ساده‌تر**: 2 مرحله به جای 3
2. **بدون تکرار**: دیگر UserCommissionPayout دوبار ساخته نمیشه
3. **واضح‌تر**: Pool از کجا میاد مشخصه
4. **قابل نگهداری**: منطق مشابه یکجا هست
5. **کامل**: تاریخچه + subordinate balances همه جا هست
---
## 🔄 مراحل بعدی (اختیاری)
### مرحله 3: پرداخت واقعی (جدا از محاسبه)
می‌توان یک Command جدید داشت که:
1. `UserCommissionPayout` با status=Pending رو بخونه
2. به کیف پول واریز کنه
3. Status رو به Paid تغییر بده
4. تاریخچه اضافه کنه
این مرحله **جدا از محاسبات** است و می‌تواند:
- دستی توسط ادمین اجرا شود
- یا به صورت خودکار بعد از تایید
---
## 📝 نکات مهم
### Pool چطور پُر میشه؟
```
1. کاربر عضو Club میشه
2. در ActivateClubMembership مبلغی کسر میشه
3. این مبلغ به Pool اضافه **نمیشه** (فقط شمارش میشه)
4. در محاسبه Pool: تعداد × ActivationFee
```
### چرا subordinate balances؟
```
در سیستم باینری، کاربر از تعادل زیرمجموعه‌های خود
(تا 15 لول پایین‌تر) هم کمیسیون می‌گیرد.
```
### چرا 15 لول؟
```
محدودیت عمق برای جلوگیری از بارگذاری بیش از حد
و تشویق به ایجاد شبکه متعادل
```
---
## 🧪 تست
### تست مرحله 1
```csharp
// 1. ایجاد کاربران در شبکه
// 2. فعالسازی Club برای برخی
// 3. اجرای CalculateWeeklyBalances
// 4. بررسی NetworkWeeklyBalance
// - TotalBalances
// - FlushedPerSide
// - TotalFlushed
```
### تست مرحله 2
```csharp
// 1. اجرای مرحله 1
// 2. اجرای CalculateWeeklyCommissionPool
// 3. بررسی WeeklyCommissionPool
// - TotalPoolAmount = تعداد فعالسازی‌ها × ActivationFee
// - ValuePerBalance صحیح باشد
// 4. بررسی UserCommissionPayout
// - برای هر کاربر ایجاد شده
// - BalancesEarned شامل subordinate هم هست
// - TotalAmount = BalancesEarned × ValuePerBalance
// 5. بررسی CommissionPayoutHistory
// - برای هر پرداخت ثبت شده
```
---
## 📚 فایل‌های تغییر یافته
1.`NetworkWeeklyBalance.cs` - اضافه شدن فیلدها
2.`CalculateWeeklyBalancesCommandHandler.cs` - ذخیره فلش
3.`CalculateWeeklyCommissionPoolCommandHandler.cs` - منطق کامل جدید
4.`TriggerWeeklyCalculationCommandHandler.cs` - حذف مرحله 3
5.`TriggerWeeklyCalculationCommand.cs` - حذف SkipPool flag
6. ✅ Migration: `AddFlushedFieldsToNetworkWeeklyBalance`
---
## 🎉 نتیجه
سیستم کمیسیون هفتگی حالا:
-**ساده‌تر** و قابل فهم‌تر
-**بدون تکرار** در کد
-**Pool از منبع صحیح** (فعالسازی‌های Club)
-**تعادل زیرمجموعه** محاسبه میشه
-**تاریخچه کامل** ثبت میشه
-**فلش دقیق** ذخیره میشه
آماده برای استفاده در Production! 🚀