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:
@@ -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; }
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Commands.ProcessUserPayouts;
|
||||
|
||||
/// <summary>
|
||||
/// Command برای پردازش و توزیع کمیسیون به کاربران
|
||||
/// </summary>
|
||||
public record ProcessUserPayoutsCommand : IRequest<int>
|
||||
{
|
||||
/// <summary>
|
||||
/// شماره هفته
|
||||
/// </summary>
|
||||
public string WeekNumber { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// آیا پرداخت مجدد انجام شود؟
|
||||
/// </summary>
|
||||
public bool ForceReprocess { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Commands.ProcessUserPayouts;
|
||||
|
||||
public class ProcessUserPayoutsCommandHandler : IRequestHandler<ProcessUserPayoutsCommand, int>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public ProcessUserPayoutsCommandHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<int> Handle(ProcessUserPayoutsCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// بررسی وجود استخر
|
||||
var pool = await _context.WeeklyCommissionPools
|
||||
.FirstOrDefaultAsync(x => x.WeekNumber == request.WeekNumber, cancellationToken);
|
||||
|
||||
if (pool == null || !pool.IsCalculated)
|
||||
{
|
||||
throw new InvalidOperationException($"استخر کمیسیون هفته {request.WeekNumber} هنوز محاسبه نشده است");
|
||||
}
|
||||
|
||||
// بررسی پرداخت قبلی
|
||||
var existingPayouts = await _context.UserCommissionPayouts
|
||||
.Where(x => x.WeekNumber == request.WeekNumber)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
if (existingPayouts.Any() && !request.ForceReprocess)
|
||||
{
|
||||
throw new InvalidOperationException($"پرداختهای هفته {request.WeekNumber} قبلاً انجام شده است");
|
||||
}
|
||||
|
||||
// حذف پرداختهای قبلی در صورت ForceReprocess
|
||||
if (existingPayouts.Any())
|
||||
{
|
||||
_context.UserCommissionPayouts.RemoveRange(existingPayouts);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
// دریافت تعادلهای هفتگی
|
||||
var weeklyBalances = await _context.NetworkWeeklyBalances
|
||||
.Where(x => x.WeekNumber == request.WeekNumber && x.TotalBalances > 0)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var payoutsList = new List<UserCommissionPayout>();
|
||||
|
||||
foreach (var balance in weeklyBalances)
|
||||
{
|
||||
// محاسبه مبلغ کمیسیون
|
||||
var totalAmount = (long)(balance.TotalBalances * pool.ValuePerBalance);
|
||||
|
||||
var payout = new UserCommissionPayout
|
||||
{
|
||||
UserId = balance.UserId,
|
||||
WeekNumber = request.WeekNumber,
|
||||
WeeklyPoolId = pool.Id,
|
||||
BalancesEarned = balance.TotalBalances,
|
||||
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,
|
||||
WeekNumber = request.WeekNumber,
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Commands.ProcessUserPayouts;
|
||||
|
||||
public class ProcessUserPayoutsCommandValidator : AbstractValidator<ProcessUserPayoutsCommand>
|
||||
{
|
||||
public ProcessUserPayoutsCommandValidator()
|
||||
{
|
||||
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<ProcessUserPayoutsCommand>.CreateWithOptions(
|
||||
(ProcessUserPayoutsCommand)model,
|
||||
x => x.IncludeProperties(propertyName)));
|
||||
|
||||
if (result.IsValid)
|
||||
return Array.Empty<string>();
|
||||
|
||||
return result.Errors.Select(e => e.ErrorMessage);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Commands.ProcessWithdrawal;
|
||||
|
||||
/// <summary>
|
||||
/// Command برای پردازش برداشت (توسط Admin)
|
||||
/// </summary>
|
||||
public record ProcessWithdrawalCommand : IRequest<Unit>
|
||||
{
|
||||
/// <summary>
|
||||
/// شناسه پرداخت کمیسیون
|
||||
/// </summary>
|
||||
public long PayoutId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// آیا تایید شده است؟
|
||||
/// </summary>
|
||||
public bool IsApproved { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// دلیل (در صورت رد)
|
||||
/// </summary>
|
||||
public string? Reason { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Commands.ProcessWithdrawal;
|
||||
|
||||
public class ProcessWithdrawalCommandHandler : IRequestHandler<ProcessWithdrawalCommand, Unit>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public ProcessWithdrawalCommandHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<Unit> Handle(ProcessWithdrawalCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var payout = await _context.UserCommissionPayouts
|
||||
.FirstOrDefaultAsync(x => x.Id == request.PayoutId, cancellationToken);
|
||||
|
||||
if (payout == null)
|
||||
{
|
||||
throw new NotFoundException(nameof(UserCommissionPayout), request.PayoutId);
|
||||
}
|
||||
|
||||
// بررسی وضعیت
|
||||
if (payout.Status != CommissionPayoutStatus.WithdrawRequested)
|
||||
{
|
||||
throw new InvalidOperationException($"فقط درخواستهای با وضعیت WithdrawRequested قابل پردازش هستند. وضعیت فعلی: {payout.Status}");
|
||||
}
|
||||
|
||||
var oldStatus = payout.Status;
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
if (request.IsApproved)
|
||||
{
|
||||
// تایید برداشت
|
||||
payout.Status = CommissionPayoutStatus.Withdrawn;
|
||||
payout.WithdrawnAt = now;
|
||||
|
||||
// اگر روش برداشت Diamond بود، باید مبلغ به کیف پول تخفیف اضافه شود
|
||||
if (payout.WithdrawalMethod == WithdrawalMethod.Diamond)
|
||||
{
|
||||
var wallet = await _context.UserWallets
|
||||
.FirstOrDefaultAsync(x => x.UserId == payout.UserId, cancellationToken);
|
||||
|
||||
if (wallet != null)
|
||||
{
|
||||
wallet.DiscountBalance += payout.TotalAmount;
|
||||
_context.UserWallets.Update(wallet);
|
||||
}
|
||||
}
|
||||
|
||||
_context.UserCommissionPayouts.Update(payout);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// ثبت تاریخچه
|
||||
var history = new CommissionPayoutHistory
|
||||
{
|
||||
UserCommissionPayoutId = payout.Id,
|
||||
UserId = payout.UserId,
|
||||
WeekNumber = payout.WeekNumber,
|
||||
AmountBefore = payout.TotalAmount,
|
||||
AmountAfter = payout.TotalAmount,
|
||||
OldStatus = oldStatus,
|
||||
NewStatus = CommissionPayoutStatus.Withdrawn,
|
||||
Action = CommissionPayoutAction.Withdrawn,
|
||||
PerformedBy = "Admin", // TODO: باید از Current User گرفته شود
|
||||
Reason = $"تایید برداشت به روش {payout.WithdrawalMethod}"
|
||||
};
|
||||
|
||||
await _context.CommissionPayoutHistories.AddAsync(history, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
// رد برداشت - برگشت به وضعیت Paid
|
||||
payout.Status = CommissionPayoutStatus.Paid;
|
||||
payout.WithdrawalMethod = null;
|
||||
payout.IbanNumber = null;
|
||||
|
||||
_context.UserCommissionPayouts.Update(payout);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// ثبت تاریخچه
|
||||
var history = new CommissionPayoutHistory
|
||||
{
|
||||
UserCommissionPayoutId = payout.Id,
|
||||
UserId = payout.UserId,
|
||||
WeekNumber = payout.WeekNumber,
|
||||
AmountBefore = payout.TotalAmount,
|
||||
AmountAfter = payout.TotalAmount,
|
||||
OldStatus = oldStatus,
|
||||
NewStatus = CommissionPayoutStatus.Paid,
|
||||
Action = CommissionPayoutAction.Cancelled,
|
||||
PerformedBy = "Admin", // TODO: باید از Current User گرفته شود
|
||||
Reason = request.Reason ?? "درخواست برداشت رد شد"
|
||||
};
|
||||
|
||||
await _context.CommissionPayoutHistories.AddAsync(history, cancellationToken);
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return Unit.Value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Commands.ProcessWithdrawal;
|
||||
|
||||
public class ProcessWithdrawalCommandValidator : AbstractValidator<ProcessWithdrawalCommand>
|
||||
{
|
||||
public ProcessWithdrawalCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.PayoutId)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("شناسه پرداخت معتبر نیست");
|
||||
|
||||
RuleFor(x => x.Reason)
|
||||
.NotEmpty()
|
||||
.WithMessage("دلیل رد الزامی است")
|
||||
.MaximumLength(500)
|
||||
.WithMessage("طول دلیل نباید بیشتر از 500 کاراکتر باشد")
|
||||
.When(x => !x.IsApproved);
|
||||
}
|
||||
|
||||
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
||||
{
|
||||
var result = await ValidateAsync(
|
||||
ValidationContext<ProcessWithdrawalCommand>.CreateWithOptions(
|
||||
(ProcessWithdrawalCommand)model,
|
||||
x => x.IncludeProperties(propertyName)));
|
||||
|
||||
if (result.IsValid)
|
||||
return Array.Empty<string>();
|
||||
|
||||
return result.Errors.Select(e => e.ErrorMessage);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Commands.RequestWithdrawal;
|
||||
|
||||
/// <summary>
|
||||
/// Command برای درخواست برداشت کمیسیون
|
||||
/// </summary>
|
||||
public record RequestWithdrawalCommand : IRequest<Unit>
|
||||
{
|
||||
/// <summary>
|
||||
/// شناسه پرداخت کمیسیون
|
||||
/// </summary>
|
||||
public long PayoutId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// روش برداشت (Cash یا Diamond)
|
||||
/// </summary>
|
||||
public WithdrawalMethod Method { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// شماره شبا (برای Cash)
|
||||
/// </summary>
|
||||
public string? IbanNumber { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Commands.RequestWithdrawal;
|
||||
|
||||
public class RequestWithdrawalCommandHandler : IRequestHandler<RequestWithdrawalCommand, Unit>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public RequestWithdrawalCommandHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<Unit> Handle(RequestWithdrawalCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var payout = await _context.UserCommissionPayouts
|
||||
.FirstOrDefaultAsync(x => x.Id == request.PayoutId, cancellationToken);
|
||||
|
||||
if (payout == null)
|
||||
{
|
||||
throw new NotFoundException(nameof(UserCommissionPayout), request.PayoutId);
|
||||
}
|
||||
|
||||
// بررسی وضعیت
|
||||
if (payout.Status != CommissionPayoutStatus.Paid)
|
||||
{
|
||||
throw new InvalidOperationException($"فقط پرداختهای با وضعیت Paid قابل برداشت هستند. وضعیت فعلی: {payout.Status}");
|
||||
}
|
||||
|
||||
var oldStatus = payout.Status;
|
||||
|
||||
// بهروزرسانی وضعیت
|
||||
payout.Status = CommissionPayoutStatus.WithdrawRequested;
|
||||
payout.WithdrawalMethod = request.Method;
|
||||
|
||||
if (request.Method == WithdrawalMethod.Cash)
|
||||
{
|
||||
payout.IbanNumber = request.IbanNumber;
|
||||
}
|
||||
|
||||
_context.UserCommissionPayouts.Update(payout);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// ثبت تاریخچه
|
||||
var history = new CommissionPayoutHistory
|
||||
{
|
||||
UserCommissionPayoutId = payout.Id,
|
||||
UserId = payout.UserId,
|
||||
WeekNumber = payout.WeekNumber,
|
||||
AmountBefore = payout.TotalAmount,
|
||||
AmountAfter = payout.TotalAmount,
|
||||
OldStatus = oldStatus,
|
||||
NewStatus = CommissionPayoutStatus.WithdrawRequested,
|
||||
Action = CommissionPayoutAction.WithdrawRequested,
|
||||
PerformedBy = "User", // TODO: باید از Current User گرفته شود
|
||||
Reason = $"درخواست برداشت به روش {request.Method}"
|
||||
};
|
||||
|
||||
await _context.CommissionPayoutHistories.AddAsync(history, cancellationToken);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return Unit.Value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Commands.RequestWithdrawal;
|
||||
|
||||
public class RequestWithdrawalCommandValidator : AbstractValidator<RequestWithdrawalCommand>
|
||||
{
|
||||
public RequestWithdrawalCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.PayoutId)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("شناسه پرداخت معتبر نیست");
|
||||
|
||||
RuleFor(x => x.Method)
|
||||
.IsInEnum()
|
||||
.WithMessage("روش برداشت باید Cash یا Diamond باشد");
|
||||
|
||||
RuleFor(x => x.IbanNumber)
|
||||
.NotEmpty()
|
||||
.WithMessage("شماره شبا الزامی است")
|
||||
.Matches(@"^IR\d{24}$")
|
||||
.WithMessage("فرمت شماره شبا معتبر نیست (IR + 24 رقم)")
|
||||
.When(x => x.Method == WithdrawalMethod.Cash);
|
||||
}
|
||||
|
||||
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
||||
{
|
||||
var result = await ValidateAsync(
|
||||
ValidationContext<RequestWithdrawalCommand>.CreateWithOptions(
|
||||
(RequestWithdrawalCommand)model,
|
||||
x => x.IncludeProperties(propertyName)));
|
||||
|
||||
if (result.IsValid)
|
||||
return Array.Empty<string>();
|
||||
|
||||
return result.Errors.Select(e => e.ErrorMessage);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetCommissionPayoutHistory;
|
||||
|
||||
/// <summary>
|
||||
/// Query برای دریافت تاریخچه تغییرات کمیسیون
|
||||
/// </summary>
|
||||
public record GetCommissionPayoutHistoryQuery : IRequest<GetCommissionPayoutHistoryResponseDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// شناسه پرداخت (اختیاری)
|
||||
/// </summary>
|
||||
public long? PayoutId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه کاربر (اختیاری)
|
||||
/// </summary>
|
||||
public long? UserId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// شماره هفته (اختیاری)
|
||||
/// </summary>
|
||||
public string? WeekNumber { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// مرتبسازی
|
||||
/// </summary>
|
||||
public string? SortBy { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Pagination
|
||||
/// </summary>
|
||||
public PaginationState? PaginationState { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetCommissionPayoutHistory;
|
||||
|
||||
public class GetCommissionPayoutHistoryQueryHandler : IRequestHandler<GetCommissionPayoutHistoryQuery, GetCommissionPayoutHistoryResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public GetCommissionPayoutHistoryQueryHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<GetCommissionPayoutHistoryResponseDto> Handle(GetCommissionPayoutHistoryQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var query = _context.CommissionPayoutHistories
|
||||
.AsNoTracking()
|
||||
.AsQueryable();
|
||||
|
||||
// فیلترها
|
||||
if (request.PayoutId.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.UserCommissionPayoutId == request.PayoutId.Value);
|
||||
}
|
||||
|
||||
if (request.UserId.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.UserId == request.UserId.Value);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(request.WeekNumber))
|
||||
{
|
||||
query = query.Where(x => x.WeekNumber == request.WeekNumber);
|
||||
}
|
||||
|
||||
query = query.ApplyOrder(sortBy: request.SortBy ?? "-Created");
|
||||
|
||||
var meta = await query.GetMetaData(request.PaginationState, cancellationToken);
|
||||
|
||||
var models = await query
|
||||
.PaginatedListAsync(paginationState: request.PaginationState)
|
||||
.Select(x => new GetCommissionPayoutHistoryResponseModel
|
||||
{
|
||||
Id = x.Id,
|
||||
UserCommissionPayoutId = x.UserCommissionPayoutId,
|
||||
UserId = x.UserId,
|
||||
WeekNumber = x.WeekNumber,
|
||||
AmountBefore = x.AmountBefore,
|
||||
AmountAfter = x.AmountAfter,
|
||||
OldStatus = x.OldStatus,
|
||||
NewStatus = x.NewStatus,
|
||||
Action = x.Action,
|
||||
PerformedBy = x.PerformedBy,
|
||||
Reason = x.Reason,
|
||||
Created = x.Created
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return new GetCommissionPayoutHistoryResponseDto
|
||||
{
|
||||
MetaData = meta,
|
||||
Models = models
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetCommissionPayoutHistory;
|
||||
|
||||
public class GetCommissionPayoutHistoryQueryValidator : AbstractValidator<GetCommissionPayoutHistoryQuery>
|
||||
{
|
||||
public GetCommissionPayoutHistoryQueryValidator()
|
||||
{
|
||||
RuleFor(x => x.PayoutId)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("شناسه پرداخت معتبر نیست")
|
||||
.When(x => x.PayoutId.HasValue);
|
||||
|
||||
RuleFor(x => x.UserId)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("شناسه کاربر معتبر نیست")
|
||||
.When(x => x.UserId.HasValue);
|
||||
|
||||
RuleFor(x => x.WeekNumber)
|
||||
.Matches(@"^\d{4}-W\d{2}$")
|
||||
.WithMessage("فرمت شماره هفته باید YYYY-Www باشد")
|
||||
.When(x => !string.IsNullOrEmpty(x.WeekNumber));
|
||||
}
|
||||
|
||||
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
||||
{
|
||||
var result = await ValidateAsync(
|
||||
ValidationContext<GetCommissionPayoutHistoryQuery>.CreateWithOptions(
|
||||
(GetCommissionPayoutHistoryQuery)model,
|
||||
x => x.IncludeProperties(propertyName)));
|
||||
|
||||
if (result.IsValid)
|
||||
return Array.Empty<string>();
|
||||
|
||||
return result.Errors.Select(e => e.ErrorMessage);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetCommissionPayoutHistory;
|
||||
|
||||
public class GetCommissionPayoutHistoryResponseDto
|
||||
{
|
||||
public MetaData MetaData { get; set; }
|
||||
public List<GetCommissionPayoutHistoryResponseModel> Models { get; set; }
|
||||
}
|
||||
|
||||
public class GetCommissionPayoutHistoryResponseModel
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public long UserCommissionPayoutId { get; set; }
|
||||
public long UserId { get; set; }
|
||||
public string WeekNumber { get; set; } = string.Empty;
|
||||
public long AmountBefore { get; set; }
|
||||
public long AmountAfter { get; set; }
|
||||
public CommissionPayoutStatus? OldStatus { get; set; }
|
||||
public CommissionPayoutStatus NewStatus { get; set; }
|
||||
public CommissionPayoutAction Action { get; set; }
|
||||
public string? PerformedBy { get; set; }
|
||||
public string? Reason { get; set; }
|
||||
public DateTimeOffset Created { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetUserCommissionPayouts;
|
||||
|
||||
/// <summary>
|
||||
/// Query برای دریافت پرداختهای کمیسیون کاربر
|
||||
/// </summary>
|
||||
public record GetUserCommissionPayoutsQuery : IRequest<GetUserCommissionPayoutsResponseDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// شناسه کاربر (اختیاری)
|
||||
/// </summary>
|
||||
public long? UserId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// فیلتر وضعیت
|
||||
/// </summary>
|
||||
public CommissionPayoutStatus? Status { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// شماره هفته (اختیاری)
|
||||
/// </summary>
|
||||
public string? WeekNumber { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// مرتبسازی
|
||||
/// </summary>
|
||||
public string? SortBy { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Pagination
|
||||
/// </summary>
|
||||
public PaginationState? PaginationState { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetUserCommissionPayouts;
|
||||
|
||||
public class GetUserCommissionPayoutsQueryHandler : IRequestHandler<GetUserCommissionPayoutsQuery, GetUserCommissionPayoutsResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public GetUserCommissionPayoutsQueryHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<GetUserCommissionPayoutsResponseDto> Handle(GetUserCommissionPayoutsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var query = _context.UserCommissionPayouts
|
||||
.AsNoTracking()
|
||||
.AsQueryable();
|
||||
|
||||
// فیلترها
|
||||
if (request.UserId.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.UserId == request.UserId.Value);
|
||||
}
|
||||
|
||||
if (request.Status.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.Status == request.Status.Value);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(request.WeekNumber))
|
||||
{
|
||||
query = query.Where(x => x.WeekNumber == request.WeekNumber);
|
||||
}
|
||||
|
||||
query = query.ApplyOrder(sortBy: request.SortBy ?? "-Created");
|
||||
|
||||
var meta = await query.GetMetaData(request.PaginationState, cancellationToken);
|
||||
|
||||
var models = await query
|
||||
.PaginatedListAsync(paginationState: request.PaginationState)
|
||||
.Select(x => new GetUserCommissionPayoutsResponseModel
|
||||
{
|
||||
Id = x.Id,
|
||||
UserId = x.UserId,
|
||||
WeekNumber = x.WeekNumber,
|
||||
WeeklyPoolId = x.WeeklyPoolId,
|
||||
BalancesEarned = x.BalancesEarned,
|
||||
ValuePerBalance = x.ValuePerBalance,
|
||||
TotalAmount = x.TotalAmount,
|
||||
Status = x.Status,
|
||||
PaidAt = x.PaidAt,
|
||||
WithdrawalMethod = x.WithdrawalMethod,
|
||||
IbanNumber = x.IbanNumber,
|
||||
WithdrawnAt = x.WithdrawnAt,
|
||||
Created = x.Created
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return new GetUserCommissionPayoutsResponseDto
|
||||
{
|
||||
MetaData = meta,
|
||||
Models = models
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetUserCommissionPayouts;
|
||||
|
||||
public class GetUserCommissionPayoutsQueryValidator : AbstractValidator<GetUserCommissionPayoutsQuery>
|
||||
{
|
||||
public GetUserCommissionPayoutsQueryValidator()
|
||||
{
|
||||
RuleFor(x => x.UserId)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("شناسه کاربر معتبر نیست")
|
||||
.When(x => x.UserId.HasValue);
|
||||
|
||||
RuleFor(x => x.Status)
|
||||
.IsInEnum()
|
||||
.WithMessage("وضعیت معتبر نیست")
|
||||
.When(x => x.Status.HasValue);
|
||||
|
||||
RuleFor(x => x.WeekNumber)
|
||||
.Matches(@"^\d{4}-W\d{2}$")
|
||||
.WithMessage("فرمت شماره هفته باید YYYY-Www باشد")
|
||||
.When(x => !string.IsNullOrEmpty(x.WeekNumber));
|
||||
}
|
||||
|
||||
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
||||
{
|
||||
var result = await ValidateAsync(
|
||||
ValidationContext<GetUserCommissionPayoutsQuery>.CreateWithOptions(
|
||||
(GetUserCommissionPayoutsQuery)model,
|
||||
x => x.IncludeProperties(propertyName)));
|
||||
|
||||
if (result.IsValid)
|
||||
return Array.Empty<string>();
|
||||
|
||||
return result.Errors.Select(e => e.ErrorMessage);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetUserCommissionPayouts;
|
||||
|
||||
public class GetUserCommissionPayoutsResponseDto
|
||||
{
|
||||
public MetaData MetaData { get; set; }
|
||||
public List<GetUserCommissionPayoutsResponseModel> Models { get; set; }
|
||||
}
|
||||
|
||||
public class GetUserCommissionPayoutsResponseModel
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public long UserId { get; set; }
|
||||
public string WeekNumber { get; set; } = string.Empty;
|
||||
public long WeeklyPoolId { get; set; }
|
||||
public long BalancesEarned { get; set; }
|
||||
public decimal ValuePerBalance { get; set; }
|
||||
public long TotalAmount { get; set; }
|
||||
public CommissionPayoutStatus Status { get; set; }
|
||||
public DateTime? PaidAt { get; set; }
|
||||
public WithdrawalMethod? WithdrawalMethod { get; set; }
|
||||
public string? IbanNumber { get; set; }
|
||||
public DateTime? WithdrawnAt { get; set; }
|
||||
public DateTimeOffset Created { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetUserWeeklyBalances;
|
||||
|
||||
/// <summary>
|
||||
/// Query برای دریافت تعادلهای هفتگی کاربر
|
||||
/// </summary>
|
||||
public record GetUserWeeklyBalancesQuery : IRequest<GetUserWeeklyBalancesResponseDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// شناسه کاربر (اختیاری)
|
||||
/// </summary>
|
||||
public long? UserId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// شماره هفته (اختیاری)
|
||||
/// </summary>
|
||||
public string? WeekNumber { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// فقط موارد Expired نشده؟
|
||||
/// </summary>
|
||||
public bool? OnlyActive { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// مرتبسازی
|
||||
/// </summary>
|
||||
public string? SortBy { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Pagination
|
||||
/// </summary>
|
||||
public PaginationState? PaginationState { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetUserWeeklyBalances;
|
||||
|
||||
public class GetUserWeeklyBalancesQueryHandler : IRequestHandler<GetUserWeeklyBalancesQuery, GetUserWeeklyBalancesResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public GetUserWeeklyBalancesQueryHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<GetUserWeeklyBalancesResponseDto> Handle(GetUserWeeklyBalancesQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var query = _context.NetworkWeeklyBalances
|
||||
.AsNoTracking()
|
||||
.AsQueryable();
|
||||
|
||||
// فیلترها
|
||||
if (request.UserId.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.UserId == request.UserId.Value);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(request.WeekNumber))
|
||||
{
|
||||
query = query.Where(x => x.WeekNumber == request.WeekNumber);
|
||||
}
|
||||
|
||||
if (request.OnlyActive.HasValue && request.OnlyActive.Value)
|
||||
{
|
||||
query = query.Where(x => !x.IsExpired);
|
||||
}
|
||||
|
||||
query = query.ApplyOrder(sortBy: request.SortBy ?? "-WeekNumber");
|
||||
|
||||
var meta = await query.GetMetaData(request.PaginationState, cancellationToken);
|
||||
|
||||
var models = await query
|
||||
.PaginatedListAsync(paginationState: request.PaginationState)
|
||||
.Select(x => new GetUserWeeklyBalancesResponseModel
|
||||
{
|
||||
Id = x.Id,
|
||||
UserId = x.UserId,
|
||||
WeekNumber = x.WeekNumber,
|
||||
LeftLegBalances = x.LeftLegBalances,
|
||||
RightLegBalances = x.RightLegBalances,
|
||||
TotalBalances = x.TotalBalances,
|
||||
WeeklyPoolContribution = x.WeeklyPoolContribution,
|
||||
CalculatedAt = x.CalculatedAt,
|
||||
IsExpired = x.IsExpired,
|
||||
Created = x.Created
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return new GetUserWeeklyBalancesResponseDto
|
||||
{
|
||||
MetaData = meta,
|
||||
Models = models
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetUserWeeklyBalances;
|
||||
|
||||
public class GetUserWeeklyBalancesQueryValidator : AbstractValidator<GetUserWeeklyBalancesQuery>
|
||||
{
|
||||
public GetUserWeeklyBalancesQueryValidator()
|
||||
{
|
||||
RuleFor(x => x.UserId)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("شناسه کاربر معتبر نیست")
|
||||
.When(x => x.UserId.HasValue);
|
||||
|
||||
RuleFor(x => x.WeekNumber)
|
||||
.Matches(@"^\d{4}-W\d{2}$")
|
||||
.WithMessage("فرمت شماره هفته باید YYYY-Www باشد")
|
||||
.When(x => !string.IsNullOrEmpty(x.WeekNumber));
|
||||
}
|
||||
|
||||
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
||||
{
|
||||
var result = await ValidateAsync(
|
||||
ValidationContext<GetUserWeeklyBalancesQuery>.CreateWithOptions(
|
||||
(GetUserWeeklyBalancesQuery)model,
|
||||
x => x.IncludeProperties(propertyName)));
|
||||
|
||||
if (result.IsValid)
|
||||
return Array.Empty<string>();
|
||||
|
||||
return result.Errors.Select(e => e.ErrorMessage);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetUserWeeklyBalances;
|
||||
|
||||
public class GetUserWeeklyBalancesResponseDto
|
||||
{
|
||||
public MetaData MetaData { get; set; }
|
||||
public List<GetUserWeeklyBalancesResponseModel> Models { get; set; }
|
||||
}
|
||||
|
||||
public class GetUserWeeklyBalancesResponseModel
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public long UserId { get; set; }
|
||||
public string WeekNumber { get; set; } = string.Empty;
|
||||
public int LeftLegBalances { get; set; }
|
||||
public int RightLegBalances { get; set; }
|
||||
public int TotalBalances { get; set; }
|
||||
public long WeeklyPoolContribution { get; set; }
|
||||
public DateTime? CalculatedAt { get; set; }
|
||||
public bool IsExpired { get; set; }
|
||||
public DateTimeOffset Created { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWeeklyCommissionPool;
|
||||
|
||||
/// <summary>
|
||||
/// Query برای دریافت استخر کمیسیون هفتگی
|
||||
/// </summary>
|
||||
public record GetWeeklyCommissionPoolQuery : IRequest<WeeklyCommissionPoolDto?>
|
||||
{
|
||||
/// <summary>
|
||||
/// شماره هفته
|
||||
/// </summary>
|
||||
public string WeekNumber { get; init; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWeeklyCommissionPool;
|
||||
|
||||
public class GetWeeklyCommissionPoolQueryHandler : IRequestHandler<GetWeeklyCommissionPoolQuery, WeeklyCommissionPoolDto?>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public GetWeeklyCommissionPoolQueryHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<WeeklyCommissionPoolDto?> Handle(GetWeeklyCommissionPoolQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var pool = await _context.WeeklyCommissionPools
|
||||
.AsNoTracking()
|
||||
.Where(x => x.WeekNumber == request.WeekNumber)
|
||||
.Select(x => new WeeklyCommissionPoolDto
|
||||
{
|
||||
Id = x.Id,
|
||||
WeekNumber = x.WeekNumber,
|
||||
TotalPoolAmount = x.TotalPoolAmount,
|
||||
TotalBalances = x.TotalBalances,
|
||||
ValuePerBalance = x.ValuePerBalance,
|
||||
IsCalculated = x.IsCalculated,
|
||||
CalculatedAt = x.CalculatedAt,
|
||||
Created = x.Created
|
||||
})
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
return pool;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWeeklyCommissionPool;
|
||||
|
||||
public class GetWeeklyCommissionPoolQueryValidator : AbstractValidator<GetWeeklyCommissionPoolQuery>
|
||||
{
|
||||
public GetWeeklyCommissionPoolQueryValidator()
|
||||
{
|
||||
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<GetWeeklyCommissionPoolQuery>.CreateWithOptions(
|
||||
(GetWeeklyCommissionPoolQuery)model,
|
||||
x => x.IncludeProperties(propertyName)));
|
||||
|
||||
if (result.IsValid)
|
||||
return Array.Empty<string>();
|
||||
|
||||
return result.Errors.Select(e => e.ErrorMessage);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWeeklyCommissionPool;
|
||||
|
||||
/// <summary>
|
||||
/// DTO برای استخر کمیسیون هفتگی
|
||||
/// </summary>
|
||||
public class WeeklyCommissionPoolDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string WeekNumber { get; set; } = string.Empty;
|
||||
public long TotalPoolAmount { get; set; }
|
||||
public long TotalBalances { get; set; }
|
||||
public decimal ValuePerBalance { get; set; }
|
||||
public bool IsCalculated { get; set; }
|
||||
public DateTime? CalculatedAt { get; set; }
|
||||
public DateTimeOffset Created { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user