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.
This commit is contained in:
masoodafar-web
2025-12-20 06:15:59 +03:30
parent 13a3489765
commit 002e99f6bf
32 changed files with 10263 additions and 106 deletions
@@ -0,0 +1,546 @@
# محاسبات پلن باینری (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)
- از باقیمانده‌ها در هفته‌های بعد استفاده کنند