namespace CMSMicroservice.Application.CommissionCQ.Commands.CalculateWeeklyCommissionPool; public class CalculateWeeklyCommissionPoolCommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IWeekDefinitionRepository _weekRepository; public CalculateWeeklyCommissionPoolCommandHandler( IApplicationDbContext context, IWeekDefinitionRepository weekRepository) { _context = context; _weekRepository = weekRepository; } public async Task Handle(CalculateWeeklyCommissionPoolCommand request, CancellationToken cancellationToken) { // تبدیل WeekNumber به WeekDefinitionId var weekDefinitionId = _weekRepository.GetGregorianWeekNumber(request.WeekDefinitionId); if (weekDefinitionId == null) { throw new InvalidOperationException($"هفته {request.WeekDefinitionId} در سیستم تعریف نشده است"); } // بررسی وجود استخر var existingPool = await _context.WeeklyCommissionPools .FirstOrDefaultAsync(x => x.WeekDefinitionId == request.WeekDefinitionId, cancellationToken); if (existingPool == null) { throw new InvalidOperationException( $"Pool هفته {request.WeekDefinitionId} وجود ندارد. " + "Pool باید در هنگام فعالسازی باشگاه مشتریان ایجاد شده باشد" ); } if (existingPool.IsCalculated && !request.ForceRecalculate) { throw new InvalidOperationException($"استخر کمیسیون هفته {request.WeekDefinitionId} قبلاً محاسبه شده است"); } // بررسی وجود تعادل‌های هفتگی var weeklyBalances = await _context.NetworkWeeklyBalances .Where(x => x.WeekDefinitionId == request.WeekDefinitionId) .ToListAsync(cancellationToken); if (!weeklyBalances.Any()) { throw new InvalidOperationException($"تعادل‌های هفته {request.WeekDefinitionId} هنوز محاسبه نشده است. ابتدا CalculateWeeklyBalances را اجرا کنید"); } // ⭐ Pool از قبل پُر شده (توسط ActivateClubMembership) var totalPoolAmount = existingPool.TotalPoolAmount; // محاسبه مجموع تعادل‌های کل شبکه // نکته: SubordinateBalances اضافه نمی‌کنیم چون وقتی همه TotalBalances رو جمع می‌زنیم، // خودش شامل بالانس‌های زیرمجموعه‌ها هم هست (تکراری نشه) var totalBalancesInNetwork = weeklyBalances.Sum(x => x.TotalBalances); // محاسبه ارزش هر امتیاز long valuePerBalance = 0; if (totalBalancesInNetwork > 0) { valuePerBalance = totalPoolAmount / totalBalancesInNetwork; } // به‌روزرسانی Pool existingPool.TotalBalances = totalBalancesInNetwork; existingPool.ValuePerBalance = valuePerBalance; existingPool.IsCalculated = true; existingPool.CalculatedAt = DateTime.Now; _context.WeeklyCommissionPools.Update(existingPool); await _context.SaveChangesAsync(cancellationToken); // حذف پرداخت‌های قبلی در صورت ForceRecalculate if (request.ForceRecalculate) { var oldPayouts = await _context.UserCommissionPayouts .Where(p => p.WeekDefinitionId == request.WeekDefinitionId) .ToListAsync(cancellationToken); if (oldPayouts.Any()) { var oldPayoutIds = oldPayouts.Select(p => p.Id).ToList(); // ⭐ ۱. برگرداندن NetworkBalance به حالت قبل (Revert) await RevertUserWalletsAsync(oldPayoutIds, cancellationToken); // ⭐ ۲. حذف تاریخچه‌ها (به خاطر FK constraint) var oldHistories = await _context.CommissionPayoutHistories .Where(h => oldPayoutIds.Contains(h.UserCommissionPayoutId)) .ToListAsync(cancellationToken); if (oldHistories.Any()) { _context.CommissionPayoutHistories.RemoveRange(oldHistories); } // ⭐ ۳. حذف پرداخت‌ها _context.UserCommissionPayouts.RemoveRange(oldPayouts); await _context.SaveChangesAsync(cancellationToken); } } // ⭐ ثبت پرداخت برای کاربرانی که تعادل دارند // تعادل شخصی + زیرمجموعه قبلاً در CalculateWeeklyBalances محاسبه شده var payouts = new List(); foreach (var balance in weeklyBalances) { // فقط تعادل شخصی (SubordinateBalances اضافه نمی‌شه چون در SUM کل شبکه خودش حساب میشه) var userBalance = balance.TotalBalances; // اگر تعادل صفر است، نیازی به ثبت نیست if (userBalance <= 0) { continue; } // محاسبه مبلغ کمیسیون var totalAmount = (long)(userBalance * valuePerBalance); var payout = new UserCommissionPayout { UserId = balance.UserId, WeekDefinitionId = request.WeekDefinitionId, WeeklyPoolId = existingPool.Id, BalancesEarned = userBalance, ValuePerBalance = valuePerBalance, TotalAmount = totalAmount, Status = CommissionPayoutStatus.Pending, PaidAt = null, WithdrawalMethod = null, IbanNumber = null, WithdrawnAt = null }; payouts.Add(payout); } if (payouts.Any()) { await _context.UserCommissionPayouts.AddRangeAsync(payouts, cancellationToken); await _context.SaveChangesAsync(cancellationToken); // ثبت تاریخچه برای هر پرداخت var historyList = new List(); foreach (var payout in payouts) { 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); // ⭐ شارژ کیف پول کاربران (NetworkBalance) و ثبت لاگ await ChargeUserWalletsAsync(payouts, request.WeekDefinitionId, cancellationToken); } return existingPool.Id; } /// /// شارژ کیف پول کاربران با مبلغ کمیسیون (به NetworkBalance اضافه می‌شود) /// و ثبت لاگ تغییرات کیف پول /// private async Task ChargeUserWalletsAsync( List payouts, long weekDefinitionId, CancellationToken cancellationToken) { var userIds = payouts.Select(p => p.UserId).Distinct().ToList(); // دریافت کیف پول‌های موجود var existingWallets = await _context.UserWallets .Where(w => userIds.Contains(w.UserId)) .ToDictionaryAsync(w => w.UserId, cancellationToken); var newWallets = new List(); var walletLogs = new List(); foreach (var payout in payouts) { if (payout.TotalAmount <= 0) continue; UserWallet wallet; // پیدا کردن یا ایجاد کیف پول if (existingWallets.TryGetValue(payout.UserId, out var existingWallet)) { wallet = existingWallet; } else { // ایجاد کیف پول جدید wallet = new UserWallet { UserId = payout.UserId, Balance = 0, NetworkBalance = 0, DiscountBalance = 0 }; newWallets.Add(wallet); existingWallets[payout.UserId] = wallet; // برای جلوگیری از تکرار } } // ذخیره کیف پول‌های جدید if (newWallets.Any()) { await _context.UserWallets.AddRangeAsync(newWallets, cancellationToken); await _context.SaveChangesAsync(cancellationToken); } // شارژ کیف پول‌ها و ثبت لاگ foreach (var payout in payouts) { if (payout.TotalAmount <= 0) continue; var wallet = existingWallets[payout.UserId]; // ذخیره موجودی قبلی برای لاگ var previousNetworkBalance = wallet.NetworkBalance; // شارژ NetworkBalance wallet.NetworkBalance += payout.TotalAmount; // ایجاد لاگ تغییر کیف پول var walletLog = new UserWalletChangeLog { WalletId = wallet.Id, CurrentBalance = wallet.Balance, ChangeValue = 0, // Balance اصلی تغییر نمی‌کند CurrentNetworkBalance = wallet.NetworkBalance, ChangeNerworkValue = payout.TotalAmount, CurrentDiscountBalance = wallet.DiscountBalance, ChangeDiscountValue = 0, IsIncrease = true, RefrenceId = payout.Id // ارجاع به UserCommissionPayout }; walletLogs.Add(walletLog); } // ذخیره تغییرات کیف پول و لاگ‌ها await _context.UserWalletChangeLogs.AddRangeAsync(walletLogs, cancellationToken); await _context.SaveChangesAsync(cancellationToken); } /// /// برگرداندن NetworkBalance به حالت قبل از شارژ کمیسیون /// این متد قبل از محاسبه مجدد (ForceRecalculate) اجرا می‌شود /// private async Task RevertUserWalletsAsync( List oldPayoutIds, CancellationToken cancellationToken) { // پیدا کردن لاگ‌های کیف پول مرتبط با پرداخت‌های قبلی var oldWalletLogs = await _context.UserWalletChangeLogs .Where(l => l.RefrenceId.HasValue && oldPayoutIds.Contains(l.RefrenceId.Value)) .ToListAsync(cancellationToken); if (!oldWalletLogs.Any()) return; // پیدا کردن کیف پول‌های مرتبط var walletIds = oldWalletLogs.Select(l => l.WalletId).Distinct().ToList(); var wallets = await _context.UserWallets .Where(w => walletIds.Contains(w.Id)) .ToDictionaryAsync(w => w.Id, cancellationToken); // برگرداندن NetworkBalance foreach (var log in oldWalletLogs) { if (wallets.TryGetValue(log.WalletId, out var wallet)) { // کم کردن مبلغ شارژ شده قبلی از NetworkBalance wallet.NetworkBalance -= log.ChangeNerworkValue; // اطمینان از منفی نشدن if (wallet.NetworkBalance < 0) { wallet.NetworkBalance = 0; } } } // حذف لاگ‌های قبلی _context.UserWalletChangeLogs.RemoveRange(oldWalletLogs); await _context.SaveChangesAsync(cancellationToken); } }