# 🔧 اصلاح محاسبه کمیسیون هفتگی - تحلیل و برنامه اجرایی **تاریخ**: ۱۴ آذر ۱۴۰۴ (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 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 /// /// محاسبه مجموع تعادل‌های زیرمجموعه یک کاربر تا N لول /// private async Task 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; } /// /// پیدا کردن بازگشتی زیرمجموعه‌ها /// private async Task> GetSubordinatesRecursive( long userId, int currentLevel, int maxLevel, CancellationToken cancellationToken) { if (currentLevel > maxLevel) return new List(); var result = new List(); // پیدا کردن فرزندان مستقیم 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 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 /// /// تعادل شخصی (بدون زیرمجموعه) /// public int PersonalBalances { get; set; } /// /// تعادل زیرمجموعه‌ها /// public int SubordinateBalances { get; set; } /// /// مجموع (PersonalBalances + SubordinateBalances) /// 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 --- **آیا این تحلیل و برنامه مورد تأیید شماست؟**