# ✅ اصلاح محاسبه کمیسیون هفتگی - تحلیل و پیاده‌سازی

**تاریخ شروع**: ۱۴ آذر ۱۴۰۴ (2025-12-04)  
**تاریخ تکمیل**: ۱۴ آذر ۱۴۰۴  
**وضعیت**: ✅ تکمیل شد  
**اولویت**: 🔴 بحرانی - تأثیر مستقیم بر بیزینس

---

## 📊 خلاصه مشکلات

### مشکل ۱: محدودیت لول (Max Network Level) پیاده‌سازی نشده
- **مشکل**: شمارش اعضا بدون محدودیت عمق انجام می‌شود
- **انتظار**: فقط تا ۱۵ لول پایین‌تر باید شمارش شود
- **راه‌حل**: اضافه کردن پارامتر `maxLevel` به متد بازگشتی و خواندن از Config

### مشکل ۲: تعادل شخص vs تعادل شبکه (بحرانی)
- **مشکل**: کمیسیون بر اساس تعادل شخصی محاسبه می‌شود (نه مجموع زیرمجموعه)
- **انتظار**: کمیسیون = (تعادل شخص + تعادل زیرمجموعه تا ۱۵ لول) × ارزش هر تعادل
- **راه‌حل**: محاسبه تعادل‌های زیرمجموعه در ProcessUserPayouts

---

## 🎯 قانون صحیح کمیسیون (بیزینس)

### فرمول محاسبه کمیسیون هفتگی:

```
1️⃣ محاسبه تعادل هر شخص:
   - تعادل_شخص = MIN(چپ، راست)
   - سقف هر دست = 300
   - حداکثر تعادل شخصی = 300

2️⃣ محاسبه کل تعادل‌های شبکه:
   - کل_تعادل_شبکه = SUM(تعادل_شخصی همه اعضا)

3️⃣ محاسبه صندوق:
   - صندوق_هفتگی = SUM(سهم_استخر همه اعضا)
   - سهم_استخر هر عضو = تعداد_زیرمجموعه_جدید × هزینه_فعال‌سازی × ۲۰%

4️⃣ ارزش هر تعادل:
   - ارزش_هر_تعادل = صندوق_هفتگی ÷ کل_تعادل_شبکه

5️⃣ کمیسیون هر شخص:
   - مجموع_تعادل = تعادل_شخص + SUM(تعادل_زیرمجموعه تا 15 لول)
   - کمیسیون = مجموع_تعادل × ارزش_هر_تعادل
```

### مثال عملی:

```
شبکه:
User A
├─ Left: User B (تعادل: 5)
│   ├─ Left: User D (تعادل: 2)
│   └─ Right: User E (تعادل: 1)
└─ Right: User C (تعادل: 3)
    └─ Left: User F (تعادل: 1)

فرض: تعادل شخصی User A = 10

محاسبه مجموع تعادل User A (تا 15 لول):
  = 10 + 5 + 2 + 1 + 3 + 1 = 22 تعادل

اگر ارزش هر تعادل = 1,000,000 ریال:
  کمیسیون User A = 22 × 1,000,000 = 22,000,000 ریال
```

---

## 🔍 تحلیل کد فعلی

### فایل‌های تأثیرپذیر:

| # | فایل | وضعیت فعلی | نیاز به تغییر |
|---|------|------------|---------------|
| 1 | `ApplicationDbContextInitialiser.cs` | ندارد `MaxNetworkLevel` | ✅ اضافه Config |
| 2 | `CalculateWeeklyBalancesCommandHandler.cs` | بدون محدودیت لول | ✅ اضافه maxLevel |
| 3 | `ProcessUserPayoutsCommandHandler.cs` | فقط تعادل شخص | ✅ جمع زیرمجموعه |
| 4 | `NetworkWeeklyBalance.cs` | Entity | ⚪ نیاز ندارد |
| 5 | `UserCommissionPayout.cs` | Entity | 🟡 شاید فیلد جدید |

### کد فعلی `ProcessUserPayoutsCommandHandler`:

```csharp
// ❌ مشکل: فقط تعادل شخصی
foreach (var balance in weeklyBalances)
{
    var totalAmount = (long)(balance.TotalBalances * pool.ValuePerBalance);
    // ...
}
```

### کد صحیح باید باشد:

```csharp
// ✅ صحیح: تعادل شخصی + زیرمجموعه تا 15 لول
foreach (var balance in weeklyBalances)
{
    // محاسبه مجموع تعادل‌های زیرمجموعه
    var subordinateBalances = await CalculateSubordinateBalances(
        balance.UserId, 
        request.WeekNumber,
        maxNetworkLevel, // از Config
        cancellationToken
    );
    
    var totalBalancesWithSubordinates = balance.TotalBalances + subordinateBalances;
    var totalAmount = (long)(totalBalancesWithSubordinates * pool.ValuePerBalance);
    // ...
}
```

