Files
CMS/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyCommissionPool/CalculateWeeklyCommissionPoolCommandHandler.cs
T

308 lines
13 KiB
C#

namespace CMSMicroservice.Application.CommissionCQ.Commands.CalculateWeeklyCommissionPool;
public class CalculateWeeklyCommissionPoolCommandHandler : IRequestHandler<CalculateWeeklyCommissionPoolCommand, long>
{
private readonly IApplicationDbContext _context;
private readonly IWeekDefinitionRepository _weekRepository;
public CalculateWeeklyCommissionPoolCommandHandler(
IApplicationDbContext context,
IWeekDefinitionRepository weekRepository)
{
_context = context;
_weekRepository = weekRepository;
}
public async Task<long> 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<UserCommissionPayout>();
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<CommissionPayoutHistory>();
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;
}
/// <summary>
/// شارژ کیف پول کاربران با مبلغ کمیسیون (به NetworkBalance اضافه می‌شود)
/// و ثبت لاگ تغییرات کیف پول
/// </summary>
private async Task ChargeUserWalletsAsync(
List<UserCommissionPayout> 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<UserWallet>();
var walletLogs = new List<UserWalletChangeLog>();
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);
}
/// <summary>
/// برگرداندن NetworkBalance به حالت قبل از شارژ کمیسیون
/// این متد قبل از محاسبه مجدد (ForceRecalculate) اجرا می‌شود
/// </summary>
private async Task RevertUserWalletsAsync(
List<long> 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);
}
}