feat: implement commission calculation strategy with ORM and SP options
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 1m43s
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 1m43s
This commit is contained in:
+2
@@ -12,6 +12,7 @@ public class GetAllClubMembershipsQueryHandler : IRequestHandler<GetAllClubMembe
|
||||
public async Task<GetAllClubMembershipsResponseDto> Handle(GetAllClubMembershipsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var query = _context.ClubMemberships
|
||||
.Include(x => x.User)
|
||||
.ApplyOrder(sortBy: request.SortBy)
|
||||
.AsNoTracking()
|
||||
.AsQueryable();
|
||||
@@ -33,6 +34,7 @@ public class GetAllClubMembershipsQueryHandler : IRequestHandler<GetAllClubMembe
|
||||
{
|
||||
Id = x.Id,
|
||||
UserId = x.UserId,
|
||||
UserName = x.User != null ? $"{x.User.FirstName} {x.User.LastName}" : string.Empty,
|
||||
IsActive = x.IsActive,
|
||||
ActivatedAt = x.ActivatedAt,
|
||||
InitialContribution = x.InitialContribution,
|
||||
|
||||
+1
@@ -10,6 +10,7 @@ public class GetAllClubMembershipsResponseModel
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public long UserId { get; set; }
|
||||
public string UserName { get; set; } = string.Empty;
|
||||
public bool IsActive { get; set; }
|
||||
public DateTime? ActivatedAt { get; set; }
|
||||
public long InitialContribution { get; set; }
|
||||
|
||||
+22
-16
@@ -8,13 +8,16 @@ public class TriggerWeeklyCalculationCommandHandler : IRequestHandler<TriggerWee
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly IMediator _mediator;
|
||||
private readonly ICommissionCalculationStrategyFactory _strategyFactory;
|
||||
|
||||
public TriggerWeeklyCalculationCommandHandler(
|
||||
IApplicationDbContext context,
|
||||
IMediator mediator)
|
||||
IMediator mediator,
|
||||
ICommissionCalculationStrategyFactory strategyFactory)
|
||||
{
|
||||
_context = context;
|
||||
_mediator = mediator;
|
||||
_strategyFactory = strategyFactory;
|
||||
}
|
||||
|
||||
public async Task<TriggerWeeklyCalculationResponseDto> Handle(
|
||||
@@ -26,9 +29,8 @@ public class TriggerWeeklyCalculationCommandHandler : IRequestHandler<TriggerWee
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
// Validate week number format
|
||||
if (request.WeekDefinitionId<=0)
|
||||
if (request.WeekDefinitionId <= 0)
|
||||
{
|
||||
return new TriggerWeeklyCalculationResponseDto
|
||||
{
|
||||
@@ -40,33 +42,37 @@ public class TriggerWeeklyCalculationCommandHandler : IRequestHandler<TriggerWee
|
||||
}
|
||||
|
||||
var steps = new List<string>();
|
||||
|
||||
// ⭐ دریافت استراتژی براساس Config (ORM یا SP)
|
||||
var strategy = await _strategyFactory.CreateStrategyAsync(cancellationToken);
|
||||
var strategyName = strategy.GetType().Name.Contains("StoredProcedure") ? "SP" : "ORM";
|
||||
|
||||
// Step 1: Calculate Weekly Balances (تا 15 لول)
|
||||
if (!request.SkipBalances)
|
||||
{
|
||||
await _mediator.Send(new CalculateWeeklyBalancesCommand
|
||||
{
|
||||
WeekDefinitionId = request.WeekDefinitionId,
|
||||
ForceRecalculate = request.ForceRecalculate
|
||||
}, cancellationToken);
|
||||
steps.Add("محاسبه امتیازات هفتگی");
|
||||
var balancesCount = await strategy.CalculateWeeklyBalancesAsync(
|
||||
request.WeekDefinitionId,
|
||||
request.ForceRecalculate,
|
||||
cancellationToken);
|
||||
|
||||
steps.Add($"محاسبه امتیازات هفتگی ({balancesCount} کاربر)");
|
||||
}
|
||||
|
||||
// Step 2: Calculate Pool & Process Payouts (محاسبه استخر + پرداخت کاربران)
|
||||
if (!request.SkipPayouts)
|
||||
{
|
||||
await _mediator.Send(new CalculateWeeklyCommissionPoolCommand
|
||||
{
|
||||
WeekDefinitionId = request.WeekDefinitionId,
|
||||
ForceRecalculate = request.ForceRecalculate
|
||||
}, cancellationToken);
|
||||
steps.Add("محاسبه استخر و پرداخت کاربران");
|
||||
var poolId = await strategy.CalculateWeeklyCommissionPoolAsync(
|
||||
request.WeekDefinitionId,
|
||||
request.ForceRecalculate,
|
||||
cancellationToken);
|
||||
|
||||
steps.Add($"محاسبه استخر و پرداخت کاربران (Pool: {poolId})");
|
||||
}
|
||||
|
||||
return new TriggerWeeklyCalculationResponseDto
|
||||
{
|
||||
Success = true,
|
||||
Message = $"محاسبات هفته {request.WeekDefinitionId} با موفقیت انجام شد. مراحل: {string.Join(", ", steps)}",
|
||||
Message = $"محاسبات هفته {request.WeekDefinitionId} با موفقیت انجام شد [{strategyName}]. مراحل: {string.Join(", ", steps)}",
|
||||
ExecutionId = executionId,
|
||||
StartedAt = startedAt
|
||||
};
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
namespace CMSMicroservice.Application.Common.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// استراتژی محاسبه کمیسیون هفتگی
|
||||
/// دو پیادهسازی دارد: ORM و Stored Procedure
|
||||
/// </summary>
|
||||
public interface ICommissionCalculationStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// محاسبه تعادلهای هفتگی (مرحله 1)
|
||||
/// </summary>
|
||||
/// <param name="weekDefinitionId">شناسه هفته</param>
|
||||
/// <param name="forceRecalculate">محاسبه مجدد حتی اگر قبلاً محاسبه شده</param>
|
||||
/// <param name="cancellationToken">توکن لغو</param>
|
||||
/// <returns>تعداد رکوردهای محاسبه شده</returns>
|
||||
Task<int> CalculateWeeklyBalancesAsync(
|
||||
long weekDefinitionId,
|
||||
bool forceRecalculate,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// محاسبه استخر کمیسیون و پرداختها (مرحله 2)
|
||||
/// </summary>
|
||||
/// <param name="weekDefinitionId">شناسه هفته</param>
|
||||
/// <param name="forceRecalculate">محاسبه مجدد حتی اگر قبلاً محاسبه شده</param>
|
||||
/// <param name="cancellationToken">توکن لغو</param>
|
||||
/// <returns>شناسه Pool ایجاد شده</returns>
|
||||
Task<long> CalculateWeeklyCommissionPoolAsync(
|
||||
long weekDefinitionId,
|
||||
bool forceRecalculate,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// نوع استراتژی محاسبه کمیسیون
|
||||
/// </summary>
|
||||
public enum CommissionCalculationStrategyType
|
||||
{
|
||||
/// <summary>
|
||||
/// استفاده از Entity Framework (ORM) - پیشفرض
|
||||
/// </summary>
|
||||
Orm = 0,
|
||||
|
||||
/// <summary>
|
||||
/// استفاده از Stored Procedure - بهینهتر برای دادههای زیاد
|
||||
/// </summary>
|
||||
StoredProcedure = 1
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
namespace CMSMicroservice.Application.Common.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Factory برای ایجاد Strategy مناسب براساس Config
|
||||
/// </summary>
|
||||
public interface ICommissionCalculationStrategyFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// ایجاد Strategy براساس تنظیمات سیستم
|
||||
/// </summary>
|
||||
/// <returns>استراتژی مناسب (ORM یا SP)</returns>
|
||||
Task<ICommissionCalculationStrategy> CreateStrategyAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// ایجاد Strategy مشخص
|
||||
/// </summary>
|
||||
/// <param name="strategyType">نوع استراتژی</param>
|
||||
/// <returns>استراتژی درخواستی</returns>
|
||||
ICommissionCalculationStrategy CreateStrategy(CommissionCalculationStrategyType strategyType);
|
||||
}
|
||||
+7
-3
@@ -56,16 +56,20 @@ public class GetNetworkStatisticsQueryHandler : IRequestHandler<GetNetworkStatis
|
||||
|
||||
// Monthly growth (last 6 months) - using Created date
|
||||
var sixMonthsAgo = DateTime.Now.AddMonths(-6);
|
||||
var monthlyGrowth = await _context.Users
|
||||
var monthlyGrowthRaw = await _context.Users
|
||||
.Where(x => x.NetworkParentId != null && x.Created >= sixMonthsAgo)
|
||||
.GroupBy(x => new { x.Created.Year, x.Created.Month })
|
||||
.Select(x => new { x.Created.Year, x.Created.Month })
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var monthlyGrowth = monthlyGrowthRaw
|
||||
.GroupBy(x => new { x.Year, x.Month })
|
||||
.Select(g => new MonthlyGrowthModel
|
||||
{
|
||||
Month = $"{g.Key.Year}-{g.Key.Month:D2}",
|
||||
NewMembers = g.Count()
|
||||
})
|
||||
.OrderBy(x => x.Month)
|
||||
.ToListAsync(cancellationToken);
|
||||
.ToList();
|
||||
|
||||
// Top users by total children count
|
||||
var topUsers = await _context.Users
|
||||
|
||||
Reference in New Issue
Block a user