329 lines
13 KiB
C#
329 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 existingPoolQuery = _context.WeeklyCommissionPools
|
|
.Where(x => x.WeekDefinitionId == request.WeekDefinitionId);
|
|
|
|
if (request.PackageId.HasValue)
|
|
{
|
|
existingPoolQuery = existingPoolQuery.Where(x => x.PackageId == request.PackageId.Value);
|
|
}
|
|
|
|
var existingPool = await existingPoolQuery
|
|
.OrderBy(x => x.Id)
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
|
|
if (existingPool == null)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Pool هفته {request.WeekDefinitionId} وجود ندارد. " +
|
|
"Pool باید در هنگام فعالسازی باشگاه مشتریان ایجاد شده باشد"
|
|
);
|
|
}
|
|
|
|
if (existingPool.IsCalculated && !request.ForceRecalculate)
|
|
{
|
|
throw new InvalidOperationException($"استخر کمیسیون هفته {request.WeekDefinitionId} قبلاً محاسبه شده است");
|
|
}
|
|
|
|
// بررسی وجود تعادلهای هفتگی
|
|
var weeklyBalancesQuery = _context.NetworkWeeklyBalances
|
|
.Where(x => x.WeekDefinitionId == request.WeekDefinitionId);
|
|
|
|
if (request.PackageId.HasValue)
|
|
{
|
|
weeklyBalancesQuery = weeklyBalancesQuery.Where(x => x.PackageId == request.PackageId.Value);
|
|
}
|
|
|
|
var weeklyBalances = await weeklyBalancesQuery.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 oldPayoutsQuery = _context.UserCommissionPayouts
|
|
.Where(p => p.WeekDefinitionId == request.WeekDefinitionId);
|
|
|
|
if (request.PackageId.HasValue)
|
|
{
|
|
oldPayoutsQuery = oldPayoutsQuery.Where(p => p.PackageId == request.PackageId.Value);
|
|
}
|
|
|
|
var oldPayouts = await oldPayoutsQuery.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<UserWalletHistory>();
|
|
|
|
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 UserWalletHistory
|
|
{
|
|
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.UserWalletHistories.AddRangeAsync(walletLogs, cancellationToken);
|
|
await _context.SaveChangesAsync(cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// برگرداندن NetworkBalance به حالت قبل از شارژ کمیسیون
|
|
/// این متد قبل از محاسبه مجدد (ForceRecalculate) اجرا میشود
|
|
/// </summary>
|
|
private async Task RevertUserWalletsAsync(
|
|
List<long> oldPayoutIds,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
// پیدا کردن لاگهای کیف پول مرتبط با پرداختهای قبلی
|
|
var oldWalletLogs = await _context.UserWalletHistories
|
|
.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.UserWalletHistories.RemoveRange(oldWalletLogs);
|
|
|
|
await _context.SaveChangesAsync(cancellationToken);
|
|
}
|
|
}
|