002e99f6bf
- 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.
547 lines
20 KiB
Markdown
547 lines
20 KiB
Markdown
# محاسبات پلن باینری (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)
|
|
- از باقیماندهها در هفتههای بعد استفاده کنند
|