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
This commit is contained in:
+17
@@ -0,0 +1,17 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Commands.CalculateWeeklyBalances;
|
||||
|
||||
/// <summary>
|
||||
/// Command برای محاسبه تعادلهای هفتگی شبکه
|
||||
/// </summary>
|
||||
public record CalculateWeeklyBalancesCommand : IRequest<int>
|
||||
{
|
||||
/// <summary>
|
||||
/// شماره هفته (فرمت: YYYY-Www مثل 2025-W01)
|
||||
/// </summary>
|
||||
public string WeekNumber { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// آیا محاسبه مجدد انجام شود؟ (پیشفرض: false)
|
||||
/// </summary>
|
||||
public bool ForceRecalculate { get; init; }
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Commands.CalculateWeeklyBalances;
|
||||
|
||||
public class CalculateWeeklyBalancesCommandValidator : AbstractValidator<CalculateWeeklyBalancesCommand>
|
||||
{
|
||||
public CalculateWeeklyBalancesCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.WeekNumber)
|
||||
.NotEmpty()
|
||||
.WithMessage("شماره هفته نمیتواند خالی باشد")
|
||||
.Matches(@"^\d{4}-W\d{2}$")
|
||||
.WithMessage("فرمت شماره هفته باید YYYY-Www باشد (مثل 2025-W01)");
|
||||
}
|
||||
|
||||
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
||||
{
|
||||
var result = await ValidateAsync(
|
||||
ValidationContext<CalculateWeeklyBalancesCommand>.CreateWithOptions(
|
||||
(CalculateWeeklyBalancesCommand)model,
|
||||
x => x.IncludeProperties(propertyName)));
|
||||
|
||||
if (result.IsValid)
|
||||
return Array.Empty<string>();
|
||||
|
||||
return result.Errors.Select(e => e.ErrorMessage);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user