using CMSMicroservice.Domain.Common; namespace CMSMicroservice.Application.CommissionCQ.Commands.ProcessUserPayouts; public class ProcessUserPayoutsCommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IWeekDefinitionRepository _weekRepository; public ProcessUserPayoutsCommandHandler( IApplicationDbContext context, IWeekDefinitionRepository weekRepository) { _context = context; _weekRepository = weekRepository; } public async Task Handle(ProcessUserPayoutsCommand request, CancellationToken cancellationToken) { var regorianWeekNumber = _weekRepository.GetGregorianWeekNumber(request.WeekDefinitionId); if (regorianWeekNumber == null) { throw new InvalidOperationException($"هفته {request.WeekDefinitionId} در سیستم تعریف نشده است"); } // بررسی وجود استخر var pool = await _context.WeeklyCommissionPools .FirstOrDefaultAsync(x => x.WeekDefinitionId == request.WeekDefinitionId, cancellationToken); if (pool == null || !pool.IsCalculated) { throw new InvalidOperationException($"استخر کمیسیون هفته {request.WeekDefinitionId} هنوز محاسبه نشده است"); } // بررسی پرداخت قبلی var existingPayouts = await _context.UserCommissionPayouts .Where(x => x.WeekDefinitionId == request.WeekDefinitionId) .ToListAsync(cancellationToken); if (existingPayouts.Any() && !request.ForceReprocess) { throw new InvalidOperationException($"پرداخت‌های هفته {request.WeekDefinitionId} قبلاً انجام شده است"); } // حذف پرداخت‌های قبلی در صورت ForceReprocess if (existingPayouts.Any()) { _context.UserCommissionPayouts.RemoveRange(existingPayouts); await _context.SaveChangesAsync(cancellationToken); } // ⭐ خواندن MaxNetworkLevel از SystemConstants (استاتیک) var maxNetworkLevel = SystemConstants.CommissionMaxNetworkLevel; // دریافت همه تعادل‌های هفتگی (شامل صفرها هم برای محاسبه زیرمجموعه) var allWeeklyBalances = await _context.NetworkWeeklyBalances .Where(x => x.WeekDefinitionId == request.WeekDefinitionId) .ToDictionaryAsync(x => x.UserId, cancellationToken); // دریافت کاربرانی که تعادل > 0 دارند (یا زیرمجموعه‌شان دارد) var usersWithBalances = await _context.NetworkWeeklyBalances .Where(x => x.WeekDefinitionId == request.WeekDefinitionId && 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 userId in usersToProcess) { // ⭐ محاسبه تعادل شخصی var personalBalances = 0; if (allWeeklyBalances.ContainsKey(userId)) { personalBalances = allWeeklyBalances[userId].TotalBalances; } // ⭐ محاسبه مجموع تعادل‌های زیرمجموعه تا maxNetworkLevel لول var subordinateBalances = await CalculateSubordinateBalancesAsync( userId, request.WeekDefinitionId, allWeeklyBalances, maxNetworkLevel, cancellationToken ); // ⭐ مجموع تعادل = شخصی + زیرمجموعه var totalBalancesWithSubordinates = personalBalances + subordinateBalances; // اگر مجموع تعادل صفر است، نیازی به ثبت نیست if (totalBalancesWithSubordinates <= 0) { continue; } // محاسبه مبلغ کمیسیون var totalAmount = (long)(totalBalancesWithSubordinates * pool.ValuePerBalance); var payout = new UserCommissionPayout { UserId = userId, WeekDefinitionId = request.WeekDefinitionId, WeeklyPoolId = pool.Id, BalancesEarned = totalBalancesWithSubordinates, // ⭐ شامل زیرمجموعه ValuePerBalance = pool.ValuePerBalance, TotalAmount = totalAmount, Status = CommissionPayoutStatus.Pending, PaidAt = null, WithdrawalMethod = null, IbanNumber = null, WithdrawnAt = null }; payoutsList.Add(payout); } await _context.UserCommissionPayouts.AddRangeAsync(payoutsList, cancellationToken); await _context.SaveChangesAsync(cancellationToken); // ثبت تاریخچه برای هر پرداخت var historyList = new List(); foreach (var payout in payoutsList) { var history = new CommissionPayoutHistory { UserCommissionPayoutId = payout.Id, UserId = payout.UserId, WeekDefinitionId = request.WeekDefinitionId, AmountBefore = 0, AmountAfter = payout.TotalAmount, OldStatus = default(CommissionPayoutStatus), NewStatus = CommissionPayoutStatus.Pending, Action = CommissionPayoutAction.Created, PerformedBy = "System", Reason = "پردازش خودکار کمیسیون هفتگی" }; historyList.Add(history); } await _context.CommissionPayoutHistories.AddRangeAsync(historyList, cancellationToken); await _context.SaveChangesAsync(cancellationToken); return payoutsList.Count; } /// /// پیدا کردن والدین یک کاربر تا 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, long WeekDefinitionId, 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; } }