feat: implement commission calculation strategy with ORM and SP options
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 1m43s

This commit is contained in:
masoodafar-web
2025-12-20 03:15:28 +03:30
parent 9864302bab
commit 25476ba120
18 changed files with 1687 additions and 21 deletions
@@ -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,
@@ -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; }
@@ -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
}
@@ -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);
}
@@ -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