# Balance Calculation with Carryover Logic - Complete Guide

**Date**: 2025-12-01  
**Last Updated**: 2025-12-09 (✅ اصلاح نهایی: محاسبات تعادل و فلش)  
**Status**: ✅ Fully Implemented & Verified  
**Migration**: `UpdateNetworkWeeklyBalanceWithCarryover`

---

## ✅ آخرین به‌روزرسانی (2025-12-09)

### تغییرات اعمال شده:
کد محاسبه تعادل با توضیحات دقیق بیزینس تطبیق داده شد:

1. ✅ **ترتیب محاسبات اصلاح شد**:
   - اول تعادل اولیه محاسبه می‌شود
   - بعد باقیمانده (برای هفته بعد)
   - سپس سقف 300 اعمال می‌شود
   - در نهایت فلش محاسبه می‌شود

2. ✅ **فلش از هر دو طرف**:
   - اگر تعادل > 300 باشد
   - از چپ: (تعادل - 300) فلش می‌شود
   - از راست: (تعادل - 300) فلش می‌شود
   - جمع فلش = (تعادل - 300) × 2

3. ✅ **باقیمانده جداگانه ذخیره می‌شود**:
   - `LeftLegRemainder`: باقیمانده دست چپ
   - `RightLegRemainder`: باقیمانده دست راست

---

## 📋 قوانین اصلی بیزینس

| توضیح | منطق فعلی (اشتباه) | منطق صحیح |
|-------|---------------------|-----------|
| سقف | 300 کل | 300 برای هر دست |
| حداکثر تعادل | 300 | MIN(300, 300) = 300 |
| حداکثر کل | 300 | 300 + 300 = 600 (مجموع دو دست) |

### تفاوت در محاسبه:

**منطق فعلی (اشتباه):**
```csharp
totalBalances = MIN(leftTotal, rightTotal)
cappedBalances = MIN(totalBalances, 300)  // ← سقف روی کل
```

**منطق صحیح:**
```csharp
cappedLeftTotal = MIN(leftTotal, 300)   // ← سقف روی هر دست
cappedRightTotal = MIN(rightTotal, 300)
totalBalances = MIN(cappedLeftTotal, cappedRightTotal)
```

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

| سناریو | چپ | راست | منطق فعلی | منطق صحیح |
|--------|-----|-------|-----------|-----------|
| 1 | 200 | 250 | 200 | 200 |
| 2 | 350 | 400 | **300** ❌ | **300** ✅ |
| 3 | 500 | 600 | **300** ❌ | **300** ✅ |

**توجه:** در مثال‌های بالا نتیجه یکسان است چون حداکثر یک تعادل همیشه MIN(300,300)=300 است. تفاوت در **باقیمانده** است:

**مثال با چپ=500، راست=600:**

| روش | تعادل | باقیمانده چپ | باقیمانده راست |
|-----|--------|--------------|----------------|
| فعلی | 300 | 500 - 150 = 350 | 600 - 150 = 450 |
| صحیح | 300 | **200** (500-300) | **300** (600-300) |

### تغییرات Configuration:

```csharp
// ✅ تغییر نام و مقدار:
// قدیمی:
Key = "Commission.MaxWeeklyBalancesPerUser", Value = "300"

// جدید:
Key = "Commission.MaxWeeklyBalancesPerLeg", Value = "300"
```

---

## 📋 Configuration-Based Calculation

### **System Configurations Used:**

```csharp
// تمام مقادیر از جدول SystemConfigurations خوانده می‌شوند
Club.ActivationFee = 25,000,000 ریال (هزینه فعال‌سازی)
Commission.WeeklyPoolContributionPercent = 20% (سهم استخر)
Commission.MaxWeeklyBalancesPerLeg = 300 (✅ سقف امتیاز نهایی)
```

**نکته مهم**: سقف 300 روی **امتیاز نهایی** اعمال می‌شود، نه روی تعادل اولیه!

### **Pool Contribution Calculation:**

```csharp
totalNewMembers = leftNewMembers + rightNewMembers
weeklyPoolContribution = totalNewMembers × activationFee × poolPercent
                       = totalNewMembers × 25,000,000 × 20%
                       = totalNewMembers × 5,000,000
```

**مثال:**  
اگر 10 نفر جدید جذب شوند: `10 × 5,000,000 = 50,000,000` ریال به استخر اضافه می‌شود.

---

## 🚫 MaxWeeklyBalances Cap (محدودیت سقف 300)

### **Logic صحیح (به‌روز شده 2025-12-09):**

