diff --git a/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyBalances/CalculateWeeklyBalancesCommandHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyBalances/CalculateWeeklyBalancesCommandHandler.cs index 740763f..247e533 100644 --- a/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyBalances/CalculateWeeklyBalancesCommandHandler.cs +++ b/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyBalances/CalculateWeeklyBalancesCommandHandler.cs @@ -54,12 +54,16 @@ public class CalculateWeeklyBalancesCommandHandler : IRequestHandler x.IsActive && ( x.Key == "Club.ActivationFee" || x.Key == "Commission.WeeklyPoolContributionPercent" || - x.Key == "Commission.MaxWeeklyBalancesPerUser")) + x.Key == "Commission.MaxWeeklyBalancesPerLeg" || + x.Key == "Commission.MaxNetworkLevel")) .ToDictionaryAsync(x => x.Key, x => x.Value, cancellationToken); var activationFee = long.Parse(configs.GetValueOrDefault("Club.ActivationFee", "25000000")); var poolPercent = decimal.Parse(configs.GetValueOrDefault("Commission.WeeklyPoolContributionPercent", "20")) / 100m; - var maxWeeklyBalances = int.Parse(configs.GetValueOrDefault("Commission.MaxWeeklyBalancesPerUser", "300")); + // سقف تعادل هفتگی برای هر دست (نه کل) - 300 برای چپ + 300 برای راست = حداکثر 600 تعادل + var maxBalancesPerLeg = int.Parse(configs.GetValueOrDefault("Commission.MaxWeeklyBalancesPerLeg", "300")); + // حداکثر عمق شبکه برای شمارش اعضا (15 لول) + var maxNetworkLevel = int.Parse(configs.GetValueOrDefault("Commission.MaxNetworkLevel", "15")); foreach (var user in usersInNetwork) { @@ -72,31 +76,32 @@ public class CalculateWeeklyBalancesCommandHandler : IRequestHandler /// شمارش اعضای جدیدی که در این هفته به یک پا اضافه شدند - /// فقط فرزندان مستقیم که ActivatedAt آنها در این هفته است + /// تا maxLevel لول پایین‌تر شمارش می‌شود /// - private async Task CountNewMembersInLeg(long userId, NetworkLeg leg, string weekNumber, CancellationToken cancellationToken) + private async Task CountNewMembersInLeg(long userId, NetworkLeg leg, string weekNumber, int maxLevel, CancellationToken cancellationToken) { // تبدیل WeekNumber به بازه تاریخی var (startDate, endDate) = GetWeekDateRange(weekNumber); - // شمارش تمام اعضای زیرمجموعه که در این هفته فعال شدند - var count = await CountNewMembersRecursive(userId, leg, startDate, endDate, cancellationToken); + // شمارش تمام اعضای زیرمجموعه که در این هفته فعال شدند (تا maxLevel لول) + var count = await CountNewMembersRecursive(userId, leg, startDate, endDate, 0, maxLevel, cancellationToken); return count; } /// /// شمارش بازگشتی اعضای جدید در یک پا + /// محدودیت عمق: تا maxLevel لول پایین‌تر شمارش می‌شود /// - private async Task CountNewMembersRecursive(long userId, NetworkLeg leg, DateTime startDate, DateTime endDate, CancellationToken cancellationToken) + 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); @@ -203,9 +222,9 @@ public class CalculateWeeklyBalancesCommandHandler : IRequestHandler x.Key == "Commission.MaxNetworkLevel" && x.IsActive) + .Select(x => x.Value) + .FirstOrDefaultAsync(cancellationToken); + var maxNetworkLevel = int.Parse(maxNetworkLevelConfig ?? "15"); + + // دریافت همه تعادل‌های هفتگی (شامل صفرها هم برای محاسبه زیرمجموعه) + var allWeeklyBalances = await _context.NetworkWeeklyBalances + .Where(x => x.WeekNumber == request.WeekNumber) + .ToDictionaryAsync(x => x.UserId, cancellationToken); + + // دریافت کاربرانی که تعادل > 0 دارند (یا زیرمجموعه‌شان دارد) + var usersWithBalances = await _context.NetworkWeeklyBalances .Where(x => x.WeekNumber == request.WeekNumber && x.TotalBalances > 0) + .Select(x => x.UserId) .ToListAsync(cancellationToken); + // پیدا کردن تمام کاربرانی که باید کمیسیون بگیرند (شامل والدین) + var usersToProcess = new HashSet(usersWithBalances); + + // اضافه کردن والدین تا 15 لول بالاتر + foreach (var userId in usersWithBalances) + { + var ancestors = await GetAncestors(userId, maxNetworkLevel, cancellationToken); + foreach (var ancestorId in ancestors) + { + usersToProcess.Add(ancestorId); + } + } + var payoutsList = new List(); - foreach (var balance in weeklyBalances) + foreach (var userId in usersToProcess) { + // ⭐ محاسبه تعادل شخصی + var personalBalances = 0; + if (allWeeklyBalances.ContainsKey(userId)) + { + personalBalances = allWeeklyBalances[userId].TotalBalances; + } + + // ⭐ محاسبه مجموع تعادل‌های زیرمجموعه تا maxNetworkLevel لول + var subordinateBalances = await CalculateSubordinateBalancesAsync( + userId, + request.WeekNumber, + allWeeklyBalances, + maxNetworkLevel, + cancellationToken + ); + + // ⭐ مجموع تعادل = شخصی + زیرمجموعه + var totalBalancesWithSubordinates = personalBalances + subordinateBalances; + + // اگر مجموع تعادل صفر است، نیازی به ثبت نیست + if (totalBalancesWithSubordinates <= 0) + { + continue; + } + // محاسبه مبلغ کمیسیون - var totalAmount = (long)(balance.TotalBalances * pool.ValuePerBalance); + var totalAmount = (long)(totalBalancesWithSubordinates * pool.ValuePerBalance); var payout = new UserCommissionPayout { - UserId = balance.UserId, + UserId = userId, WeekNumber = request.WeekNumber, WeeklyPoolId = pool.Id, - BalancesEarned = balance.TotalBalances, + BalancesEarned = totalBalancesWithSubordinates, // ⭐ شامل زیرمجموعه ValuePerBalance = pool.ValuePerBalance, TotalAmount = totalAmount, Status = CommissionPayoutStatus.Pending, @@ -96,4 +147,92 @@ public class ProcessUserPayoutsCommandHandler : IRequestHandler + /// پیدا کردن والدین یک کاربر تا N لول بالاتر + /// + private async Task> GetAncestors(long userId, int maxLevels, CancellationToken cancellationToken) + { + var ancestors = new List(); + var currentUserId = userId; + + for (int level = 0; level < maxLevels; level++) + { + var user = await _context.Users + .Where(x => x.Id == currentUserId) + .Select(x => x.NetworkParentId) + .FirstOrDefaultAsync(cancellationToken); + + if (user == null || !user.HasValue) + { + break; + } + + ancestors.Add(user.Value); + currentUserId = user.Value; + } + + return ancestors; + } + + /// + /// محاسبه مجموع تعادل‌های زیرمجموعه یک کاربر تا N لول پایین‌تر + /// + private async Task CalculateSubordinateBalancesAsync( + long userId, + string weekNumber, + Dictionary allBalances, + int maxLevel, + CancellationToken cancellationToken) + { + // پیدا کردن همه زیرمجموعه‌ها تا maxLevel لول + var subordinates = await GetSubordinatesRecursive(userId, 1, maxLevel, cancellationToken); + + // جمع تعادل‌های آنها + var totalSubordinateBalances = 0; + foreach (var subordinateId in subordinates) + { + if (allBalances.ContainsKey(subordinateId)) + { + totalSubordinateBalances += allBalances[subordinateId].TotalBalances; + } + } + + return totalSubordinateBalances; + } + + /// + /// پیدا کردن بازگشتی زیرمجموعه‌ها تا N لول + /// + 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; + } } diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWithdrawalRequests/GetWithdrawalRequestsQuery.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWithdrawalRequests/GetWithdrawalRequestsQuery.cs index 9338d47..78ab665 100644 --- a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWithdrawalRequests/GetWithdrawalRequestsQuery.cs +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWithdrawalRequests/GetWithdrawalRequestsQuery.cs @@ -5,6 +5,7 @@ public class GetWithdrawalRequestsQuery : IRequest x.WeekNumber == request.WeekNumber); } + if (!string.IsNullOrWhiteSpace(request.IbanNumber)) + { + query = query.Where(x => x.IbanNumber != null && x.IbanNumber.Contains(request.IbanNumber)); + } + query = query.ApplyOrder(sortBy: request.SortBy ?? "-Created"); var meta = await query.GetMetaData(request.PaginationState, cancellationToken); @@ -53,8 +58,11 @@ public class GetWithdrawalRequestsQueryHandler : IRequestHandler