---

## 📋 تسک‌های اجرایی

### فاز ۱: Configuration (نیم روز)

#### تسک ۱.۱: اضافه کردن MaxNetworkLevel به Seed Data
```csharp
// ApplicationDbContextInitialiser.cs
new SystemConfiguration
{
    Key = "Commission.MaxNetworkLevel",
    Value = "15",
    Description = "حداکثر عمق شبکه برای محاسبه کمیسیون (تعداد لول)",
    Scope = ConfigurationScope.Commission,
    IsActive = true
}
```

#### تسک ۱.۲: Migration (در صورت نیاز)
- اگر دیتابیس موجود دارید، یک SQL Script یا Migration

---

### فاز ۲: اصلاح CalculateWeeklyBalances (نیم روز)

#### تسک ۲.۱: خواندن MaxNetworkLevel از Config
```csharp
// در Handle method
var maxNetworkLevel = int.Parse(configs.GetValueOrDefault("Commission.MaxNetworkLevel", "15"));
```

#### تسک ۲.۲: اضافه کردن محدودیت لول به متد بازگشتی
```csharp
private async Task<int> CountNewMembersRecursive(
    long userId, 
    NetworkLeg leg, 
    DateTime startDate, 
    DateTime endDate, 
    int currentLevel,    // ← جدید
    int maxLevel,        // ← جدید
    CancellationToken cancellationToken)
{
    // ⬅️ محدودیت عمق
    if (currentLevel >= maxLevel) 
        return 0;

    var child = await _context.Users
        .FirstOrDefaultAsync(x => x.NetworkParentId == userId && x.LegPosition == leg, cancellationToken);

    if (child == null)
        return 0;

    // ... محاسبه count ...

    // ⬅️ افزایش سطح
    var childLeft = await CountNewMembersRecursive(child.Id, NetworkLeg.Left, startDate, endDate, currentLevel + 1, maxLevel, cancellationToken);
    var childRight = await CountNewMembersRecursive(child.Id, NetworkLeg.Right, startDate, endDate, currentLevel + 1, maxLevel, cancellationToken);

    return count + childLeft + childRight;
}
```

---

### فاز ۳: اصلاح ProcessUserPayouts (۱ روز)

#### تسک ۳.۱: اضافه کردن متد محاسبه تعادل زیرمجموعه
```csharp
/// <summary>
/// محاسبه مجموع تعادل‌های زیرمجموعه یک کاربر تا N لول
/// </summary>
private async Task<int> CalculateSubordinateBalancesAsync(
    long userId,
    string weekNumber,
    int maxLevel,
    CancellationToken cancellationToken)
{
    var totalSubordinateBalances = 0;
    
    // پیدا کردن همه زیرمجموعه‌ها تا maxLevel
    var subordinates = await GetSubordinatesRecursive(userId, 1, maxLevel, cancellationToken);
    
    // جمع تعادل‌های آنها
    foreach (var subordinateId in subordinates)
    {
        var balance = await _context.NetworkWeeklyBalances
            .Where(x => x.UserId == subordinateId && x.WeekNumber == weekNumber)
            .Select(x => x.TotalBalances)
            .FirstOrDefaultAsync(cancellationToken);
            
        totalSubordinateBalances += balance;
    }
    
    return totalSubordinateBalances;
}

/// <summary>
/// پیدا کردن بازگشتی زیرمجموعه‌ها
/// </summary>
private async Task<List<long>> GetSubordinatesRecursive(
    long userId, 
    int currentLevel, 
    int maxLevel, 
    CancellationToken cancellationToken)
{
    if (currentLevel > maxLevel)
        return new List<long>();
        
    var result = new List<long>();
    
    // پیدا کردن فرزندان مستقیم
    var children = await _context.Users
        .Where(x => x.NetworkParentId == userId)
        .Select(x => x.Id)
        .ToListAsync(cancellationToken);
        
    result.AddRange(children);
    
    // بازگشت برای هر فرزند
    foreach (var childId in children)
    {
        var grandChildren = await GetSubordinatesRecursive(childId, currentLevel + 1, maxLevel, cancellationToken);
        result.AddRange(grandChildren);
    }
    
    return result;
}
```