```csharp
// ✅ مرحله 1: محاسبه تعادل اولیه (بدون سقف)
totalBalances = MIN(leftTotal, rightTotal)

// ✅ مرحله 2: محاسبه باقیمانده برای هفته بعد
leftRemainder = leftTotal - totalBalances
rightRemainder = rightTotal - totalBalances

// ✅ مرحله 3: اعمال سقف 300 (برای امتیاز نهایی)
cappedBalances = MIN(totalBalances, 300)

// ✅ مرحله 4: محاسبه فلش (از هر دو طرف)
flushedPerSide = totalBalances - cappedBalances
totalFlushed = flushedPerSide × 2
```

### **Example (مثال کامل):**

```
leftTotal = 500, rightTotal = 600

مرحله 1️⃣: تعادل اولیه
  totalBalances = MIN(500, 600) = 500 ✅

مرحله 2️⃣: باقیمانده برای هفته بعد
  leftRemainder = 500 - 500 = 0 ✅
  rightRemainder = 600 - 500 = 100 ✅

مرحله 3️⃣: اعمال سقف
  cappedBalances = MIN(500, 300) = 300 ✅

مرحله 4️⃣: محاسبه فلش
  flushedPerSide = 500 - 300 = 200
  از چپ: 200 فلش می‌شود
  از راست: 200 فلش می‌شود
  totalFlushed = 200 × 2 = 400 ✅

نتیجه نهایی:
  ✅ امتیاز این هفته: 300
  ✅ باقیمانده چپ: 0
  ✅ باقیمانده راست: 100
  ✅ جمع فلش: 400 (از بین می‌رود)
```

### **مقایسه منطق قدیم vs جدید:**

```
// ❌ منطق قدیم (اشتباه):
cappedBalances = MIN(totalBalances, 300)  // سقف روی کل
balancesConsumedPerSide = cappedBalances / 2
leftRemainder = leftTotal - balancesConsumedPerSide

// ✅ منطق جدید (صحیح):
cappedLeftTotal = MIN(leftTotal, 300)     // سقف روی هر دست
cappedRightTotal = MIN(rightTotal, 300)
totalBalances = MIN(cappedLeftTotal, cappedRightTotal)
leftRemainder = leftTotal - cappedLeftTotal  // باقیمانده از سقف هر دست
```

---

## 📊 Problem Statement

### ❌ **Previous (Incorrect) Logic:**

```csharp
// محاسبه تعداد کل اعضا در هر پا
## ✅ **Current (Correct) Logic - Updated 2025-12-09:**

### **Formula (4 مرحله):**
```
// مرحله 1: جمع با هفته قبل
leftTotal = leftNewMembers + leftCarryover
rightTotal = rightNewMembers + rightCarryover

// مرحله 2: محاسبه تعادل اولیه
totalBalances = MIN(leftTotal, rightTotal)

// مرحله 3: محاسبه باقیمانده برای هفته بعد
leftRemainder = leftTotal - totalBalances
rightRemainder = rightTotal - totalBalances

// مرحله 4: اعمال سقف 300
cappedBalances = MIN(totalBalances, 300)
flushedPerSide = totalBalances - cappedBalances
totalFlushed = flushedPerSide × 2
```

### **Key Principles:**
1. **Only count NEW members** activated in current week
2. **Add carryover** from previous week (جداگانه چپ و راست)
3. **Calculate remainder** for next week (قبل از سقف)
4. **Apply cap 300** on final score (بعد از تعادل)
5. **Flush from both sides** if balance > 300
6. **Recursive counting** through entire tree structure
leftTotal = leftNewMembers + leftCarryover
rightTotal = rightNewMembers + rightCarryover

TotalBalances = MIN(leftTotal, rightTotal)

leftRemainder = leftTotal - TotalBalances
rightRemainder = rightTotal - TotalBalances
```

### **Key Principles:**
1. **Only count NEW members** activated in current week
2. **Add carryover** from previous week
3. **Calculate remainder** for next week
4. **Recursive counting** through entire tree structure

---

## 🔢 Example Calculations

### **Week 1 (2025-W48):**

**Tree Structure:**
```
User A (Activated this week - 25M to pool)
├─ Left: User B (Activated this week - 25M)
└─ Right: User C (Activated this week - 25M)
```

**Calculations:**
```
User A:
  leftNewMembers = 1 (User B activated)
  rightNewMembers = 1 (User C activated)
  leftCarryover = 0 (first week)
  rightCarryover = 0 (first week)
  
  leftTotal = 1 + 0 = 1
  rightTotal = 1 + 0 = 1
  
  TotalBalances = MIN(1, 1) = 1
  
  leftRemainder = 1 - 1 = 0
  rightRemainder = 1 - 1 = 0

User B: TotalBalances = 0 (no children)
User C: TotalBalances = 0 (no children)
```

