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.CalculateWeeklyCommissionPool;
|
||||
|
||||
/// <summary>
|
||||
/// Command برای محاسبه استخر کمیسیون هفتگی
|
||||
/// </summary>
|
||||
public record CalculateWeeklyCommissionPoolCommand : IRequest<long>
|
||||
{
|
||||
/// <summary>
|
||||
/// شماره هفته (فرمت: YYYY-Www)
|
||||
/// </summary>
|
||||
public string WeekNumber { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// آیا محاسبه مجدد انجام شود؟
|
||||
/// </summary>
|
||||
public bool ForceRecalculate { get; init; }
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Commands.CalculateWeeklyCommissionPool;
|
||||
|
||||
public class CalculateWeeklyCommissionPoolCommandHandler : IRequestHandler<CalculateWeeklyCommissionPoolCommand, long>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public CalculateWeeklyCommissionPoolCommandHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<long> Handle(CalculateWeeklyCommissionPoolCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// بررسی وجود استخر قبلی
|
||||
var existingPool = await _context.WeeklyCommissionPools
|
||||
.FirstOrDefaultAsync(x => x.WeekNumber == request.WeekNumber, cancellationToken);
|
||||
|
||||
if (existingPool != null && existingPool.IsCalculated && !request.ForceRecalculate)
|
||||
{
|
||||
throw new InvalidOperationException($"استخر کمیسیون هفته {request.WeekNumber} قبلاً محاسبه شده است");
|
||||
}
|
||||
|
||||
// بررسی وجود تعادلهای هفتگی
|
||||
var weeklyBalances = await _context.NetworkWeeklyBalances
|
||||
.Where(x => x.WeekNumber == request.WeekNumber)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
if (!weeklyBalances.Any())
|
||||
{
|
||||
throw new InvalidOperationException($"تعادلهای هفته {request.WeekNumber} هنوز محاسبه نشده است. ابتدا CalculateWeeklyBalances را اجرا کنید");
|
||||
}
|
||||
|
||||
// محاسبه مجموع مشارکتها در استخر
|
||||
var totalPoolAmount = weeklyBalances.Sum(x => x.WeeklyPoolContribution);
|
||||
|
||||
// محاسبه مجموع Balances
|
||||
var totalBalances = weeklyBalances.Sum(x => x.TotalBalances);
|
||||
|
||||
// محاسبه ارزش هر Balance (تقسیم صحیح برای ریال)
|
||||
long valuePerBalance = 0;
|
||||
if (totalBalances > 0)
|
||||
{
|
||||
valuePerBalance = totalPoolAmount / totalBalances;
|
||||
}
|
||||
|
||||
if (existingPool != null)
|
||||
{
|
||||
// بهروزرسانی
|
||||
existingPool.TotalPoolAmount = totalPoolAmount;
|
||||
existingPool.TotalBalances = totalBalances;
|
||||
existingPool.ValuePerBalance = valuePerBalance;
|
||||
existingPool.IsCalculated = true;
|
||||
existingPool.CalculatedAt = DateTime.UtcNow;
|
||||
|
||||
_context.WeeklyCommissionPools.Update(existingPool);
|
||||
}
|
||||
else
|
||||
{
|
||||
// ایجاد جدید
|
||||
var pool = new WeeklyCommissionPool
|
||||
{
|
||||
WeekNumber = request.WeekNumber,
|
||||
TotalPoolAmount = totalPoolAmount,
|
||||
TotalBalances = totalBalances,
|
||||
ValuePerBalance = valuePerBalance,
|
||||
IsCalculated = true,
|
||||
CalculatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await _context.WeeklyCommissionPools.AddAsync(pool, cancellationToken);
|
||||
existingPool = pool;
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return existingPool.Id;
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Commands.CalculateWeeklyCommissionPool;
|
||||
|
||||
public class CalculateWeeklyCommissionPoolCommandValidator : AbstractValidator<CalculateWeeklyCommissionPoolCommand>
|
||||
{
|
||||
public CalculateWeeklyCommissionPoolCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.WeekNumber)
|
||||
.NotEmpty()
|
||||
.WithMessage("شماره هفته نمیتواند خالی باشد")
|
||||
.Matches(@"^\d{4}-W\d{2}$")
|
||||
.WithMessage("فرمت شماره هفته باید YYYY-Www باشد");
|
||||
}
|
||||
|
||||
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
||||
{
|
||||
var result = await ValidateAsync(
|
||||
ValidationContext<CalculateWeeklyCommissionPoolCommand>.CreateWithOptions(
|
||||
(CalculateWeeklyCommissionPoolCommand)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