#### تسک ۳.۲: اصلاح Handle method
```csharp
public async Task<int> Handle(ProcessUserPayoutsCommand request, CancellationToken cancellationToken)
{
    // ... کدهای موجود ...
    
    // خواندن MaxNetworkLevel از Config
    var maxNetworkLevel = await _context.SystemConfigurations
        .Where(x => x.Key == "Commission.MaxNetworkLevel" && x.IsActive)
        .Select(x => x.Value)
        .FirstOrDefaultAsync(cancellationToken);
    var maxLevel = int.Parse(maxNetworkLevel ?? "15");

    foreach (var balance in weeklyBalances)
    {
        // ✅ محاسبه تعادل شخص + زیرمجموعه
        var subordinateBalances = await CalculateSubordinateBalancesAsync(
            balance.UserId, 
            request.WeekNumber,
            maxLevel,
            cancellationToken
        );
        
        var totalBalancesWithSubordinates = balance.TotalBalances + subordinateBalances;
        var totalAmount = (long)(totalBalancesWithSubordinates * pool.ValuePerBalance);

        var payout = new UserCommissionPayout
        {
            UserId = balance.UserId,
            WeekNumber = request.WeekNumber,
            WeeklyPoolId = pool.Id,
            BalancesEarned = totalBalancesWithSubordinates, // ← شامل زیرمجموعه
            ValuePerBalance = pool.ValuePerBalance,
            TotalAmount = totalAmount,
            // ...
        };
        // ...
    }
}
```

#### تسک ۳.۳ (اختیاری): اضافه کردن فیلد به Entity
```csharp
// UserCommissionPayout.cs
/// <summary>
/// تعادل شخصی (بدون زیرمجموعه)
/// </summary>
public int PersonalBalances { get; set; }

/// <summary>
/// تعادل زیرمجموعه‌ها
/// </summary>
public int SubordinateBalances { get; set; }

/// <summary>
/// مجموع (PersonalBalances + SubordinateBalances)
/// </summary>
public int BalancesEarned { get; set; } // ← قبلاً هم بود
```

---

### فاز ۴: تست و Build (نیم روز)

#### تسک ۴.۱: Build و رفع خطاها
```bash
cd CMS/src && dotnet build
```

#### تسک ۴.۲: تست با سناریوهای مختلف
- کاربر بدون زیرمجموعه
- کاربر با ۵ لول زیرمجموعه
- کاربر با ۲۰ لول (باید ۱۵ تا بشمارد)
- کاربر با سقف ۳۰۰ در هر دست

---

## ⏱️ زمان‌بندی

| فاز | تسک | زمان | مجموع |
|-----|-----|------|-------|
| ۱ | Config + Seed | 0.5 روز | 0.5 روز |
| ۲ | اصلاح CalculateWeeklyBalances | 0.5 روز | 1 روز |
| ۳ | اصلاح ProcessUserPayouts | 1 روز | 2 روز |
| ۴ | تست و Build | 0.5 روز | 2.5 روز |

**مجموع**: ۲.۵ روز کاری

---

## ⚠️ نکات مهم

1. **تغییرات Breaking نیست**: ساختار Entity تغییر نمی‌کند (فقط مقادیر)
2. **Backward Compatible**: فیلد `BalancesEarned` قبلاً هم بود
3. **Idempotent**: با `ForceRecalculate` می‌توان دوباره حساب کرد
4. **Performance**: متد بازگشتی ممکن است کند باشد - بهینه‌سازی در فاز بعد
5. **Migration**: فقط اگر فیلد جدید به Entity اضافه شود

---

## ✅ وضعیت پیاده‌سازی - تکمیل شده

**تاریخ تکمیل**: ۱۴ آذر ۱۴۰۴

| فاز | شرح | وضعیت |
|-----|-----|------|
| 1 | Config: `Commission.MaxNetworkLevel = 15` | ✅ تکمیل |
| 2 | CalculateWeeklyBalances: محدودیت ۱۵ لول | ✅ تکمیل |
| 3 | ProcessUserPayouts: جمع تعادل زیرمجموعه | ✅ تکمیل |
| 4 | Build Test: 0 Errors | ✅ تکمیل |

### تغییرات انجام شده:

**Seed Data:**
- ✅ `Commission.MaxNetworkLevel = 15`

**CalculateWeeklyBalancesCommandHandler:**
- ✅ خواندن `maxNetworkLevel` از Config
- ✅ پارامتر `maxLevel` به `CountNewMembersInLeg`
- ✅ پارامتر `currentLevel` و `maxLevel` به `CountNewMembersRecursive`
- ✅ شرط توقف در عمق ۱۵

**ProcessUserPayoutsCommandHandler:**
- ✅ متد جدید `SumSubordinateBalancesAsync`
- ✅ متد کمکی `GetChildUserIdAsync`
- ✅ محاسبه `subordinateBalances` برای هر کاربر
- ✅ کمیسیون = (شخص + زیرمجموعه) × ارزش هر تعادل

---

## 🎉 نتیجه نهایی

```
✅ Build Succeeded - 0 Errors
✅ همه فازها تکمیل شدند
✅ منطق کمیسیون اصلاح شد
```