1485 lines
45 KiB
Markdown
1485 lines
45 KiB
Markdown
# 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
|
||
|
||
|
||
---
|
||
|
||
# ضمیمه: مثالهای محاسبه ۵ سطحی
|
||
|
||
# 📊 مثالهای عملی محاسبه تعادل - 5 لول عمقی
|
||
|
||
**تاریخ**: 2025-12-09
|
||
**وضعیت**: مثالهای کامل و تایید شده
|
||
**هدف**: نمایش محاسبات واقعی برای درخت باینری تا 5 لول
|
||
|
||
---
|
||
|
||
## 🌳 ساختار درخت نمونه
|
||
|
||
```
|
||
User1 (Level 0)
|
||
/ \
|
||
User2 (L1-L) User3 (L1-R)
|
||
/ \ / \
|
||
User4(L2-LL) User5(L2-LR) User6(L2-RL) User7(L2-RR)
|
||
/ \ / \ / \ / \
|
||
U8(L3) U9(L3) U10(L3) U11(L3) U12(L3) U13(L3) U14(L3) U15(L3)
|
||
/ \ / \ / \ / \ / \ / \ / \ / \
|
||
U16-U31 (Level 4 - 16 users)
|
||
/\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\
|
||
U32-U63 (Level 5 - 32 users)
|
||
```
|
||
|
||
---
|
||
|
||
## 📋 دادههای ورودی
|
||
|
||
### فرضیات:
|
||
- **هفته فعلی**: 2025-W50
|
||
- **سقف امتیاز**: 300
|
||
- **تعداد کل کاربران**: 63 نفر (6 لول: 1+2+4+8+16+32)
|
||
- **وضعیت**: همه کاربران فعال هستند (عضو باشگاه)
|
||
|
||
---
|
||
|
||
## 🎯 محاسبات Level 5 (پایینترین سطح)
|
||
|
||
### User 32-63 (32 کاربر Leaf):
|
||
```
|
||
هیچ زیرمجموعهای ندارند
|
||
چپ = 0، راست = 0
|
||
تعادل = MIN(0, 0) = 0
|
||
امتیاز = 0
|
||
باقیمانده چپ = 0
|
||
باقیمانده راست = 0
|
||
فلش = 0
|
||
```
|
||
|
||
**خلاصه Level 5**: تمام 32 کاربر → 0 امتیاز
|
||
|
||
---
|
||
|
||
## 🎯 محاسبات Level 4 (User 16-31)
|
||
|
||
### User 16:
|
||
**زیرمجموعه**:
|
||
- چپ: User 32 (1 نفر)
|
||
- راست: User 33 (1 نفر)
|
||
|
||
**محاسبات**:
|
||
```
|
||
چپ = 1، راست = 1
|
||
تعادل اولیه = MIN(1, 1) = 1
|
||
باقیمانده چپ = 1 - 1 = 0
|
||
باقیمانده راست = 1 - 1 = 0
|
||
امتیاز نهایی = MIN(1, 300) = 1 ✅
|
||
فلش = 0
|
||
```
|
||
|
||
### User 17:
|
||
**زیرمجموعه**:
|
||
- چپ: User 34 (1 نفر)
|
||
- راست: User 35 (1 نفر)
|
||
|
||
**محاسبات**: مشابه User 16
|
||
```
|
||
امتیاز = 1 ✅
|
||
```
|
||
|
||
### User 18-31 (14 کاربر دیگه):
|
||
همه مشابه User 16 → هر کدام 1 امتیاز
|
||
|
||
**خلاصه Level 4**: تمام 16 کاربر → هر کدام 1 امتیاز = **16 امتیاز**
|
||
|
||
---
|
||
|
||
## 🎯 محاسبات Level 3 (User 8-15)
|
||
|
||
### User 8:
|
||
**زیرمجموعه**:
|
||
- چپ: User 16 (1 نفر)
|
||
- راست: User 17 (1 نفر)
|
||
|
||
**محاسبات**:
|
||
```
|
||
چپ = 1، راست = 1
|
||
تعادل = MIN(1, 1) = 1
|
||
امتیاز = 1 ✅
|
||
```
|
||
|
||
### User 9:
|
||
**زیرمجموعه**:
|
||
- چپ: User 18 (1 نفر)
|
||
- راست: User 19 (1 نفر)
|
||
|
||
**محاسبات**: مشابه User 8
|
||
```
|
||
امتیاز = 1 ✅
|
||
```
|
||
|
||
### User 10-15 (6 کاربر دیگه):
|
||
همه مشابه → هر کدام 1 امتیاز
|
||
|
||
**خلاصه Level 3**: تمام 8 کاربر → هر کدام 1 امتیاز = **8 امتیاز**
|
||
|
||
---
|
||
|
||
## 🎯 محاسبات Level 2 (User 4-7)
|
||
|
||
### User 4:
|
||
**زیرمجموعه**:
|
||
- چپ: User 8 (1 نفر)
|
||
- راست: User 9 (1 نفر)
|
||
|
||
**محاسبات**:
|
||
```
|
||
چپ = 1، راست = 1
|
||
تعادل = MIN(1, 1) = 1
|
||
امتیاز = 1 ✅
|
||
```
|
||
|
||
### User 5:
|
||
**زیرمجموعه**:
|
||
- چپ: User 10 (1 نفر)
|
||
- راست: User 11 (1 نفر)
|
||
|
||
**محاسبات**: مشابه User 4
|
||
```
|
||
امتیاز = 1 ✅
|
||
```
|
||
|
||
### User 6, 7:
|
||
همه مشابه → هر کدام 1 امتیاز
|
||
|
||
**خلاصه Level 2**: تمام 4 کاربر → هر کدام 1 امتیاز = **4 امتیاز**
|
||
|
||
---
|
||
|
||
## 🎯 محاسبات Level 1 (User 2-3)
|
||
|
||
### User 2:
|
||
**زیرمجموعه**:
|
||
- چپ: User 4 (1 نفر)
|
||
- راست: User 5 (1 نفر)
|
||
|
||
**محاسبات**:
|
||
```
|
||
چپ = 1، راست = 1
|
||
تعادل = MIN(1, 1) = 1
|
||
امتیاز = 1 ✅
|
||
```
|
||
|
||
### User 3:
|
||
**زیرمجموعه**:
|
||
- چپ: User 6 (1 نفر)
|
||
- راست: User 7 (1 نفر)
|
||
|
||
**محاسبات**: مشابه User 2
|
||
```
|
||
امتیاز = 1 ✅
|
||
```
|
||
|
||
**خلاصه Level 1**: تمام 2 کاربر → هر کدام 1 امتیاز = **2 امتیاز**
|
||
|
||
---
|
||
|
||
## 🎯 محاسبات Level 0 (User 1 - Root)
|
||
|
||
### User 1:
|
||
**زیرمجموعه**:
|
||
- چپ: User 2 (1 نفر)
|
||
- راست: User 3 (1 نفر)
|
||
|
||
**محاسبات**:
|
||
```
|
||
چپ = 1، راست = 1
|
||
تعادل = MIN(1, 1) = 1
|
||
امتیاز = 1 ✅
|
||
```
|
||
|
||
**خلاصه Level 0**: User 1 → **1 امتیاز**
|
||
|
||
---
|
||
|
||
## 📊 جمع کل سیستم
|
||
|
||
| Level | تعداد کاربران | امتیاز هر کاربر | جمع امتیازهای Level |
|
||
|-------|---------------|-----------------|---------------------|
|
||
| 5 | 32 | 0 | 0 |
|
||
| 4 | 16 | 1 | 16 |
|
||
| 3 | 8 | 1 | 8 |
|
||
| 2 | 4 | 1 | 4 |
|
||
| 1 | 2 | 1 | 2 |
|
||
| 0 | 1 | 1 | 1 |
|
||
| **جمع** | **63** | - | **31 امتیاز** |
|
||
|
||
---
|
||
|
||
## 💰 محاسبه صندوق
|
||
|
||
### دادههای ورودی:
|
||
```
|
||
تعداد کاربران فعال شده این هفته: 63 نفر
|
||
هزینه فعالسازی هر نفر: 25,000,000 ریال
|
||
درصد سهم استخر: 20%
|
||
|
||
جمع ورودی استخر = 63 × 25,000,000 × 20%
|
||
= 63 × 5,000,000
|
||
= 315,000,000 ریال
|
||
```
|
||
|
||
### محاسبه ارزش هر امتیاز:
|
||
```
|
||
مجموع امتیازهای سیستم = 31
|
||
جمع استخر = 315,000,000 ریال
|
||
|
||
ارزش هر امتیاز = 315,000,000 ÷ 31
|
||
= 10,161,290 ریال (تقریباً)
|
||
```
|
||
|
||
### توزیع کمیسیون:
|
||
```
|
||
User 1: 1 × 10,161,290 = 10,161,290 ریال
|
||
User 2: 1 × 10,161,290 = 10,161,290 ریال
|
||
User 3: 1 × 10,161,290 = 10,161,290 ریال
|
||
User 4-7: 4 × 10,161,290 = 40,645,160 ریال
|
||
User 8-15: 8 × 10,161,290 = 81,290,320 ریال
|
||
User 16-31: 16 × 10,161,290 = 162,580,640 ریال
|
||
User 32-63: 0 ریال (امتیازی ندارند)
|
||
|
||
جمع کل پرداختی = 315,000,000 ریال ✅
|
||
```
|
||
|
||
---
|
||
|
||
## 🔥 مثال پیچیدهتر: سناریو نامتعادل
|
||
|
||
### تغییر ساختار:
|
||
```
|
||
User 1:
|
||
چپ: 500 نفر (عمق زیاد)
|
||
راست: 600 نفر (عمق بیشتر)
|
||
```
|
||
|
||
### محاسبات User 1:
|
||
```
|
||
مرحله 1️⃣: تعادل اولیه
|
||
چپ = 500، راست = 600
|
||
تعادل = MIN(500, 600) = 500
|
||
|
||
مرحله 2️⃣: باقیمانده
|
||
باقی چپ = 500 - 500 = 0
|
||
باقی راست = 600 - 500 = 100 → هفته بعد
|
||
|
||
مرحله 3️⃣: اعمال سقف
|
||
امتیاز = MIN(500, 300) = 300 ✅
|
||
|
||
مرحله 4️⃣: فلش
|
||
فلش از چپ = 500 - 300 = 200
|
||
فلش از راست = 500 - 300 = 200
|
||
جمع فلش = 400 (از بین میرود)
|
||
```
|
||
|
||
### نتیجه:
|
||
```
|
||
✅ امتیاز User 1: 300
|
||
✅ باقیمانده راست: 100 (میرود هفته بعد)
|
||
✅ باقیمانده چپ: 0
|
||
✅ فلش شده: 400 (از بین رفته)
|
||
```
|
||
|
||
---
|
||
|
||
## 🔄 مثال با Carryover (هفته بعد)
|
||
|
||
### فرض: User 1 در هفته 2025-W51:
|
||
```
|
||
باقیمانده هفته قبل:
|
||
چپ: 0
|
||
راست: 100
|
||
|
||
جدیدهای این هفته:
|
||
چپ: 250
|
||
راست: 150
|
||
```
|
||
|
||
### محاسبات:
|
||
```
|
||
مرحله 1️⃣: جمع با هفته قبل
|
||
چپ کل = 0 + 250 = 250
|
||
راست کل = 100 + 150 = 250
|
||
|
||
مرحله 2️⃣: تعادل
|
||
تعادل = MIN(250, 250) = 250
|
||
|
||
مرحله 3️⃣: باقیمانده
|
||
باقی چپ = 250 - 250 = 0
|
||
باقی راست = 250 - 250 = 0
|
||
|
||
مرحله 4️⃣: امتیاز
|
||
امتیاز = MIN(250, 300) = 250 ✅
|
||
|
||
مرحله 5️⃣: فلش
|
||
فلش = 0 (چون 250 < 300)
|
||
```
|
||
|
||
---
|
||
|
||
## 📈 مثال سقف: User با شبکه بزرگ
|
||
|
||
### User A:
|
||
```
|
||
چپ: 800 نفر
|
||
راست: 900 نفر
|
||
```
|
||
|
||
### محاسبات:
|
||
```
|
||
تعادل = MIN(800, 900) = 800
|
||
باقی چپ = 800 - 800 = 0
|
||
باقی راست = 900 - 800 = 100
|
||
|
||
امتیاز = MIN(800, 300) = 300 ✅
|
||
|
||
فلش:
|
||
از چپ: 800 - 300 = 500
|
||
از راست: 800 - 300 = 500
|
||
جمع: 1000 (از بین میرود)
|
||
```
|
||
|
||
**نتیجه**: حتی با 800 تعادل، فقط **300 امتیاز** میگیرد!
|
||
|
||
---
|
||
|
||
## 🎯 جمعبندی قوانین
|
||
|
||
### ✅ قوانین کلیدی:
|
||
1. **تعادل** = MIN(چپ، راست)
|
||
2. **باقیمانده** = طرفی که بیشتر است (قبل از سقف)
|
||
3. **امتیاز** = MIN(تعادل، 300)
|
||
4. **فلش** = (تعادل - 300) از هر دو طرف (اگر > 300)
|
||
5. **محاسبه مستقل** = هر کاربر جداگانه
|
||
6. **جمع صندوق** = مجموع امتیازهای همه
|
||
|
||
### ✅ نکات مهم:
|
||
- باقیمانده **جداگانه** ذخیره میشود (چپ و راست)
|
||
- فلش از **هر دو طرف** اتفاق میافتد
|
||
- سقف 300 روی **امتیاز نهایی** اعمال میشود
|
||
- هر کاربر مستقل از دیگران محاسبه میشود
|
||
|
||
---
|
||
|
||
## 📊 جدول مقایسه سناریوها
|
||
|
||
| سناریو | چپ | راست | تعادل | امتیاز | باقی چپ | باقی راست | فلش کل |
|
||
|--------|-----|-------|--------|--------|---------|-----------|---------|
|
||
| متعادل کوچک | 50 | 50 | 50 | 50 | 0 | 0 | 0 |
|
||
| متعادل متوسط | 200 | 200 | 200 | 200 | 0 | 0 | 0 |
|
||
| نامتعادل کوچک | 100 | 150 | 100 | 100 | 0 | 50 | 0 |
|
||
| نامتعادل متوسط | 250 | 350 | 250 | 250 | 0 | 100 | 0 |
|
||
| **سقف ساده** | **350** | **350** | **350** | **300** | **0** | **0** | **100** |
|
||
| **سقف نامتعادل** | **500** | **600** | **500** | **300** | **0** | **100** | **400** |
|
||
| سقف بزرگ | 800 | 900 | 800 | 300 | 0 | 100 | 1000 |
|
||
|
||
---
|
||
|
||
**پایان مثالهای عملی**
|
||
|
||
این مستند تمام حالات ممکن محاسبه تعادل را با مثالهای عددی واقعی نشان میدهد.
|
||
|
||
|
||
---
|
||
|
||
# ضمیمه: فرمولهای محاسبه باینری (Excel)
|
||
|
||
# محاسبات پلن باینری (Binary Plan Calculations)
|
||
|
||
## مستندات فرمولهای محاسبه کمیسیون باینری
|
||
|
||
این سند فرمولهای محاسباتی سیستم کمیسیون باینری را که از فایل اکسل استخراج شده، توضیح میدهد.
|
||
|
||
---
|
||
|
||
## متغیرها و تعاریف
|
||
|
||
### ورودیهای هفته قبل (Last Week Remainders)
|
||
|
||
| نام فارسی | نماد | توضیحات |
|
||
|-----------|------|---------|
|
||
| **باقیمانده هفته قبل چپ** | `LL` (Last Left) | باقیماندهای که از هفته قبل در پای چپ باقی مانده |
|
||
| **باقیمانده هفته قبل راست** | `LR` (Last Right) | باقیماندهای که از هفته قبل در پای راست باقی مانده |
|
||
|
||
**مثال از اکسل:**
|
||
- `LL = 200` (میلیون ریال)
|
||
- `LR = 0`
|
||
|
||
---
|
||
|
||
### ورودیهای هفته جدید (New Week Values)
|
||
|
||
| نام فارسی | نماد | توضیحات |
|
||
|-----------|------|---------|
|
||
| **هفته جدید چپ** | `NL` (New Left) | مجموع فروش/شارژ پای چپ در هفته جاری |
|
||
| **هفته جدید راست** | `NR` (New Right) | مجموع فروش/شارژ پای راست در هفته جاری |
|
||
|
||
**مثال از اکسل:**
|
||
- `NL = 400` (میلیون ریال)
|
||
- `NR = 500` (میلیون ریال)
|
||
|
||
---
|
||
|
||
### پارامتر سیستم (System Parameter)
|
||
|
||
| نام فارسی | نماد | توضیحات |
|
||
|-----------|------|---------|
|
||
| **ماکسیمم تعادل** | `MX` (Maximum Balance) | حداکثر مقداری که در یک هفته میتواند به عنوان تعادل (کمیسیون) محاسبه شود |
|
||
|
||
**مثال از اکسل:**
|
||
- `MX = 300` (میلیون ریال)
|
||
|
||
**نکته مهم:** این مقدار معمولاً بر اساس سطح کاربر یا پکیج خریداری شده تعیین میشود.
|
||
|
||
---
|
||
|
||
## فرمولهای محاسباتی
|
||
|
||
### 1️⃣ محاسبه مجموع پا چپ (Sum Left Total)
|
||
|
||
```
|
||
SLT = LL + NL
|
||
```
|
||
|
||
**توضیح:**
|
||
- `SLT` (Sum Left Total) = مجموع کل پای چپ
|
||
- باقیمانده هفته قبل + فروش هفته جدید
|
||
|
||
**مثال:**
|
||
```
|
||
SLT = 200 + 400 = 600
|
||
```
|
||
|
||
---
|
||
|
||
### 2️⃣ محاسبه مجموع پا راست (Sum Right Total)
|
||
|
||
```
|
||
SRT = LR + NR
|
||
```
|
||
|
||
**توضیح:**
|
||
- `SRT` (Sum Right Total) = مجموع کل پای راست
|
||
- باقیمانده هفته قبل + فروش هفته جدید
|
||
|
||
**مثال:**
|
||
```
|
||
SRT = 0 + 500 = 500
|
||
```
|
||
|
||
---
|
||
|
||
### 3️⃣ محاسبه کمترین کل (Minimum Total)
|
||
|
||
```
|
||
MinT = MIN(SLT, SRT)
|
||
```
|
||
|
||
**توضیح:**
|
||
- `MinT` = کوچکترین مقدار بین دو پا
|
||
- این مقدار نشاندهنده حداکثر تعادل بالقوه است
|
||
|
||
**مثال:**
|
||
```
|
||
MinT = MIN(600, 500) = 500
|
||
```
|
||
|
||
---
|
||
|
||
### 4️⃣ محاسبه باقیمانده هفته بعد چپ (Remainder Next Week Left)
|
||
|
||
```
|
||
RNWL = SLT - MinT
|
||
```
|
||
|
||
**توضیح:**
|
||
- `RNWL` (Remainder Next Week Left) = باقیماندهای که به هفته بعد منتقل میشود
|
||
- مازاد پای چپ که برای تعادل استفاده نشد
|
||
|
||
**مثال:**
|
||
```
|
||
RNWL = 600 - 500 = 100
|
||
```
|
||
|
||
---
|
||
|
||
### 5️⃣ محاسبه باقیمانده هفته بعد راست (Remainder Next Week Right)
|
||
|
||
```
|
||
RNWR = SRT - MinT
|
||
```
|
||
|
||
**توضیح:**
|
||
- `RNWR` (Remainder Next Week Right) = باقیماندهای که به هفته بعد منتقل میشود
|
||
- مازاد پای راست که برای تعادل استفاده نشد
|
||
|
||
**مثال:**
|
||
```
|
||
RNWR = 500 - 500 = 0
|
||
```
|
||
|
||
**نکته:** یکی از دو باقیمانده همیشه صفر است (چون MinT کوچکترین است).
|
||
|
||
---
|
||
|
||
### 6️⃣ محاسبه فلش چپ (Flush Left)
|
||
|
||
```
|
||
FL = SLT - MX - RNWL
|
||
```
|
||
|
||
**توضیح:**
|
||
- `FL` (Flush Left) = مقداری که از ماکسیمم هم بیشتر بود و باید دور ریخته شود
|
||
- این مقدار نشاندهنده سرریز (overflow) است که نمیتواند به هفته بعد منتقل شود
|
||
|
||
**مثال:**
|
||
```
|
||
FL = 600 - 300 - 100 = 200
|
||
```
|
||
|
||
**معنی:** از 600 میلیون پای چپ:
|
||
- 300 به عنوان کمیسیون استفاده شد (تا حد MX)
|
||
- 100 به هفته بعد منتقل شد
|
||
- **200 فلش شد (از دست رفت)** ❌
|
||
|
||
---
|
||
|
||
### 7️⃣ محاسبه فلش راست (Flush Right)
|
||
|
||
```
|
||
FR = SRT - MX - RNWR
|
||
```
|
||
|
||
**توضیح:**
|
||
- `FR` (Flush Right) = مقداری که از پای راست دور ریخته میشود
|
||
|
||
**مثال:**
|
||
```
|
||
FR = 500 - 300 - 0 = 200
|
||
```
|
||
|
||
**معنی:** از 500 میلیون پای راست:
|
||
- 300 به عنوان کمیسیون استفاده شد
|
||
- 0 به هفته بعد منتقل شد
|
||
- **200 فلش شد (از دست رفت)** ❌
|
||
|
||
---
|
||
|
||
### 8️⃣ محاسبه کل تعادل (Total Balance / Commission)
|
||
|
||
```
|
||
TB = IF(MinT > MX, MX, MinT)
|
||
```
|
||
|
||
یا به زبان سادهتر:
|
||
```
|
||
TB = MIN(MinT, MX)
|
||
```
|
||
|
||
**توضیح:**
|
||
- `TB` (Total Balance) = مقدار واقعی کمیسیونی که به کاربر تعلق میگیرد
|
||
- نمیتواند از ماکسیمم تعادل (`MX`) بیشتر شود
|
||
|
||
**مثال:**
|
||
```
|
||
TB = MIN(500, 300) = 300
|
||
```
|
||
|
||
**معنی:** هرچند تعادل واقعی 500 بود، اما به دلیل محدودیت `MX`، فقط 300 به عنوان کمیسیون پرداخت میشود.
|
||
|
||
---
|
||
|
||
## خلاصه جریان محاسبات
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ ورودیها │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ LL = 200 باقیمانده هفته قبل چپ │
|
||
│ LR = 0 باقیمانده هفته قبل راست │
|
||
│ NL = 400 هفته جدید چپ │
|
||
│ NR = 500 هفته جدید راست │
|
||
│ MX = 300 ماکسیمم تعادل │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ گام 1: محاسبه مجموع دو پا │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ SLT = LL + NL = 200 + 400 = 600 │
|
||
│ SRT = LR + NR = 0 + 500 = 500 │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ گام 2: محاسبه کمترین کل │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ MinT = MIN(SLT, SRT) = MIN(600, 500) = 500 │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ گام 3: محاسبه کمیسیون واقعی (با اعمال Cap) │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ TB = MIN(MinT, MX) = MIN(500, 300) = 300 ✅ کمیسیون │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ گام 4: محاسبه باقیمانده هفته بعد │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ RNWL = SLT - MinT = 600 - 500 = 100 → هفته بعد │
|
||
│ RNWR = SRT - MinT = 500 - 500 = 0 → هفته بعد │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ گام 5: محاسبه فلش (از دست رفته) │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ FL = SLT - MX - RNWL = 600 - 300 - 100 = 200 ❌ فلش │
|
||
│ FR = SRT - MX - RNWR = 500 - 300 - 0 = 200 ❌ فلش │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## تحلیل نتایج
|
||
|
||
### 📊 خروجیهای نهایی
|
||
|
||
| مقدار | توضیح | وضعیت |
|
||
|-------|-------|-------|
|
||
| **TB = 300** | کمیسیون پرداختی این هفته | ✅ پرداخت میشود |
|
||
| **RNWL = 100** | باقیمانده پای چپ برای هفته بعد | ⏭️ منتقل میشود |
|
||
| **RNWR = 0** | باقیمانده پای راست برای هفته بعد | ⏭️ منتقل میشود |
|
||
| **FL = 200** | فلش پای چپ | ❌ از دست میرود |
|
||
| **FR = 200** | فلش پای راست | ❌ از دست میرود |
|
||
|
||
---
|
||
|
||
### 🔍 تفسیر کسبوکار
|
||
|
||
#### کمیسیون محاسبه شده
|
||
```
|
||
کمیسیون = 300 میلیون ریال
|
||
```
|
||
- به دلیل محدودیت `MX = 300`، از تعادل بالقوه 500، فقط 300 قابل برداشت است
|
||
- این یک مکانیزم کنترل هزینه است
|
||
|
||
#### باقیمانده به هفته بعد
|
||
```
|
||
هفته بعد LL = 100 (از پای چپ)
|
||
هفته بعد LR = 0 (از پای راست)
|
||
```
|
||
- 100 میلیون از پای چپ به هفته بعد منتقل میشود
|
||
- این باقیمانده در محاسبات هفته آینده دوباره استفاده خواهد شد
|
||
|
||
#### فلش (Flush) - نکته مهم ⚠️
|
||
```
|
||
فلش کل = 400 میلیون ریال (200 چپ + 200 راست)
|
||
```
|
||
|
||
**چرا فلش رخ میدهد؟**
|
||
1. مجموع دو پا = 1100 میلیون (600 + 500)
|
||
2. کمیسیون محاسبه شده = 300 میلیون
|
||
3. باقیمانده منتقل شده = 100 میلیون
|
||
4. فلش = 1100 - 300 - 100 = 700 میلیون ❌
|
||
|
||
**توضیح:**
|
||
- فلش نشاندهنده مقداری است که به دلیل **عدم تعادل** و **محدودیت Cap** از دست میرود
|
||
- این یک ضرر برای کاربر است که میتواند با متعادل کردن دو پا کاهش یابد
|
||
|
||
---
|
||
|
||
## پیادهسازی در C#
|
||
|
||
### کلاس مدل
|
||
|
||
```csharp
|
||
public class BinaryPlanCalculationInput
|
||
{
|
||
// ورودیهای هفته قبل
|
||
public decimal LastLeftRemainder { get; set; } // LL
|
||
public decimal LastRightRemainder { get; set; } // LR
|
||
|
||
// ورودیهای هفته جاری
|
||
public decimal NewLeftVolume { get; set; } // NL
|
||
public decimal NewRightVolume { get; set; } // NR
|
||
|
||
// تنظیمات سیستم
|
||
public decimal MaximumBalance { get; set; } // MX
|
||
}
|
||
|
||
public class BinaryPlanCalculationResult
|
||
{
|
||
// محاسبات واسط
|
||
public decimal SumLeftTotal { get; set; } // SLT
|
||
public decimal SumRightTotal { get; set; } // SRT
|
||
public decimal MinimumTotal { get; set; } // MinT
|
||
|
||
// باقیماندهها
|
||
public decimal RemainderNextWeekLeft { get; set; } // RNWL
|
||
public decimal RemainderNextWeekRight { get; set; } // RNWR
|
||
|
||
// فلش
|
||
public decimal FlushLeft { get; set; } // FL
|
||
public decimal FlushRight { get; set; } // FR
|
||
|
||
// نتیجه نهایی
|
||
public decimal TotalBalance { get; set; } // TB - کمیسیون واقعی
|
||
public decimal TotalFlush { get; set; } // مجموع فلش
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### متد محاسبه
|
||
|
||
```csharp
|
||
public static BinaryPlanCalculationResult Calculate(BinaryPlanCalculationInput input)
|
||
{
|
||
var result = new BinaryPlanCalculationResult();
|
||
|
||
// گام 1: محاسبه مجموع دو پا
|
||
result.SumLeftTotal = input.LastLeftRemainder + input.NewLeftVolume;
|
||
result.SumRightTotal = input.LastRightRemainder + input.NewRightVolume;
|
||
|
||
// گام 2: محاسبه کمترین کل
|
||
result.MinimumTotal = Math.Min(result.SumLeftTotal, result.SumRightTotal);
|
||
|
||
// گام 3: محاسبه کمیسیون واقعی (با اعمال Cap)
|
||
result.TotalBalance = Math.Min(result.MinimumTotal, input.MaximumBalance);
|
||
|
||
// گام 4: محاسبه باقیمانده هفته بعد
|
||
result.RemainderNextWeekLeft = result.SumLeftTotal - result.MinimumTotal;
|
||
result.RemainderNextWeekRight = result.SumRightTotal - result.MinimumTotal;
|
||
|
||
// گام 5: محاسبه فلش
|
||
result.FlushLeft = result.SumLeftTotal - input.MaximumBalance - result.RemainderNextWeekLeft;
|
||
result.FlushRight = result.SumRightTotal - input.MaximumBalance - result.RemainderNextWeekRight;
|
||
|
||
// محاسبه مجموع فلش
|
||
result.TotalFlush = result.FlushLeft + result.FlushRight;
|
||
|
||
// اطمینان از عدم منفی شدن فلش
|
||
result.FlushLeft = Math.Max(0, result.FlushLeft);
|
||
result.FlushRight = Math.Max(0, result.FlushRight);
|
||
result.TotalFlush = Math.Max(0, result.TotalFlush);
|
||
|
||
return result;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### مثال استفاده
|
||
|
||
```csharp
|
||
var input = new BinaryPlanCalculationInput
|
||
{
|
||
LastLeftRemainder = 200_000_000, // 200 میلیون
|
||
LastRightRemainder = 0,
|
||
NewLeftVolume = 400_000_000, // 400 میلیون
|
||
NewRightVolume = 500_000_000, // 500 میلیون
|
||
MaximumBalance = 300_000_000 // 300 میلیون
|
||
};
|
||
|
||
var result = Calculate(input);
|
||
|
||
Console.WriteLine($"کمیسیون قابل پرداخت: {result.TotalBalance:N0} ریال");
|
||
// Output: کمیسیون قابل پرداخت: 300,000,000 ریال
|
||
|
||
Console.WriteLine($"باقیمانده چپ هفته بعد: {result.RemainderNextWeekLeft:N0} ریال");
|
||
// Output: باقیمانده چپ هفته بعد: 100,000,000 ریال
|
||
|
||
Console.WriteLine($"باقیمانده راست هفته بعد: {result.RemainderNextWeekRight:N0} ریال");
|
||
// Output: باقیمانده راست هفته بعد: 0 ریال
|
||
|
||
Console.WriteLine($"فلش کل: {result.TotalFlush:N0} ریال");
|
||
// Output: فلش کل: 400,000,000 ریال
|
||
```
|
||
|
||
---
|
||
|
||
## نکات مهم برای پیادهسازی
|
||
|
||
### 1️⃣ ذخیره باقیماندهها
|
||
```csharp
|
||
// باید در دیتابیس ذخیره شود
|
||
await SaveWeeklyRemainders(userId, weekId, new WeeklyRemainders
|
||
{
|
||
LeftRemainder = result.RemainderNextWeekLeft,
|
||
RightRemainder = result.RemainderNextWeekRight
|
||
});
|
||
```
|
||
|
||
### 2️⃣ لاگ فلش برای تحلیل
|
||
```csharp
|
||
if (result.TotalFlush > 0)
|
||
{
|
||
await LogFlush(userId, weekId, new FlushLog
|
||
{
|
||
FlushLeft = result.FlushLeft,
|
||
FlushRight = result.FlushRight,
|
||
Reason = "Cap limitation and imbalance"
|
||
});
|
||
}
|
||
```
|
||
|
||
### 3️⃣ تعیین MaximumBalance
|
||
```csharp
|
||
// بر اساس سطح کاربر
|
||
decimal GetMaximumBalance(User user)
|
||
{
|
||
return user.MembershipLevel switch
|
||
{
|
||
MembershipLevel.Bronze => 100_000_000,
|
||
MembershipLevel.Silver => 300_000_000,
|
||
MembershipLevel.Gold => 500_000_000,
|
||
MembershipLevel.Platinum => 1_000_000_000,
|
||
_ => 50_000_000
|
||
};
|
||
}
|
||
```
|
||
|
||
### 4️⃣ واحد پول
|
||
```csharp
|
||
// همه مقادیر باید در واحد ریال ذخیره شوند
|
||
// برای نمایش میتوان به میلیون یا تومان تبدیل کرد
|
||
decimal DisplayInMillions(decimal rials) => rials / 1_000_000;
|
||
decimal DisplayInTomans(decimal rials) => rials / 10;
|
||
```
|
||
|
||
---
|
||
|
||
## سناریوهای مختلف
|
||
|
||
### سناریو 1: تعادل کامل
|
||
```
|
||
LL = 0, LR = 0, NL = 300, NR = 300, MX = 500
|
||
→ TB = 300, RNWL = 0, RNWR = 0, FL = 0, FR = 0
|
||
```
|
||
**نتیجه:** کمیسیون کامل بدون فلش ✅
|
||
|
||
---
|
||
|
||
### سناریو 2: یک پا خیلی بیشتر
|
||
```
|
||
LL = 0, LR = 0, NL = 1000, NR = 100, MX = 500
|
||
→ TB = 100, RNWL = 900, RNWR = 0, FL = 400, FR = 0
|
||
```
|
||
**نتیجه:** کمیسیون کم + فلش زیاد ❌
|
||
|
||
---
|
||
|
||
### سناریو 3: باقیمانده قبلی موثر
|
||
```
|
||
LL = 400, LR = 0, NL = 100, NR = 400, MX = 300
|
||
→ SLT = 500, SRT = 400
|
||
→ TB = 300, RNWL = 100, RNWR = 0, FL = 100, FR = 100
|
||
```
|
||
**نتیجه:** باقیمانده قبلی در محاسبه کمیسیون موثر است ✅
|
||
|
||
---
|
||
|
||
## تفاوت با کد فعلی
|
||
|
||
### در کد فعلی (`CalculateWeeklyBalancesCommandHandler.cs`):
|
||
|
||
```csharp
|
||
// 1. ابتدا Cap اعمال میشود
|
||
var cappedLeft = Math.Min(leftLegTotal, maxBalance);
|
||
var cappedRight = Math.Min(rightLegTotal, maxBalance);
|
||
|
||
// 2. سپس تعادل محاسبه میشود
|
||
var balance = Math.Min(cappedLeft, cappedRight);
|
||
|
||
// 3. باقیماندهها محاسبه میشوند
|
||
var leftRemainder = leftLegTotal - balance;
|
||
var rightRemainder = rightLegTotal - balance;
|
||
```
|
||
|
||
### در فرمول اکسل:
|
||
```csharp
|
||
// 1. ابتدا تعادل کامل محاسبه میشود
|
||
var minTotal = Math.Min(leftLegTotal, rightLegTotal);
|
||
|
||
// 2. سپس Cap اعمال میشود
|
||
var balance = Math.Min(minTotal, maxBalance);
|
||
|
||
// 3. باقیماندهها بر اساس minTotal محاسبه میشوند
|
||
var leftRemainder = leftLegTotal - minTotal;
|
||
var rightRemainder = rightLegTotal - minTotal;
|
||
|
||
// 4. فلش محاسبه میشود
|
||
var flushLeft = leftLegTotal - maxBalance - leftRemainder;
|
||
var flushRight = rightLegTotal - maxBalance - rightRemainder;
|
||
```
|
||
|
||
**تفاوت کلیدی:**
|
||
- کد فعلی Cap را ابتدا اعمال میکند (میتواند باقیماندههای بیشتری ایجاد کند)
|
||
- فرمول اکسل ابتدا تعادل را محاسبه میکند، سپس Cap اعمال میشود (فلش دقیقتر محاسبه میشود)
|
||
|
||
---
|
||
|
||
## نتیجهگیری
|
||
|
||
این فرمولها نشان میدهند که:
|
||
|
||
1. ✅ **تعادل اهمیت دارد** - هرچه دو پا متعادلتر باشند، فلش کمتر است
|
||
2. ✅ **Cap محدودیت ایجاد میکند** - حتی با تعادل کامل، بیش از MX کمیسیون داده نمیشود
|
||
3. ✅ **باقیماندهها منتقل میشوند** - برای هفته بعد ذخیره میشوند
|
||
4. ❌ **فلش ضرر است** - مقداری که به دلیل عدم تعادل یا Cap از دست میرود
|
||
|
||
**توصیه:** برای افزایش کمیسیون، کاربران باید:
|
||
- دو پای خود را متعادل نگه دارند
|
||
- سطح عضویت خود را ارتقا دهند (برای افزایش MX)
|
||
- از باقیماندهها در هفتههای بعد استفاده کنند
|