8e5c7c5205
- ActivateClubMembership: Load Package, set First/Last fields, PackageId on Cycle+Pool - AcceptClubMembershipContract: Load Package, set First/Last fields, PackageId on Pool - CalculateWeeklyBalances: Load base package, use package.MaxBalancesPerLeg/MaxNetworkLevel, set PackageId on NetworkWeeklyBalance - ProcessUserPayouts: Load base package, use package.MaxNetworkLevel, set PackageId on UserCommissionPayout - CheckAndProcessDayaLoans: Load base package (IsBasePackage), replace DayaLoanAmount→package.Price, fix hardcoded p.Id==4 - CreateManualPayment: Load base package, replace BasePackageAmount→package.Price, use DiscountMultiplier - InitiateBasePackagePayment: Remove hardcoded BasePackageId=4, load from IsBasePackage, use package.Price/Id - VerifyBasePackagePayment: Load Package from order.PackageId, use package.Price+DiscountMultiplier - ChargeMagicWallet: Load user's package from current cycle, use package.MagicWalletMaxDeposit - VerifyMagicWalletCharge: Load user's package, use package.MagicWalletMultiplier/MagicWalletMaxDeposit - OrmCommissionCalculationStrategy: Load base package in both methods, set PackageId on Balance+Payout - ConfigurationService: Inject IApplicationDbContext, read package values from DB instead of SystemConstants - UserOrderService: Load user's package for EXIT trigger MagicWalletMaxDeposit check - UserWalletService: Load user's package for GetMagicWalletStatus display All 9 deprecated SystemConstants now have ZERO usages outside SystemConstants.cs Build: 0 errors, 521 warnings (all pre-existing)
251 lines
9.5 KiB
C#
251 lines
9.5 KiB
C#
using CMSMicroservice.Domain.Common;
|
|
|
|
namespace CMSMicroservice.Application.CommissionCQ.Commands.ProcessUserPayouts;
|
|
|
|
public class ProcessUserPayoutsCommandHandler : IRequestHandler<ProcessUserPayoutsCommand, int>
|
|
{
|
|
private readonly IApplicationDbContext _context;
|
|
private readonly IWeekDefinitionRepository _weekRepository;
|
|
|
|
public ProcessUserPayoutsCommandHandler(
|
|
IApplicationDbContext context,
|
|
IWeekDefinitionRepository weekRepository)
|
|
{
|
|
_context = context;
|
|
_weekRepository = weekRepository;
|
|
}
|
|
|
|
public async Task<int> 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);
|
|
}
|
|
|
|
// بارگذاری پکیج پایه برای خواندن سقفها
|
|
var package = await _context.Packages
|
|
.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken)
|
|
?? throw new InvalidOperationException("پکیج پایه یافت نشد");
|
|
var maxNetworkLevel = package.MaxNetworkLevel;
|
|
|
|
// دریافت همه تعادلهای هفتگی (شامل صفرها هم برای محاسبه زیرمجموعه)
|
|
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<long>(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<UserCommissionPayout>();
|
|
|
|
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,
|
|
PackageId = package.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<CommissionPayoutHistory>();
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// پیدا کردن والدین یک کاربر تا N لول بالاتر
|
|
/// </summary>
|
|
private async Task<List<long>> GetAncestors(long userId, int maxLevels, CancellationToken cancellationToken)
|
|
{
|
|
var ancestors = new List<long>();
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// محاسبه مجموع تعادلهای زیرمجموعه یک کاربر تا N لول پایینتر
|
|
/// </summary>
|
|
private async Task<int> CalculateSubordinateBalancesAsync(
|
|
long userId,
|
|
long WeekDefinitionId,
|
|
Dictionary<long, NetworkWeeklyBalance> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// پیدا کردن بازگشتی زیرمجموعهها تا N لول
|
|
/// </summary>
|
|
private async Task<List<long>> GetSubordinatesRecursive(
|
|
long userId,
|
|
int currentLevel,
|
|
int maxLevel,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
// محدودیت عمق
|
|
if (currentLevel > maxLevel)
|
|
{
|
|
return new List<long>();
|
|
}
|
|
|
|
var result = new List<long>();
|
|
|
|
// پیدا کردن فرزندان مستقیم
|
|
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;
|
|
}
|
|
}
|