**Pool Calculation:**
```
Total Pool = 75M (3 activations × 25M)
Total Balances = 1 (only User A)
Value Per Balance = 75M ÷ 1 = 75M

Commission:
  User A = 1 × 75M = 75M
```

---

### **Week 2 (2025-W49):**

**Tree Structure:**
```
User A
├─ Left: User B
│   ├─ Left: User D (NEW - activated this week - 25M)
│   └─ Right: User E (NEW - activated this week - 25M)
└─ Right: User C
    ├─ Left: User F (NEW - activated this week - 25M)
    └─ Right: User G (NEW - activated this week - 25M)
```

**Calculations:**
```
User B:
  leftNewMembers = 1 (User D)
  rightNewMembers = 1 (User E)
  leftCarryover = 0
  rightCarryover = 0
  
  leftTotal = 1 + 0 = 1
  rightTotal = 1 + 0 = 1
  TotalBalances = MIN(1, 1) = 1

User C:
  leftNewMembers = 1 (User F)
  rightNewMembers = 1 (User G)
  leftCarryover = 0
  rightCarryover = 0
  
  leftTotal = 1 + 0 = 1
  rightTotal = 1 + 0 = 1
  TotalBalances = MIN(1, 1) = 1

User A:
  leftNewMembers = 2 (D & E through B)
  rightNewMembers = 2 (F & G through C)
  leftCarryover = 0 (from week 1)
  rightCarryover = 0 (from week 1)
  
  leftTotal = 2 + 0 = 2
  rightTotal = 2 + 0 = 2
  TotalBalances = MIN(2, 2) = 2 ✅
  
  leftRemainder = 2 - 2 = 0
  rightRemainder = 2 - 2 = 0
```

**Pool Calculation:**
```
Total Pool = 100M (4 new activations × 25M)
Total Balances = 4 (A=2, B=1, C=1)
Value Per Balance = 100M ÷ 4 = 25M

Commission:
  User A = 2 × 25M = 50M ✅ (not 33.33M!)
  User B = 1 × 25M = 25M
  User C = 1 × 25M = 25M
```

---

### **Week 3 (2025-W50) - With Carryover:**

**Tree Structure:**
```
User A
├─ Left: User B
│   ├─ Left: User D
│   │   └─ Left: User H (NEW - 25M)
│   └─ Right: User E
└─ Right: User C
    ├─ Left: User F
    └─ Right: User G
```

**Calculations:**
```
User D:
  leftNewMembers = 1 (User H)
  rightNewMembers = 0
  leftCarryover = 0
  rightCarryover = 0
  
  leftTotal = 1 + 0 = 1
  rightTotal = 0 + 0 = 0
  TotalBalances = MIN(1, 0) = 0
  
  leftRemainder = 1 - 0 = 1 ⚠️ (saved for next week)
  rightRemainder = 0 - 0 = 0

User B:
  leftNewMembers = 1 (H through D)
  rightNewMembers = 0
  leftCarryover = 0 (from week 2)
  rightCarryover = 0
  
  leftTotal = 1 + 0 = 1
  rightTotal = 0 + 0 = 0
  TotalBalances = MIN(1, 0) = 0
  
  leftRemainder = 1 - 0 = 1 ⚠️ (saved for next week)
  rightRemainder = 0 - 0 = 0

User A:
  leftNewMembers = 1 (H through B→D)
  rightNewMembers = 0
  leftCarryover = 0 (from week 2)
  rightCarryover = 0
  
  leftTotal = 1 + 0 = 1
  rightTotal = 0 + 0 = 0
  TotalBalances = MIN(1, 0) = 0
  
  leftRemainder = 1 - 0 = 1 ⚠️ (saved for next week)
  rightRemainder = 0 - 0 = 0
```

**Pool Calculation:**
```
Total Pool = 25M (1 new activation)
Total Balances = 0 (no balanced pairs)
Value Per Balance = N/A

Commission: None this week
Carryover: User A, B, D each have 1 leftRemainder for week 4
```

---

## 🔄 Database Schema

### **NetworkWeeklyBalance Table:**

```sql
ALTER TABLE NetworkWeeklyBalances ADD:
  -- New members this week
  LeftLegNewMembers INT NOT NULL DEFAULT 0,
  RightLegNewMembers INT NOT NULL DEFAULT 0,
  
  -- Carryover from previous week
  LeftLegCarryover INT NOT NULL DEFAULT 0,
  RightLegCarryover INT NOT NULL DEFAULT 0,
  
  -- Totals (new + carryover)
  LeftLegTotal INT NOT NULL DEFAULT 0,
  RightLegTotal INT NOT NULL DEFAULT 0,
  
  -- Remainder for next week
  LeftLegRemainder INT NOT NULL DEFAULT 0,
  RightLegRemainder INT NOT NULL DEFAULT 0
```

