diff --git a/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyBalances/CalculateWeeklyBalancesCommand.cs b/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyBalances/CalculateWeeklyBalancesCommand.cs new file mode 100644 index 0000000..7ce9bfb --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyBalances/CalculateWeeklyBalancesCommand.cs @@ -0,0 +1,17 @@ +namespace CMSMicroservice.Application.CommissionCQ.Commands.CalculateWeeklyBalances; + +/// +/// Command برای محاسبه تعادل‌های هفتگی شبکه +/// +public record CalculateWeeklyBalancesCommand : IRequest +{ + /// + /// شماره هفته (فرمت: YYYY-Www مثل 2025-W01) + /// + public string WeekNumber { get; init; } = string.Empty; + + /// + /// آیا محاسبه مجدد انجام شود؟ (پیش‌فرض: false) + /// + public bool ForceRecalculate { get; init; } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyBalances/CalculateWeeklyBalancesCommandHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyBalances/CalculateWeeklyBalancesCommandHandler.cs new file mode 100644 index 0000000..27f6ac9 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyBalances/CalculateWeeklyBalancesCommandHandler.cs @@ -0,0 +1,95 @@ +namespace CMSMicroservice.Application.CommissionCQ.Commands.CalculateWeeklyBalances; + +public class CalculateWeeklyBalancesCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public CalculateWeeklyBalancesCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task 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(); + 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; + } + + /// + /// محاسبه تعادل یک پا (Left یا Right) به صورت بازگشتی + /// + private async Task 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; + } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyBalances/CalculateWeeklyBalancesCommandValidator.cs b/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyBalances/CalculateWeeklyBalancesCommandValidator.cs new file mode 100644 index 0000000..5ba35ec --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyBalances/CalculateWeeklyBalancesCommandValidator.cs @@ -0,0 +1,26 @@ +namespace CMSMicroservice.Application.CommissionCQ.Commands.CalculateWeeklyBalances; + +public class CalculateWeeklyBalancesCommandValidator : AbstractValidator +{ + public CalculateWeeklyBalancesCommandValidator() + { + RuleFor(x => x.WeekNumber) + .NotEmpty() + .WithMessage("شماره هفته نمی‌تواند خالی باشد") + .Matches(@"^\d{4}-W\d{2}$") + .WithMessage("فرمت شماره هفته باید YYYY-Www باشد (مثل 2025-W01)"); + } + + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync( + ValidationContext.CreateWithOptions( + (CalculateWeeklyBalancesCommand)model, + x => x.IncludeProperties(propertyName))); + + if (result.IsValid) + return Array.Empty(); + + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyCommissionPool/CalculateWeeklyCommissionPoolCommand.cs b/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyCommissionPool/CalculateWeeklyCommissionPoolCommand.cs new file mode 100644 index 0000000..bb2944b --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyCommissionPool/CalculateWeeklyCommissionPoolCommand.cs @@ -0,0 +1,17 @@ +namespace CMSMicroservice.Application.CommissionCQ.Commands.CalculateWeeklyCommissionPool; + +/// +/// Command برای محاسبه استخر کمیسیون هفتگی +/// +public record CalculateWeeklyCommissionPoolCommand : IRequest +{ + /// + /// شماره هفته (فرمت: YYYY-Www) + /// + public string WeekNumber { get; init; } = string.Empty; + + /// + /// آیا محاسبه مجدد انجام شود؟ + /// + public bool ForceRecalculate { get; init; } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyCommissionPool/CalculateWeeklyCommissionPoolCommandHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyCommissionPool/CalculateWeeklyCommissionPoolCommandHandler.cs new file mode 100644 index 0000000..af4b89f --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyCommissionPool/CalculateWeeklyCommissionPoolCommandHandler.cs @@ -0,0 +1,78 @@ +namespace CMSMicroservice.Application.CommissionCQ.Commands.CalculateWeeklyCommissionPool; + +public class CalculateWeeklyCommissionPoolCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public CalculateWeeklyCommissionPoolCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task 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; + } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyCommissionPool/CalculateWeeklyCommissionPoolCommandValidator.cs b/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyCommissionPool/CalculateWeeklyCommissionPoolCommandValidator.cs new file mode 100644 index 0000000..59efdd4 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyCommissionPool/CalculateWeeklyCommissionPoolCommandValidator.cs @@ -0,0 +1,26 @@ +namespace CMSMicroservice.Application.CommissionCQ.Commands.CalculateWeeklyCommissionPool; + +public class CalculateWeeklyCommissionPoolCommandValidator : AbstractValidator +{ + public CalculateWeeklyCommissionPoolCommandValidator() + { + RuleFor(x => x.WeekNumber) + .NotEmpty() + .WithMessage("شماره هفته نمی‌تواند خالی باشد") + .Matches(@"^\d{4}-W\d{2}$") + .WithMessage("فرمت شماره هفته باید YYYY-Www باشد"); + } + + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync( + ValidationContext.CreateWithOptions( + (CalculateWeeklyCommissionPoolCommand)model, + x => x.IncludeProperties(propertyName))); + + if (result.IsValid) + return Array.Empty(); + + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Commands/ProcessUserPayouts/ProcessUserPayoutsCommand.cs b/src/CMSMicroservice.Application/CommissionCQ/Commands/ProcessUserPayouts/ProcessUserPayoutsCommand.cs new file mode 100644 index 0000000..9e74664 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Commands/ProcessUserPayouts/ProcessUserPayoutsCommand.cs @@ -0,0 +1,17 @@ +namespace CMSMicroservice.Application.CommissionCQ.Commands.ProcessUserPayouts; + +/// +/// Command برای پردازش و توزیع کمیسیون به کاربران +/// +public record ProcessUserPayoutsCommand : IRequest +{ + /// + /// شماره هفته + /// + public string WeekNumber { get; init; } = string.Empty; + + /// + /// آیا پرداخت مجدد انجام شود؟ + /// + public bool ForceReprocess { get; init; } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Commands/ProcessUserPayouts/ProcessUserPayoutsCommandHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Commands/ProcessUserPayouts/ProcessUserPayoutsCommandHandler.cs new file mode 100644 index 0000000..4acc5f5 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Commands/ProcessUserPayouts/ProcessUserPayoutsCommandHandler.cs @@ -0,0 +1,99 @@ +namespace CMSMicroservice.Application.CommissionCQ.Commands.ProcessUserPayouts; + +public class ProcessUserPayoutsCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public ProcessUserPayoutsCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task 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(); + + 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(); + 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; + } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Commands/ProcessUserPayouts/ProcessUserPayoutsCommandValidator.cs b/src/CMSMicroservice.Application/CommissionCQ/Commands/ProcessUserPayouts/ProcessUserPayoutsCommandValidator.cs new file mode 100644 index 0000000..874f46c --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Commands/ProcessUserPayouts/ProcessUserPayoutsCommandValidator.cs @@ -0,0 +1,26 @@ +namespace CMSMicroservice.Application.CommissionCQ.Commands.ProcessUserPayouts; + +public class ProcessUserPayoutsCommandValidator : AbstractValidator +{ + public ProcessUserPayoutsCommandValidator() + { + RuleFor(x => x.WeekNumber) + .NotEmpty() + .WithMessage("شماره هفته نمی‌تواند خالی باشد") + .Matches(@"^\d{4}-W\d{2}$") + .WithMessage("فرمت شماره هفته باید YYYY-Www باشد"); + } + + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync( + ValidationContext.CreateWithOptions( + (ProcessUserPayoutsCommand)model, + x => x.IncludeProperties(propertyName))); + + if (result.IsValid) + return Array.Empty(); + + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Commands/ProcessWithdrawal/ProcessWithdrawalCommand.cs b/src/CMSMicroservice.Application/CommissionCQ/Commands/ProcessWithdrawal/ProcessWithdrawalCommand.cs new file mode 100644 index 0000000..5c4ce8f --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Commands/ProcessWithdrawal/ProcessWithdrawalCommand.cs @@ -0,0 +1,22 @@ +namespace CMSMicroservice.Application.CommissionCQ.Commands.ProcessWithdrawal; + +/// +/// Command برای پردازش برداشت (توسط Admin) +/// +public record ProcessWithdrawalCommand : IRequest +{ + /// + /// شناسه پرداخت کمیسیون + /// + public long PayoutId { get; init; } + + /// + /// آیا تایید شده است؟ + /// + public bool IsApproved { get; init; } + + /// + /// دلیل (در صورت رد) + /// + public string? Reason { get; init; } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Commands/ProcessWithdrawal/ProcessWithdrawalCommandHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Commands/ProcessWithdrawal/ProcessWithdrawalCommandHandler.cs new file mode 100644 index 0000000..7ad4ccf --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Commands/ProcessWithdrawal/ProcessWithdrawalCommandHandler.cs @@ -0,0 +1,102 @@ +namespace CMSMicroservice.Application.CommissionCQ.Commands.ProcessWithdrawal; + +public class ProcessWithdrawalCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public ProcessWithdrawalCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task 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; + } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Commands/ProcessWithdrawal/ProcessWithdrawalCommandValidator.cs b/src/CMSMicroservice.Application/CommissionCQ/Commands/ProcessWithdrawal/ProcessWithdrawalCommandValidator.cs new file mode 100644 index 0000000..2f299aa --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Commands/ProcessWithdrawal/ProcessWithdrawalCommandValidator.cs @@ -0,0 +1,31 @@ +namespace CMSMicroservice.Application.CommissionCQ.Commands.ProcessWithdrawal; + +public class ProcessWithdrawalCommandValidator : AbstractValidator +{ + 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>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync( + ValidationContext.CreateWithOptions( + (ProcessWithdrawalCommand)model, + x => x.IncludeProperties(propertyName))); + + if (result.IsValid) + return Array.Empty(); + + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Commands/RequestWithdrawal/RequestWithdrawalCommand.cs b/src/CMSMicroservice.Application/CommissionCQ/Commands/RequestWithdrawal/RequestWithdrawalCommand.cs new file mode 100644 index 0000000..9737bbf --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Commands/RequestWithdrawal/RequestWithdrawalCommand.cs @@ -0,0 +1,22 @@ +namespace CMSMicroservice.Application.CommissionCQ.Commands.RequestWithdrawal; + +/// +/// Command برای درخواست برداشت کمیسیون +/// +public record RequestWithdrawalCommand : IRequest +{ + /// + /// شناسه پرداخت کمیسیون + /// + public long PayoutId { get; init; } + + /// + /// روش برداشت (Cash یا Diamond) + /// + public WithdrawalMethod Method { get; init; } + + /// + /// شماره شبا (برای Cash) + /// + public string? IbanNumber { get; init; } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Commands/RequestWithdrawal/RequestWithdrawalCommandHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Commands/RequestWithdrawal/RequestWithdrawalCommandHandler.cs new file mode 100644 index 0000000..e3630e9 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Commands/RequestWithdrawal/RequestWithdrawalCommandHandler.cs @@ -0,0 +1,62 @@ +namespace CMSMicroservice.Application.CommissionCQ.Commands.RequestWithdrawal; + +public class RequestWithdrawalCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public RequestWithdrawalCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task 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; + } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Commands/RequestWithdrawal/RequestWithdrawalCommandValidator.cs b/src/CMSMicroservice.Application/CommissionCQ/Commands/RequestWithdrawal/RequestWithdrawalCommandValidator.cs new file mode 100644 index 0000000..edd74e3 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Commands/RequestWithdrawal/RequestWithdrawalCommandValidator.cs @@ -0,0 +1,35 @@ +namespace CMSMicroservice.Application.CommissionCQ.Commands.RequestWithdrawal; + +public class RequestWithdrawalCommandValidator : AbstractValidator +{ + 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>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync( + ValidationContext.CreateWithOptions( + (RequestWithdrawalCommand)model, + x => x.IncludeProperties(propertyName))); + + if (result.IsValid) + return Array.Empty(); + + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetCommissionPayoutHistory/GetCommissionPayoutHistoryQuery.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetCommissionPayoutHistory/GetCommissionPayoutHistoryQuery.cs new file mode 100644 index 0000000..4f99234 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetCommissionPayoutHistory/GetCommissionPayoutHistoryQuery.cs @@ -0,0 +1,32 @@ +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetCommissionPayoutHistory; + +/// +/// Query برای دریافت تاریخچه تغییرات کمیسیون +/// +public record GetCommissionPayoutHistoryQuery : IRequest +{ + /// + /// شناسه پرداخت (اختیاری) + /// + public long? PayoutId { get; init; } + + /// + /// شناسه کاربر (اختیاری) + /// + public long? UserId { get; init; } + + /// + /// شماره هفته (اختیاری) + /// + public string? WeekNumber { get; init; } + + /// + /// مرتب‌سازی + /// + public string? SortBy { get; init; } + + /// + /// Pagination + /// + public PaginationState? PaginationState { get; init; } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetCommissionPayoutHistory/GetCommissionPayoutHistoryQueryHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetCommissionPayoutHistory/GetCommissionPayoutHistoryQueryHandler.cs new file mode 100644 index 0000000..875fcc0 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetCommissionPayoutHistory/GetCommissionPayoutHistoryQueryHandler.cs @@ -0,0 +1,63 @@ +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetCommissionPayoutHistory; + +public class GetCommissionPayoutHistoryQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetCommissionPayoutHistoryQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task 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 + }; + } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetCommissionPayoutHistory/GetCommissionPayoutHistoryQueryValidator.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetCommissionPayoutHistory/GetCommissionPayoutHistoryQueryValidator.cs new file mode 100644 index 0000000..abf2c42 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetCommissionPayoutHistory/GetCommissionPayoutHistoryQueryValidator.cs @@ -0,0 +1,35 @@ +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetCommissionPayoutHistory; + +public class GetCommissionPayoutHistoryQueryValidator : AbstractValidator +{ + 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>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync( + ValidationContext.CreateWithOptions( + (GetCommissionPayoutHistoryQuery)model, + x => x.IncludeProperties(propertyName))); + + if (result.IsValid) + return Array.Empty(); + + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetCommissionPayoutHistory/GetCommissionPayoutHistoryResponseDto.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetCommissionPayoutHistory/GetCommissionPayoutHistoryResponseDto.cs new file mode 100644 index 0000000..656a445 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetCommissionPayoutHistory/GetCommissionPayoutHistoryResponseDto.cs @@ -0,0 +1,23 @@ +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetCommissionPayoutHistory; + +public class GetCommissionPayoutHistoryResponseDto +{ + public MetaData MetaData { get; set; } + public List 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; } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserCommissionPayouts/GetUserCommissionPayoutsQuery.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserCommissionPayouts/GetUserCommissionPayoutsQuery.cs new file mode 100644 index 0000000..6bfdfb0 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserCommissionPayouts/GetUserCommissionPayoutsQuery.cs @@ -0,0 +1,32 @@ +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetUserCommissionPayouts; + +/// +/// Query برای دریافت پرداخت‌های کمیسیون کاربر +/// +public record GetUserCommissionPayoutsQuery : IRequest +{ + /// + /// شناسه کاربر (اختیاری) + /// + public long? UserId { get; init; } + + /// + /// فیلتر وضعیت + /// + public CommissionPayoutStatus? Status { get; init; } + + /// + /// شماره هفته (اختیاری) + /// + public string? WeekNumber { get; init; } + + /// + /// مرتب‌سازی + /// + public string? SortBy { get; init; } + + /// + /// Pagination + /// + public PaginationState? PaginationState { get; init; } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserCommissionPayouts/GetUserCommissionPayoutsQueryHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserCommissionPayouts/GetUserCommissionPayoutsQueryHandler.cs new file mode 100644 index 0000000..0257728 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserCommissionPayouts/GetUserCommissionPayoutsQueryHandler.cs @@ -0,0 +1,64 @@ +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetUserCommissionPayouts; + +public class GetUserCommissionPayoutsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetUserCommissionPayoutsQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task 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 + }; + } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserCommissionPayouts/GetUserCommissionPayoutsQueryValidator.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserCommissionPayouts/GetUserCommissionPayoutsQueryValidator.cs new file mode 100644 index 0000000..600dcbd --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserCommissionPayouts/GetUserCommissionPayoutsQueryValidator.cs @@ -0,0 +1,35 @@ +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetUserCommissionPayouts; + +public class GetUserCommissionPayoutsQueryValidator : AbstractValidator +{ + 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>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync( + ValidationContext.CreateWithOptions( + (GetUserCommissionPayoutsQuery)model, + x => x.IncludeProperties(propertyName))); + + if (result.IsValid) + return Array.Empty(); + + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserCommissionPayouts/GetUserCommissionPayoutsResponseDto.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserCommissionPayouts/GetUserCommissionPayoutsResponseDto.cs new file mode 100644 index 0000000..6475975 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserCommissionPayouts/GetUserCommissionPayoutsResponseDto.cs @@ -0,0 +1,24 @@ +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetUserCommissionPayouts; + +public class GetUserCommissionPayoutsResponseDto +{ + public MetaData MetaData { get; set; } + public List 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; } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserWeeklyBalances/GetUserWeeklyBalancesQuery.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserWeeklyBalances/GetUserWeeklyBalancesQuery.cs new file mode 100644 index 0000000..9cd6a1d --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserWeeklyBalances/GetUserWeeklyBalancesQuery.cs @@ -0,0 +1,32 @@ +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetUserWeeklyBalances; + +/// +/// Query برای دریافت تعادل‌های هفتگی کاربر +/// +public record GetUserWeeklyBalancesQuery : IRequest +{ + /// + /// شناسه کاربر (اختیاری) + /// + public long? UserId { get; init; } + + /// + /// شماره هفته (اختیاری) + /// + public string? WeekNumber { get; init; } + + /// + /// فقط موارد Expired نشده؟ + /// + public bool? OnlyActive { get; init; } + + /// + /// مرتب‌سازی + /// + public string? SortBy { get; init; } + + /// + /// Pagination + /// + public PaginationState? PaginationState { get; init; } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserWeeklyBalances/GetUserWeeklyBalancesQueryHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserWeeklyBalances/GetUserWeeklyBalancesQueryHandler.cs new file mode 100644 index 0000000..5a041b7 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserWeeklyBalances/GetUserWeeklyBalancesQueryHandler.cs @@ -0,0 +1,61 @@ +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetUserWeeklyBalances; + +public class GetUserWeeklyBalancesQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetUserWeeklyBalancesQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task 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 + }; + } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserWeeklyBalances/GetUserWeeklyBalancesQueryValidator.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserWeeklyBalances/GetUserWeeklyBalancesQueryValidator.cs new file mode 100644 index 0000000..dcf2548 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserWeeklyBalances/GetUserWeeklyBalancesQueryValidator.cs @@ -0,0 +1,30 @@ +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetUserWeeklyBalances; + +public class GetUserWeeklyBalancesQueryValidator : AbstractValidator +{ + 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>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync( + ValidationContext.CreateWithOptions( + (GetUserWeeklyBalancesQuery)model, + x => x.IncludeProperties(propertyName))); + + if (result.IsValid) + return Array.Empty(); + + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserWeeklyBalances/GetUserWeeklyBalancesResponseDto.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserWeeklyBalances/GetUserWeeklyBalancesResponseDto.cs new file mode 100644 index 0000000..ecdb3fe --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserWeeklyBalances/GetUserWeeklyBalancesResponseDto.cs @@ -0,0 +1,21 @@ +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetUserWeeklyBalances; + +public class GetUserWeeklyBalancesResponseDto +{ + public MetaData MetaData { get; set; } + public List 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; } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWeeklyCommissionPool/GetWeeklyCommissionPoolQuery.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWeeklyCommissionPool/GetWeeklyCommissionPoolQuery.cs new file mode 100644 index 0000000..651dad9 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWeeklyCommissionPool/GetWeeklyCommissionPoolQuery.cs @@ -0,0 +1,12 @@ +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWeeklyCommissionPool; + +/// +/// Query برای دریافت استخر کمیسیون هفتگی +/// +public record GetWeeklyCommissionPoolQuery : IRequest +{ + /// + /// شماره هفته + /// + public string WeekNumber { get; init; } = string.Empty; +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWeeklyCommissionPool/GetWeeklyCommissionPoolQueryHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWeeklyCommissionPool/GetWeeklyCommissionPoolQueryHandler.cs new file mode 100644 index 0000000..067dfb6 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWeeklyCommissionPool/GetWeeklyCommissionPoolQueryHandler.cs @@ -0,0 +1,32 @@ +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWeeklyCommissionPool; + +public class GetWeeklyCommissionPoolQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetWeeklyCommissionPoolQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task 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; + } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWeeklyCommissionPool/GetWeeklyCommissionPoolQueryValidator.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWeeklyCommissionPool/GetWeeklyCommissionPoolQueryValidator.cs new file mode 100644 index 0000000..42a1689 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWeeklyCommissionPool/GetWeeklyCommissionPoolQueryValidator.cs @@ -0,0 +1,26 @@ +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWeeklyCommissionPool; + +public class GetWeeklyCommissionPoolQueryValidator : AbstractValidator +{ + public GetWeeklyCommissionPoolQueryValidator() + { + RuleFor(x => x.WeekNumber) + .NotEmpty() + .WithMessage("شماره هفته نمی‌تواند خالی باشد") + .Matches(@"^\d{4}-W\d{2}$") + .WithMessage("فرمت شماره هفته باید YYYY-Www باشد"); + } + + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync( + ValidationContext.CreateWithOptions( + (GetWeeklyCommissionPoolQuery)model, + x => x.IncludeProperties(propertyName))); + + if (result.IsValid) + return Array.Empty(); + + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWeeklyCommissionPool/WeeklyCommissionPoolDto.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWeeklyCommissionPool/WeeklyCommissionPoolDto.cs new file mode 100644 index 0000000..1bdd2ea --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWeeklyCommissionPool/WeeklyCommissionPoolDto.cs @@ -0,0 +1,16 @@ +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWeeklyCommissionPool; + +/// +/// DTO برای استخر کمیسیون هفتگی +/// +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; } +}