Files
CMS/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyBalances/CalculateWeeklyBalancesCommandHandler.cs
masoodafar-web 487d1ceb15 feat: Add CommissionCQ - Phase 5 Application Layer
Added 5 Commands and 4 Queries for commission calculation and payout system:

Commands:
- CalculateWeeklyBalances: Recursive binary tree traversal for leg balances
- CalculateWeeklyCommissionPool: Calculate ValuePerBalance from total pool
- ProcessUserPayouts: Distribute commission to users, create payout records
- RequestWithdrawal: User requests cash/diamond withdrawal
- ProcessWithdrawal: Admin approves/rejects withdrawal

Queries:
- GetWeeklyCommissionPool: Retrieve pool details
- GetUserCommissionPayouts: List payouts with filters (status, week, user)
- GetCommissionPayoutHistory: Complete audit trail
- GetUserWeeklyBalances: Show leg balances and contributions

Total: 35 files, ~1,100 lines of code
Binary tree algorithm, state machine, withdrawal system implemented
2025-11-29 04:32:17 +03:30

96 lines
4.0 KiB
C#

namespace CMSMicroservice.Application.CommissionCQ.Commands.CalculateWeeklyBalances;
public class CalculateWeeklyBalancesCommandHandler : IRequestHandler<CalculateWeeklyBalancesCommand, int>
{
private readonly IApplicationDbContext _context;
public CalculateWeeklyBalancesCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<int> Handle(CalculateWeeklyBalancesCommand request, CancellationToken cancellationToken)
{
// بررسی وجود محاسبه قبلی
var existingBalances = await _context.NetworkWeeklyBalances
.Where(x => x.WeekNumber == request.WeekNumber)
.ToListAsync(cancellationToken);
if (existingBalances.Any() && !request.ForceRecalculate)
{
throw new InvalidOperationException($"تعادل‌های هفته {request.WeekNumber} قبلاً محاسبه شده است. برای محاسبه مجدد از ForceRecalculate استفاده کنید");
}
// حذف محاسبات قبلی در صورت ForceRecalculate
if (existingBalances.Any())
{
_context.NetworkWeeklyBalances.RemoveRange(existingBalances);
await _context.SaveChangesAsync(cancellationToken);
}
// دریافت کاربران فعال در شبکه
var usersInNetwork = await _context.Users
.Where(x => x.NetworkParentId.HasValue)
.Select(x => new { x.Id })
.ToListAsync(cancellationToken);
var balancesList = new List<NetworkWeeklyBalance>();
var calculatedAt = DateTime.UtcNow;
foreach (var user in usersInNetwork)
{
// محاسبه تعادل پای چپ (Left Leg)
var leftLegBalances = (int)await CalculateLegBalances(user.Id, NetworkLeg.Left, cancellationToken);
// محاسبه تعادل پای راست (Right Leg)
var rightLegBalances = (int)await CalculateLegBalances(user.Id, NetworkLeg.Right, cancellationToken);
// محاسبه Total Balances (کمترین مقدار دو پا)
var totalBalances = Math.Min(leftLegBalances, rightLegBalances);
// محاسبه سهم استخر (10% از Total Balances)
var weeklyPoolContribution = (long)(totalBalances * 0.10m);
var balance = new NetworkWeeklyBalance
{
UserId = user.Id,
WeekNumber = request.WeekNumber,
LeftLegBalances = leftLegBalances,
RightLegBalances = rightLegBalances,
TotalBalances = totalBalances,
WeeklyPoolContribution = weeklyPoolContribution,
CalculatedAt = calculatedAt,
IsExpired = false
};
balancesList.Add(balance);
}
await _context.NetworkWeeklyBalances.AddRangeAsync(balancesList, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
return balancesList.Count;
}
/// <summary>
/// محاسبه تعادل یک پا (Left یا Right) به صورت بازگشتی
/// </summary>
private async Task<long> CalculateLegBalances(long userId, NetworkLeg leg, CancellationToken cancellationToken)
{
// پیدا کردن فرزند در پای مورد نظر
var child = await _context.Users
.FirstOrDefaultAsync(x => x.NetworkParentId == userId && x.LegPosition == leg, cancellationToken);
if (child == null)
{
return 0; // اگر فرزندی نداشته باشد، تعادل صفر است
}
// محاسبه بازگشتی: مجموع تعادل فرزند چپ + راست + 1 (خود فرزند)
var childLeftLeg = await CalculateLegBalances(child.Id, NetworkLeg.Left, cancellationToken);
var childRightLeg = await CalculateLegBalances(child.Id, NetworkLeg.Right, cancellationToken);
return 1 + childLeftLeg + childRightLeg;
}
}