feat: Update CURRENT-SPRINT.md with progress and task completion details
- Added last updated date and refined project status for Backend and Frontend. - Updated completion percentages for FrontOffice UI and BFF. - Documented completed tasks for BackOffice in the current sprint. - Added new high-priority tasks related to commission calculation fixes. - Resolved blockers and minor issues, providing a clearer progress summary. docs: Create commission-calculation-fix.md for weekly commission calculation analysis - Documented critical issues affecting commission calculations. - Provided a detailed plan for fixing the commission calculation logic. - Included code analysis, affected files, and step-by-step tasks for implementation. - Outlined the expected timeline and important notes regarding changes.
This commit is contained in:
@@ -1,12 +1,68 @@
|
||||
# Balance Calculation with Carryover Logic - Complete Guide
|
||||
|
||||
**Date**: 2025-12-01
|
||||
**Last Updated**: 2025-12-01 (Added Configuration Integration + MaxWeeklyBalances Cap)
|
||||
**Status**: ✅ Implemented
|
||||
**Last Updated**: 2025-12-04 (⚠️ تغییر مهم: سقف 300 برای هر دست، نه کل)
|
||||
**Status**: ✅ Implemented (نیاز به اصلاح سقف دارد)
|
||||
**Migration**: `UpdateNetworkWeeklyBalanceWithCarryover`
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ اصلاحیه مهم بیزینس (2025-12-04)
|
||||
|
||||
### مشکل شناسایی شده:
|
||||
در پیادهسازی فعلی، سقف تعادل هفتگی **300 کل** در نظر گرفته شده بود. اما طبق قانون صحیح بیزینس:
|
||||
|
||||
| توضیح | منطق فعلی (اشتباه) | منطق صحیح |
|
||||
|-------|---------------------|-----------|
|
||||
| سقف | 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:**
|
||||
@@ -15,9 +71,11 @@
|
||||
// تمام مقادیر از جدول SystemConfigurations خوانده میشوند
|
||||
Club.ActivationFee = 25,000,000 ریال (هزینه فعالسازی)
|
||||
Commission.WeeklyPoolContributionPercent = 20% (سهم استخر)
|
||||
Commission.MaxWeeklyBalancesPerUser = 300 (سقف تعادل هفتگی)
|
||||
Commission.MaxWeeklyBalancesPerLeg = 300 (⚠️ سقف تعادل هفتگی - هر دست)
|
||||
```
|
||||
|
||||
**توجه:** کلید قدیمی `MaxWeeklyBalancesPerUser` باید به `MaxWeeklyBalancesPerLeg` تغییر کند.
|
||||
|
||||
### **Pool Contribution Calculation:**
|
||||
|
||||
```csharp
|
||||
@@ -32,29 +90,53 @@ weeklyPoolContribution = totalNewMembers × activationFee × poolPercent
|
||||
|
||||
---
|
||||
|
||||
## 🚫 MaxWeeklyBalances Cap (محدودیت سقف)
|
||||
## 🚫 MaxWeeklyBalances Cap (محدودیت سقف - هر دست)
|
||||
|
||||
### **Logic:**
|
||||
### **Logic (صحیح):**
|
||||
|
||||
```csharp
|
||||
totalBalances = MIN(leftTotal, rightTotal)
|
||||
cappedBalances = MIN(totalBalances, maxWeeklyBalances) // 300
|
||||
// ⚠️ سقف روی هر دست جداگانه اعمال میشود
|
||||
cappedLeftTotal = MIN(leftTotal, maxBalancesPerLeg) // 300
|
||||
cappedRightTotal = MIN(rightTotal, maxBalancesPerLeg) // 300
|
||||
|
||||
// اگر بیشتر از سقف بود، مازاد به remainder اضافه میشود
|
||||
excessBalances = totalBalances - cappedBalances
|
||||
// تعادل = کمترین مقدار بعد از اعمال سقف
|
||||
totalBalances = MIN(cappedLeftTotal, cappedRightTotal)
|
||||
|
||||
// باقیمانده = مقدار قبل از سقف - سقف (نه از totalBalances)
|
||||
leftRemainder = leftTotal - cappedLeftTotal
|
||||
rightRemainder = rightTotal - cappedRightTotal
|
||||
```
|
||||
|
||||
### **Example:**
|
||||
### **Example (جدید):**
|
||||
|
||||
```
|
||||
Week 5:
|
||||
leftTotal = 350, rightTotal = 400
|
||||
totalBalances = MIN(350, 400) = 350
|
||||
cappedBalances = MIN(350, 300) = 300 ✅ محدود شد!
|
||||
excessBalances = 350 - 300 = 50
|
||||
maxBalancesPerLeg = 300
|
||||
|
||||
leftRemainder = 0 + 50 = 50 (میرود برای هفته بعد)
|
||||
rightRemainder = 50
|
||||
cappedLeftTotal = MIN(350, 300) = 300
|
||||
cappedRightTotal = MIN(400, 300) = 300
|
||||
|
||||
totalBalances = MIN(300, 300) = 300 ✅
|
||||
|
||||
// باقیمانده = اضافهای که از سقف رد شده
|
||||
leftRemainder = 350 - 300 = 50
|
||||
rightRemainder = 400 - 300 = 100
|
||||
```
|
||||
|
||||
### **مقایسه منطق قدیم 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 // باقیمانده از سقف هر دست
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,354 @@
|
||||
# 🔧 اصلاح محاسبه کمیسیون هفتگی - تحلیل و برنامه اجرایی
|
||||
|
||||
**تاریخ**: ۱۴ آذر ۱۴۰۴ (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. ✅ تأیید این داکیومنت توسط شما
|
||||
2. [ ] فاز ۱: Config
|
||||
3. [ ] فاز ۲: CalculateWeeklyBalances
|
||||
4. [ ] فاز ۳: ProcessUserPayouts
|
||||
5. [ ] فاز ۴: تست و Build
|
||||
6. [ ] آپدیت CURRENT-SPRINT.md
|
||||
|
||||
---
|
||||
|
||||
**آیا این تحلیل و برنامه مورد تأیید شماست؟**
|
||||
Reference in New Issue
Block a user