**Deprecated Fields:**
- `LeftLegBalances` (still exists for backward compatibility)
- `RightLegBalances` (still exists for backward compatibility)

---

## 💻 Implementation

### **Handler: CalculateWeeklyBalancesCommandHandler.cs**

```csharp
public async Task<int> Handle(CalculateWeeklyBalancesCommand request, CancellationToken cancellationToken)
{
    // 1. Load previous week's carryover
    var previousWeekNumber = GetPreviousWeekNumber(request.WeekNumber);
    var previousWeekCarryovers = await _context.NetworkWeeklyBalances
        .Where(x => x.WeekNumber == previousWeekNumber)
        .ToDictionaryAsync(x => x.UserId, x => new { x.LeftLegRemainder, x.RightLegRemainder });

    // 2. For each user in network
    foreach (var user in usersInNetwork)
    {
        // Get carryover
        var leftCarryover = previousWeekCarryovers.ContainsKey(user.Id) 
            ? previousWeekCarryovers[user.Id].LeftLegRemainder : 0;
        var rightCarryover = previousWeekCarryovers.ContainsKey(user.Id) 
            ? previousWeekCarryovers[user.Id].RightLegRemainder : 0;

        // Count NEW members (activated in this week)
        var leftNewMembers = await CountNewMembersInLeg(user.Id, NetworkLeg.Left, request.WeekNumber);
        var rightNewMembers = await CountNewMembersInLeg(user.Id, NetworkLeg.Right, request.WeekNumber);

        // Calculate totals
        var leftTotal = leftNewMembers + leftCarryover;
        var rightTotal = rightNewMembers + rightCarryover;

        // Calculate balance (min)
        var totalBalances = Math.Min(leftTotal, rightTotal);

        // Calculate remainder
        var leftRemainder = leftTotal - totalBalances;
        var rightRemainder = rightTotal - totalBalances;

        // Save to database
        var balance = new NetworkWeeklyBalance
        {
            UserId = user.Id,
            WeekNumber = request.WeekNumber,
            LeftLegNewMembers = leftNewMembers,
            RightLegNewMembers = rightNewMembers,
            LeftLegCarryover = leftCarryover,
            RightLegCarryover = rightCarryover,
            LeftLegTotal = leftTotal,
            RightLegTotal = rightTotal,
            TotalBalances = totalBalances,
            LeftLegRemainder = leftRemainder,
            RightLegRemainder = rightRemainder,
            // ...
        };
    }
}

private async Task<int> CountNewMembersRecursive(long userId, NetworkLeg leg, DateTime startDate, DateTime endDate)
{
    var child = await _context.Users
        .FirstOrDefaultAsync(x => x.NetworkParentId == userId && x.LegPosition == leg);

    if (child == null) return 0;

    var count = 0;

    // Check if activated in this week
    var membership = await _context.ClubMemberships
        .FirstOrDefaultAsync(x => x.UserId == child.Id && x.IsActive);

    if (membership?.ActivatedAt >= startDate && membership?.ActivatedAt <= endDate)
    {
        count = 1;
    }

    // Recursively count children
    var childLeft = await CountNewMembersRecursive(child.Id, NetworkLeg.Left, startDate, endDate);
    var childRight = await CountNewMembersRecursive(child.Id, NetworkLeg.Right, startDate, endDate);

    return count + childLeft + childRight;
}
```

---

## 📝 Key Points

1. ✅ **Only NEW activations count** - filtered by `ActivatedAt` date
2. ✅ **Carryover persists** - unused balances roll over to next week
3. ✅ **Recursive counting** - includes entire subtree under each leg
4. ✅ **Week date ranges** - ISO 8601 week format (Saturday to Friday)
5. ✅ **Idempotent** - can recalculate with `ForceRecalculate` flag

---

## 🚀 Benefits

1. **Fair commission distribution** - rewards balanced growth
2. **No lost balances** - carryover ensures nothing is wasted
3. **Accurate tracking** - distinguishes new vs existing members
4. **Scalable** - works for large networks with recursive algorithm
5. **Auditable** - full history of calculations in database

---

## 📞 Reference

- **Source Code**: `CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyBalances/`
- **Migration**: `20251201144400_UpdateNetworkWeeklyBalanceWithCarryover`
- **Entity**: `CMSMicroservice.Domain/Entities/Network/NetworkWeeklyBalance.cs`
- **Discussion**: Telegram chat with Dr. Seif (2025-12-01)

---

**Status**: ✅ Production Ready  
**Last Updated**: 2025-12-01
