feat: refactor week number handling to use WeekDefinitionId across multiple entities and queries

This commit is contained in:
masoodafar-web
2025-12-19 10:41:22 +03:30
parent 3cb95dc349
commit 8b784101be
83 changed files with 17592 additions and 566 deletions
@@ -14,6 +14,8 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.11" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.11" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.4.0" /> <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.4.0" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.6.10" /> <PackageReference Include="System.Linq.Dynamic.Core" Version="1.6.10" />
<PackageReference Include="DateTimeConverterCL" Version="1.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\CMSMicroservice.Domain\CMSMicroservice.Domain.csproj" /> <ProjectReference Include="..\CMSMicroservice.Domain\CMSMicroservice.Domain.csproj" />
@@ -17,15 +17,18 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
{ {
private readonly IApplicationDbContext _context; private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser; private readonly ICurrentUserService _currentUser;
private readonly IWeekDefinitionRepository _weekRepository;
private readonly ILogger<ActivateClubMembershipCommandHandler> _logger; private readonly ILogger<ActivateClubMembershipCommandHandler> _logger;
public ActivateClubMembershipCommandHandler( public ActivateClubMembershipCommandHandler(
IApplicationDbContext context, IApplicationDbContext context,
ICurrentUserService currentUser, ICurrentUserService currentUser,
IWeekDefinitionRepository weekRepository,
ILogger<ActivateClubMembershipCommandHandler> logger) ILogger<ActivateClubMembershipCommandHandler> logger)
{ {
_context = context; _context = context;
_currentUser = currentUser; _currentUser = currentUser;
_weekRepository = weekRepository;
_logger = logger; _logger = logger;
} }
@@ -250,16 +253,16 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
await _context.SaveChangesAsync(cancellationToken); await _context.SaveChangesAsync(cancellationToken);
// ⭐ 8. اضافه کردن مبلغ به Pool هفته جاری // ⭐ 8. اضافه کردن مبلغ به Pool هفته جاری
var currentWeekNumber = GetCurrentWeekNumber(); var currentWeekDefinitionId = GetCurrentWeekDefinitionId();
var weeklyPool = await _context.WeeklyCommissionPools var weeklyPool = await _context.WeeklyCommissionPools
.FirstOrDefaultAsync(p => p.WeekNumber == currentWeekNumber, cancellationToken); .FirstOrDefaultAsync(p => p.WeekDefinitionId == currentWeekDefinitionId, cancellationToken);
if (weeklyPool == null) if (weeklyPool == null)
{ {
// ایجاد Pool جدید برای این هفته // ایجاد Pool جدید برای این هفته
weeklyPool = new WeeklyCommissionPool weeklyPool = new WeeklyCommissionPool
{ {
WeekNumber = currentWeekNumber, WeekDefinitionId = currentWeekDefinitionId,
TotalPoolAmount = activationFeeValue, // مبلغ هدیه به Pool اضافه میشه TotalPoolAmount = activationFeeValue, // مبلغ هدیه به Pool اضافه میشه
TotalBalances = 0, // در CalculateWeeklyBalances محاسبه میشه TotalBalances = 0, // در CalculateWeeklyBalances محاسبه میشه
ValuePerBalance = 0, // در CalculateWeeklyCommissionPool محاسبه میشه ValuePerBalance = 0, // در CalculateWeeklyCommissionPool محاسبه میشه
@@ -270,8 +273,8 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
await _context.WeeklyCommissionPools.AddAsync(weeklyPool, cancellationToken); await _context.WeeklyCommissionPools.AddAsync(weeklyPool, cancellationToken);
_logger.LogInformation( _logger.LogInformation(
"Created new WeeklyCommissionPool for {WeekNumber} with initial amount: {Amount}", "Created new WeeklyCommissionPool for WeekDefinitionId={WeekDefinitionId} with initial amount: {Amount}",
currentWeekNumber, currentWeekDefinitionId,
giftValue giftValue
); );
} }
@@ -282,9 +285,9 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
_context.WeeklyCommissionPools.Update(weeklyPool); _context.WeeklyCommissionPools.Update(weeklyPool);
_logger.LogInformation( _logger.LogInformation(
"Added {Amount} to existing WeeklyCommissionPool for {WeekNumber}. New total: {NewTotal}", "Added {Amount} to existing WeeklyCommissionPool for WeekDefinitionId={WeekDefinitionId}. New total: {NewTotal}",
activationFeeValue, activationFeeValue,
currentWeekNumber, currentWeekDefinitionId,
weeklyPool.TotalPoolAmount weeklyPool.TotalPoolAmount
); );
} }
@@ -346,13 +349,15 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
} }
/// <summary> /// <summary>
/// دریافت شماره هفته جاری (مثال: "2025-W50") /// دریافت شناسه تعریف هفته جاری
/// </summary> /// </summary>
private string GetCurrentWeekNumber() private long GetCurrentWeekDefinitionId()
{ {
var now = DateTime.Now; var week = _weekRepository.GetCurrentWeek();
var calendar = CultureInfo.CurrentCulture.Calendar; if (week == null)
var weekOfYear = calendar.GetWeekOfYear(now, CalendarWeekRule.FirstDay, DayOfWeek.Saturday); {
return $"{now.Year}-W{weekOfYear:D2}"; throw new InvalidOperationException("هفته جاری در سیستم تعریف نشده است");
}
return week.Id;
} }
} }
@@ -8,7 +8,7 @@ public record CalculateWeeklyBalancesCommand : IRequest<int>
/// <summary> /// <summary>
/// شماره هفته (فرمت: YYYY-Www مثل 2025-W01) /// شماره هفته (فرمت: YYYY-Www مثل 2025-W01)
/// </summary> /// </summary>
public string WeekNumber { get; init; } = string.Empty; public long WeekDefinitionId { get; init; }
/// <summary> /// <summary>
/// آیا محاسبه مجدد انجام شود؟ (پیش‌فرض: false) /// آیا محاسبه مجدد انجام شود؟ (پیش‌فرض: false)
@@ -3,22 +3,33 @@ namespace CMSMicroservice.Application.CommissionCQ.Commands.CalculateWeeklyBalan
public class CalculateWeeklyBalancesCommandHandler : IRequestHandler<CalculateWeeklyBalancesCommand, int> public class CalculateWeeklyBalancesCommandHandler : IRequestHandler<CalculateWeeklyBalancesCommand, int>
{ {
private readonly IApplicationDbContext _context; private readonly IApplicationDbContext _context;
private readonly IWeekDefinitionRepository _weekRepository;
public CalculateWeeklyBalancesCommandHandler(IApplicationDbContext context) public CalculateWeeklyBalancesCommandHandler(
IApplicationDbContext context,
IWeekDefinitionRepository weekRepository)
{ {
_context = context; _context = context;
_weekRepository = weekRepository;
} }
public async Task<int> Handle(CalculateWeeklyBalancesCommand request, CancellationToken cancellationToken) public async Task<int> Handle(CalculateWeeklyBalancesCommand request, CancellationToken cancellationToken)
{ {
var regorianWeekNumber = _weekRepository.GetGregorianWeekNumber(request.WeekDefinitionId);
if (regorianWeekNumber == null)
{
throw new InvalidOperationException($"هفته {request.WeekDefinitionId} در سیستم تعریف نشده است");
}
// بررسی وجود محاسبه قبلی // بررسی وجود محاسبه قبلی
var existingBalances = await _context.NetworkWeeklyBalances var existingBalances = await _context.NetworkWeeklyBalances
.Where(x => x.WeekNumber == request.WeekNumber) .Where(x => x.WeekDefinitionId == request.WeekDefinitionId)
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
if (existingBalances.Any() && !request.ForceRecalculate) if (existingBalances.Any() && !request.ForceRecalculate)
{ {
throw new InvalidOperationException($"تعادل‌های هفته {request.WeekNumber} قبلاً محاسبه شده است. برای محاسبه مجدد از ForceRecalculate استفاده کنید"); throw new InvalidOperationException($"تعادل‌های هفته {request.WeekDefinitionId} قبلاً محاسبه شده است. برای محاسبه مجدد از ForceRecalculate استفاده کنید");
} }
// حذف محاسبات قبلی در صورت ForceRecalculate // حذف محاسبات قبلی در صورت ForceRecalculate
@@ -43,16 +54,22 @@ public class CalculateWeeklyBalancesCommandHandler : IRequestHandler<CalculateWe
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
// دریافت باقیمانده‌های هفته قبل // دریافت باقیمانده‌های هفته قبل
var previousWeekNumber = GetPreviousWeekNumber(request.WeekNumber); var previousWeekDefinitionId = GetPreviousWeekDefinitionId(request.WeekDefinitionId);
var previousWeekCarryovers = await _context.NetworkWeeklyBalances Dictionary<long, (int LeftLegRemainder, int RightLegRemainder)> previousWeekCarryovers;
.Where(x => x.WeekNumber == previousWeekNumber)
.Select(x => new if (previousWeekDefinitionId.HasValue)
{ {
x.UserId, previousWeekCarryovers = await _context.NetworkWeeklyBalances
x.LeftLegRemainder, .Where(x => x.WeekDefinitionId == previousWeekDefinitionId.Value)
x.RightLegRemainder .ToDictionaryAsync(
}) x => x.UserId,
.ToDictionaryAsync(x => x.UserId, cancellationToken); x => (x.LeftLegRemainder, x.RightLegRemainder),
cancellationToken);
}
else
{
previousWeekCarryovers = new Dictionary<long, (int, int)>();
}
var balancesList = new List<NetworkWeeklyBalance>(); var balancesList = new List<NetworkWeeklyBalance>();
var calculatedAt = DateTime.Now; var calculatedAt = DateTime.Now;
@@ -85,8 +102,8 @@ public class CalculateWeeklyBalancesCommandHandler : IRequestHandler<CalculateWe
} }
// محاسبه تعداد اعضای جدید در این هفته (تا maxNetworkLevel لول پایین‌تر) // محاسبه تعداد اعضای جدید در این هفته (تا maxNetworkLevel لول پایین‌تر)
var leftNewMembers = await CountNewMembersInLeg(user.Id, NetworkLeg.Left, request.WeekNumber, maxNetworkLevel, cancellationToken); var leftNewMembers = await CountNewMembersInLeg(user.Id, NetworkLeg.Left, request.WeekDefinitionId, maxNetworkLevel, cancellationToken);
var rightNewMembers = await CountNewMembersInLeg(user.Id, NetworkLeg.Right, request.WeekNumber, maxNetworkLevel, cancellationToken); var rightNewMembers = await CountNewMembersInLeg(user.Id, NetworkLeg.Right, request.WeekDefinitionId, maxNetworkLevel, cancellationToken);
// محاسبه مجموع هر پا (جدید + باقیمانده) // محاسبه مجموع هر پا (جدید + باقیمانده)
var leftTotal = leftNewMembers + leftCarryover; var leftTotal = leftNewMembers + leftCarryover;
@@ -124,7 +141,7 @@ public class CalculateWeeklyBalancesCommandHandler : IRequestHandler<CalculateWe
var balance = new NetworkWeeklyBalance var balance = new NetworkWeeklyBalance
{ {
UserId = user.Id, UserId = user.Id,
WeekNumber = request.WeekNumber, WeekDefinitionId = request.WeekDefinitionId,
// اطلاعات جدید // اطلاعات جدید
LeftLegNewMembers = leftNewMembers, LeftLegNewMembers = leftNewMembers,
@@ -189,36 +206,30 @@ public class CalculateWeeklyBalancesCommandHandler : IRequestHandler<CalculateWe
} }
/// <summary> /// <summary>
/// شماره هفته قبل را محاسبه می‌کند /// شناسه هفته قبل را از WeekDefinitionRepository می‌گیرد
/// </summary> /// </summary>
private string GetPreviousWeekNumber(string currentWeekNumber) private long? GetPreviousWeekDefinitionId(long currentWeekDefinitionId)
{ {
// مثال: "2025-W48" -> "2025-W47" var currentWeekNumber = _weekRepository.GetGregorianWeekNumber(currentWeekDefinitionId);
var parts = currentWeekNumber.Split('-'); if (currentWeekNumber == null) return null;
var year = int.Parse(parts[0]);
var week = int.Parse(parts[1].Replace("W", ""));
week--; var previousWeekNumber = _weekRepository.GetPreviousWeekNumber(currentWeekNumber);
if (week < 1) if (previousWeekNumber == null) return null;
{
year--;
week = 52; // یا 53 بسته به سال
}
return $"{year}-W{week:D2}"; return _weekRepository.GetWeekDefinitionId(previousWeekNumber);
} }
/// <summary> /// <summary>
/// شمارش اعضای جدیدی که در این هفته به یک پا اضافه شدند /// شمارش اعضای جدیدی که در این هفته به یک پا اضافه شدند
/// تا maxLevel لول پایین‌تر شمارش می‌شود /// تا maxLevel لول پایین‌تر شمارش می‌شود
/// </summary> /// </summary>
private async Task<int> CountNewMembersInLeg(long userId, NetworkLeg leg, string weekNumber, int maxLevel, CancellationToken cancellationToken) private async Task<int> CountNewMembersInLeg(long userId, NetworkLeg leg, long WeekDefinitionId, int maxLevel, CancellationToken cancellationToken)
{ {
// تبدیل WeekNumber به بازه تاریخی // تبدیل WeekNumber به بازه تاریخی
var (startDate, endDate) = GetWeekDateRange(weekNumber); var startDateEndDate =_weekRepository.GetWeekDateRange(WeekDefinitionId);
// شمارش تمام اعضای زیرمجموعه که در این هفته فعال شدند (تا maxLevel لول) // شمارش تمام اعضای زیرمجموعه که در این هفته فعال شدند (تا maxLevel لول)
var count = await CountNewMembersRecursive(userId, leg, startDate, endDate, 0, maxLevel, cancellationToken); var count = await CountNewMembersRecursive(userId, leg, startDateEndDate.Value.startDate, startDateEndDate.Value.endDate, 0, maxLevel, cancellationToken);
return count; return count;
} }
@@ -270,27 +281,31 @@ public class CalculateWeeklyBalancesCommandHandler : IRequestHandler<CalculateWe
} }
/// <summary> /// <summary>
/// تبدیل شماره هفته به بازه تاریخی /// تبدیل شماره هفته به بازه تاریخی از WeekDefinitionRepository
/// </summary> /// </summary>
private (DateTime startDate, DateTime endDate) GetWeekDateRange(string weekNumber) // private (DateTime startDate, DateTime endDate) GetWeekDateRange(string weekNumber)
{ // {
// مثال: "2025-W48" // var dateRange = _weekRepository.GetWeekDateRange(weekNumber);
var parts = weekNumber.Split('-'); // if (dateRange.HasValue)
var year = int.Parse(parts[0]); // {
var week = int.Parse(parts[1].Replace("W", "")); // return dateRange.Value;
// }
// محاسبه اولین شنبه سال //
var jan1 = new DateTime(year, 1, 1); // // Fallback: محاسبه دستی اگر در کش نبود
var jan1DayOfWeek = (int)jan1.DayOfWeek; // var parts = weekNumber.Split('-');
// اگر 1 ژانویه شنبه باشد: offset=0، اگر یکشنبه: offset=6، دوشنبه: offset=5، ... // var year = int.Parse(parts[0]);
var daysToFirstSaturday = jan1DayOfWeek == 6 ? 0 : (6 - jan1DayOfWeek + 7) % 7; // var week = int.Parse(parts[1].Replace("W", ""));
var firstSaturday = jan1.AddDays(daysToFirstSaturday); //
// var jan1 = new DateTime(year, 1, 1);
var weekStart = firstSaturday.AddDays((week - 1) * 7); // var jan1DayOfWeek = (int)jan1.DayOfWeek;
var weekEnd = weekStart.AddDays(6).AddHours(23).AddMinutes(59).AddSeconds(59); // var daysToFirstSaturday = jan1DayOfWeek == 6 ? 0 : (6 - jan1DayOfWeek + 7) % 7;
// var firstSaturday = jan1.AddDays(daysToFirstSaturday);
return (weekStart, weekEnd); //
} // var weekStart = firstSaturday.AddDays((week - 1) * 7);
// var weekEnd = weekStart.AddDays(6).AddHours(23).AddMinutes(59).AddSeconds(59);
//
// return (weekStart, weekEnd);
// }
/// <summary> /// <summary>
/// محاسبه مجموع تعادل‌های زیرمجموعه یک کاربر تا maxLevel لول پایین‌تر /// محاسبه مجموع تعادل‌های زیرمجموعه یک کاربر تا maxLevel لول پایین‌تر
@@ -4,11 +4,11 @@ public class CalculateWeeklyBalancesCommandValidator : AbstractValidator<Calcula
{ {
public CalculateWeeklyBalancesCommandValidator() public CalculateWeeklyBalancesCommandValidator()
{ {
RuleFor(x => x.WeekNumber) RuleFor(x => x.WeekDefinitionId)
.NotEmpty() .NotNull()
.GreaterThan(0)
.WithMessage("شماره هفته نمی‌تواند خالی باشد") .WithMessage("شماره هفته نمی‌تواند خالی باشد")
.Matches(@"^\d{4}-W\d{2}$") ;
.WithMessage("فرمت شماره هفته باید YYYY-Www باشد (مثل 2025-W01)");
} }
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) => public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
@@ -8,7 +8,7 @@ public record CalculateWeeklyCommissionPoolCommand : IRequest<long>
/// <summary> /// <summary>
/// شماره هفته (فرمت: YYYY-Www) /// شماره هفته (فرمت: YYYY-Www)
/// </summary> /// </summary>
public string WeekNumber { get; init; } = string.Empty; public long WeekDefinitionId { get; init; }
/// <summary> /// <summary>
/// آیا محاسبه مجدد انجام شود؟ /// آیا محاسبه مجدد انجام شود؟
@@ -3,39 +3,50 @@ namespace CMSMicroservice.Application.CommissionCQ.Commands.CalculateWeeklyCommi
public class CalculateWeeklyCommissionPoolCommandHandler : IRequestHandler<CalculateWeeklyCommissionPoolCommand, long> public class CalculateWeeklyCommissionPoolCommandHandler : IRequestHandler<CalculateWeeklyCommissionPoolCommand, long>
{ {
private readonly IApplicationDbContext _context; private readonly IApplicationDbContext _context;
private readonly IWeekDefinitionRepository _weekRepository;
public CalculateWeeklyCommissionPoolCommandHandler(IApplicationDbContext context) public CalculateWeeklyCommissionPoolCommandHandler(
IApplicationDbContext context,
IWeekDefinitionRepository weekRepository)
{ {
_context = context; _context = context;
_weekRepository = weekRepository;
} }
public async Task<long> Handle(CalculateWeeklyCommissionPoolCommand request, CancellationToken cancellationToken) public async Task<long> Handle(CalculateWeeklyCommissionPoolCommand request, CancellationToken cancellationToken)
{ {
// تبدیل WeekNumber به WeekDefinitionId
var weekDefinitionId = _weekRepository.GetGregorianWeekNumber(request.WeekDefinitionId);
if (weekDefinitionId == null)
{
throw new InvalidOperationException($"هفته {request.WeekDefinitionId} در سیستم تعریف نشده است");
}
// بررسی وجود استخر // بررسی وجود استخر
var existingPool = await _context.WeeklyCommissionPools var existingPool = await _context.WeeklyCommissionPools
.FirstOrDefaultAsync(x => x.WeekNumber == request.WeekNumber, cancellationToken); .FirstOrDefaultAsync(x => x.WeekDefinitionId == request.WeekDefinitionId, cancellationToken);
if (existingPool == null) if (existingPool == null)
{ {
throw new InvalidOperationException( throw new InvalidOperationException(
$"Pool هفته {request.WeekNumber} وجود ندارد. " + $"Pool هفته {request.WeekDefinitionId} وجود ندارد. " +
"Pool باید در هنگام فعالسازی باشگاه مشتریان ایجاد شده باشد" "Pool باید در هنگام فعالسازی باشگاه مشتریان ایجاد شده باشد"
); );
} }
if (existingPool.IsCalculated && !request.ForceRecalculate) if (existingPool.IsCalculated && !request.ForceRecalculate)
{ {
throw new InvalidOperationException($"استخر کمیسیون هفته {request.WeekNumber} قبلاً محاسبه شده است"); throw new InvalidOperationException($"استخر کمیسیون هفته {request.WeekDefinitionId} قبلاً محاسبه شده است");
} }
// بررسی وجود تعادل‌های هفتگی // بررسی وجود تعادل‌های هفتگی
var weeklyBalances = await _context.NetworkWeeklyBalances var weeklyBalances = await _context.NetworkWeeklyBalances
.Where(x => x.WeekNumber == request.WeekNumber) .Where(x => x.WeekDefinitionId == request.WeekDefinitionId)
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
if (!weeklyBalances.Any()) if (!weeklyBalances.Any())
{ {
throw new InvalidOperationException($"تعادل‌های هفته {request.WeekNumber} هنوز محاسبه نشده است. ابتدا CalculateWeeklyBalances را اجرا کنید"); throw new InvalidOperationException($"تعادل‌های هفته {request.WeekDefinitionId} هنوز محاسبه نشده است. ابتدا CalculateWeeklyBalances را اجرا کنید");
} }
// ⭐ Pool از قبل پُر شده (توسط ActivateClubMembership) // ⭐ Pool از قبل پُر شده (توسط ActivateClubMembership)
@@ -66,7 +77,7 @@ public class CalculateWeeklyCommissionPoolCommandHandler : IRequestHandler<Calcu
if (request.ForceRecalculate) if (request.ForceRecalculate)
{ {
var oldPayouts = await _context.UserCommissionPayouts var oldPayouts = await _context.UserCommissionPayouts
.Where(p => p.WeekNumber == request.WeekNumber) .Where(p => p.WeekDefinitionId == request.WeekDefinitionId)
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
if (oldPayouts.Any()) if (oldPayouts.Any())
@@ -110,7 +121,7 @@ public class CalculateWeeklyCommissionPoolCommandHandler : IRequestHandler<Calcu
var payout = new UserCommissionPayout var payout = new UserCommissionPayout
{ {
UserId = balance.UserId, UserId = balance.UserId,
WeekNumber = request.WeekNumber, WeekDefinitionId = request.WeekDefinitionId,
WeeklyPoolId = existingPool.Id, WeeklyPoolId = existingPool.Id,
BalancesEarned = userBalance, BalancesEarned = userBalance,
ValuePerBalance = valuePerBalance, ValuePerBalance = valuePerBalance,
@@ -138,7 +149,7 @@ public class CalculateWeeklyCommissionPoolCommandHandler : IRequestHandler<Calcu
{ {
UserCommissionPayoutId = payout.Id, UserCommissionPayoutId = payout.Id,
UserId = payout.UserId, UserId = payout.UserId,
WeekNumber = request.WeekNumber, WeekDefinitionId = request.WeekDefinitionId,
AmountBefore = 0, AmountBefore = 0,
AmountAfter = payout.TotalAmount, AmountAfter = payout.TotalAmount,
OldStatus = default(CommissionPayoutStatus), OldStatus = default(CommissionPayoutStatus),
@@ -4,11 +4,10 @@ public class CalculateWeeklyCommissionPoolCommandValidator : AbstractValidator<C
{ {
public CalculateWeeklyCommissionPoolCommandValidator() public CalculateWeeklyCommissionPoolCommandValidator()
{ {
RuleFor(x => x.WeekNumber) RuleFor(x => x.WeekDefinitionId)
.NotEmpty() .NotNull()
.WithMessage("شماره هفته نمی‌تواند خالی باشد") .GreaterThan(0)
.Matches(@"^\d{4}-W\d{2}$") .WithMessage("شماره هفته نمی‌تواند خالی باشد");
.WithMessage("فرمت شماره هفته باید YYYY-Www باشد");
} }
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) => public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
@@ -8,7 +8,7 @@ public record ProcessUserPayoutsCommand : IRequest<int>
/// <summary> /// <summary>
/// شماره هفته /// شماره هفته
/// </summary> /// </summary>
public string WeekNumber { get; init; } = string.Empty; public long WeekDefinitionId { get; init; }
/// <summary> /// <summary>
/// آیا پرداخت مجدد انجام شود؟ /// آیا پرداخت مجدد انجام شود؟
@@ -3,31 +3,41 @@ namespace CMSMicroservice.Application.CommissionCQ.Commands.ProcessUserPayouts;
public class ProcessUserPayoutsCommandHandler : IRequestHandler<ProcessUserPayoutsCommand, int> public class ProcessUserPayoutsCommandHandler : IRequestHandler<ProcessUserPayoutsCommand, int>
{ {
private readonly IApplicationDbContext _context; private readonly IApplicationDbContext _context;
private readonly IWeekDefinitionRepository _weekRepository;
public ProcessUserPayoutsCommandHandler(IApplicationDbContext context) public ProcessUserPayoutsCommandHandler(
IApplicationDbContext context,
IWeekDefinitionRepository weekRepository)
{ {
_context = context; _context = context;
_weekRepository = weekRepository;
} }
public async Task<int> Handle(ProcessUserPayoutsCommand request, CancellationToken cancellationToken) public async Task<int> Handle(ProcessUserPayoutsCommand request, CancellationToken cancellationToken)
{ {
var regorianWeekNumber = _weekRepository.GetGregorianWeekNumber(request.WeekDefinitionId);
if (regorianWeekNumber == null)
{
throw new InvalidOperationException($"هفته {request.WeekDefinitionId} در سیستم تعریف نشده است");
}
// بررسی وجود استخر // بررسی وجود استخر
var pool = await _context.WeeklyCommissionPools var pool = await _context.WeeklyCommissionPools
.FirstOrDefaultAsync(x => x.WeekNumber == request.WeekNumber, cancellationToken); .FirstOrDefaultAsync(x => x.WeekDefinitionId == request.WeekDefinitionId, cancellationToken);
if (pool == null || !pool.IsCalculated) if (pool == null || !pool.IsCalculated)
{ {
throw new InvalidOperationException($"استخر کمیسیون هفته {request.WeekNumber} هنوز محاسبه نشده است"); throw new InvalidOperationException($"استخر کمیسیون هفته {request.WeekDefinitionId} هنوز محاسبه نشده است");
} }
// بررسی پرداخت قبلی // بررسی پرداخت قبلی
var existingPayouts = await _context.UserCommissionPayouts var existingPayouts = await _context.UserCommissionPayouts
.Where(x => x.WeekNumber == request.WeekNumber) .Where(x => x.WeekDefinitionId == request.WeekDefinitionId)
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
if (existingPayouts.Any() && !request.ForceReprocess) if (existingPayouts.Any() && !request.ForceReprocess)
{ {
throw new InvalidOperationException($"پرداخت‌های هفته {request.WeekNumber} قبلاً انجام شده است"); throw new InvalidOperationException($"پرداخت‌های هفته {request.WeekDefinitionId} قبلاً انجام شده است");
} }
// حذف پرداخت‌های قبلی در صورت ForceReprocess // حذف پرداخت‌های قبلی در صورت ForceReprocess
@@ -46,12 +56,12 @@ public class ProcessUserPayoutsCommandHandler : IRequestHandler<ProcessUserPayou
// دریافت همه تعادل‌های هفتگی (شامل صفرها هم برای محاسبه زیرمجموعه) // دریافت همه تعادل‌های هفتگی (شامل صفرها هم برای محاسبه زیرمجموعه)
var allWeeklyBalances = await _context.NetworkWeeklyBalances var allWeeklyBalances = await _context.NetworkWeeklyBalances
.Where(x => x.WeekNumber == request.WeekNumber) .Where(x => x.WeekDefinitionId == request.WeekDefinitionId)
.ToDictionaryAsync(x => x.UserId, cancellationToken); .ToDictionaryAsync(x => x.UserId, cancellationToken);
// دریافت کاربرانی که تعادل > 0 دارند (یا زیرمجموعه‌شان دارد) // دریافت کاربرانی که تعادل > 0 دارند (یا زیرمجموعه‌شان دارد)
var usersWithBalances = await _context.NetworkWeeklyBalances var usersWithBalances = await _context.NetworkWeeklyBalances
.Where(x => x.WeekNumber == request.WeekNumber && x.TotalBalances > 0) .Where(x => x.WeekDefinitionId == request.WeekDefinitionId && x.TotalBalances > 0)
.Select(x => x.UserId) .Select(x => x.UserId)
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
@@ -82,7 +92,7 @@ public class ProcessUserPayoutsCommandHandler : IRequestHandler<ProcessUserPayou
// ⭐ محاسبه مجموع تعادل‌های زیرمجموعه تا maxNetworkLevel لول // ⭐ محاسبه مجموع تعادل‌های زیرمجموعه تا maxNetworkLevel لول
var subordinateBalances = await CalculateSubordinateBalancesAsync( var subordinateBalances = await CalculateSubordinateBalancesAsync(
userId, userId,
request.WeekNumber, request.WeekDefinitionId,
allWeeklyBalances, allWeeklyBalances,
maxNetworkLevel, maxNetworkLevel,
cancellationToken cancellationToken
@@ -103,7 +113,7 @@ public class ProcessUserPayoutsCommandHandler : IRequestHandler<ProcessUserPayou
var payout = new UserCommissionPayout var payout = new UserCommissionPayout
{ {
UserId = userId, UserId = userId,
WeekNumber = request.WeekNumber, WeekDefinitionId = request.WeekDefinitionId,
WeeklyPoolId = pool.Id, WeeklyPoolId = pool.Id,
BalancesEarned = totalBalancesWithSubordinates, // ⭐ شامل زیرمجموعه BalancesEarned = totalBalancesWithSubordinates, // ⭐ شامل زیرمجموعه
ValuePerBalance = pool.ValuePerBalance, ValuePerBalance = pool.ValuePerBalance,
@@ -129,7 +139,7 @@ public class ProcessUserPayoutsCommandHandler : IRequestHandler<ProcessUserPayou
{ {
UserCommissionPayoutId = payout.Id, UserCommissionPayoutId = payout.Id,
UserId = payout.UserId, UserId = payout.UserId,
WeekNumber = request.WeekNumber, WeekDefinitionId = request.WeekDefinitionId,
AmountBefore = 0, AmountBefore = 0,
AmountAfter = payout.TotalAmount, AmountAfter = payout.TotalAmount,
OldStatus = default(CommissionPayoutStatus), OldStatus = default(CommissionPayoutStatus),
@@ -180,7 +190,7 @@ public class ProcessUserPayoutsCommandHandler : IRequestHandler<ProcessUserPayou
/// </summary> /// </summary>
private async Task<int> CalculateSubordinateBalancesAsync( private async Task<int> CalculateSubordinateBalancesAsync(
long userId, long userId,
string weekNumber, long WeekDefinitionId,
Dictionary<long, NetworkWeeklyBalance> allBalances, Dictionary<long, NetworkWeeklyBalance> allBalances,
int maxLevel, int maxLevel,
CancellationToken cancellationToken) CancellationToken cancellationToken)
@@ -4,11 +4,10 @@ public class ProcessUserPayoutsCommandValidator : AbstractValidator<ProcessUserP
{ {
public ProcessUserPayoutsCommandValidator() public ProcessUserPayoutsCommandValidator()
{ {
RuleFor(x => x.WeekNumber) RuleFor(x => x.WeekDefinitionId)
.NotEmpty() .NotNull()
.WithMessage("شماره هفته نمی‌تواند خالی باشد") .GreaterThan(0)
.Matches(@"^\d{4}-W\d{2}$") .WithMessage("شماره هفته نمی‌تواند خالی باشد");
.WithMessage("فرمت شماره هفته باید YYYY-Www باشد");
} }
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) => public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
@@ -73,7 +73,7 @@ public class ProcessWithdrawalCommandHandler : IRequestHandler<ProcessWithdrawal
UserId = payout.UserId, UserId = payout.UserId,
Iban = payout.IbanNumber ?? throw new InvalidOperationException("شماره شبا یافت نشد"), Iban = payout.IbanNumber ?? throw new InvalidOperationException("شماره شبا یافت نشد"),
AccountHolderName = $"{payout.User.FirstName} {payout.User.LastName}", AccountHolderName = $"{payout.User.FirstName} {payout.User.LastName}",
Description = $"برداشت کمیسیون هفته {payout.WeekNumber}", Description = $"برداشت کمیسیون هفته {payout.WeekDefinitionId}",
InternalRefId = $"PAYOUT-{payout.Id}" InternalRefId = $"PAYOUT-{payout.Id}"
}; };
@@ -120,7 +120,7 @@ public class ProcessWithdrawalCommandHandler : IRequestHandler<ProcessWithdrawal
{ {
UserCommissionPayoutId = payout.Id, UserCommissionPayoutId = payout.Id,
UserId = payout.UserId, UserId = payout.UserId,
WeekNumber = payout.WeekNumber, WeekDefinitionId = payout.WeekDefinitionId,
AmountBefore = payout.TotalAmount, AmountBefore = payout.TotalAmount,
AmountAfter = payout.TotalAmount, AmountAfter = payout.TotalAmount,
OldStatus = oldStatus, OldStatus = oldStatus,
@@ -147,7 +147,7 @@ public class ProcessWithdrawalCommandHandler : IRequestHandler<ProcessWithdrawal
{ {
UserCommissionPayoutId = payout.Id, UserCommissionPayoutId = payout.Id,
UserId = payout.UserId, UserId = payout.UserId,
WeekNumber = payout.WeekNumber, WeekDefinitionId = payout.WeekDefinitionId,
AmountBefore = payout.TotalAmount, AmountBefore = payout.TotalAmount,
AmountAfter = payout.TotalAmount, AmountAfter = payout.TotalAmount,
OldStatus = oldStatus, OldStatus = oldStatus,
@@ -48,7 +48,7 @@ public class RequestWithdrawalCommandHandler : IRequestHandler<RequestWithdrawal
{ {
UserCommissionPayoutId = payout.Id, UserCommissionPayoutId = payout.Id,
UserId = payout.UserId, UserId = payout.UserId,
WeekNumber = payout.WeekNumber, WeekDefinitionId = payout.WeekDefinitionId,
AmountBefore = payout.TotalAmount, AmountBefore = payout.TotalAmount,
AmountAfter = payout.TotalAmount, AmountAfter = payout.TotalAmount,
OldStatus = oldStatus, OldStatus = oldStatus,
@@ -5,7 +5,7 @@ public record TriggerWeeklyCalculationCommand : IRequest<TriggerWeeklyCalculatio
/// <summary> /// <summary>
/// شماره هفته (فرمت: "YYYY-Www") /// شماره هفته (فرمت: "YYYY-Www")
/// </summary> /// </summary>
public string WeekNumber { get; init; } = string.Empty; public long WeekDefinitionId { get; init; }
/// <summary> /// <summary>
/// اگر true باشد، محاسبات قبلی را حذف و دوباره محاسبه می‌کند /// اگر true باشد، محاسبات قبلی را حذف و دوباره محاسبه می‌کند
@@ -26,8 +26,9 @@ public class TriggerWeeklyCalculationCommandHandler : IRequestHandler<TriggerWee
try try
{ {
// Validate week number format // Validate week number format
if (string.IsNullOrWhiteSpace(request.WeekNumber)) if (request.WeekDefinitionId<=0)
{ {
return new TriggerWeeklyCalculationResponseDto return new TriggerWeeklyCalculationResponseDto
{ {
@@ -45,7 +46,7 @@ public class TriggerWeeklyCalculationCommandHandler : IRequestHandler<TriggerWee
{ {
await _mediator.Send(new CalculateWeeklyBalancesCommand await _mediator.Send(new CalculateWeeklyBalancesCommand
{ {
WeekNumber = request.WeekNumber, WeekDefinitionId = request.WeekDefinitionId,
ForceRecalculate = request.ForceRecalculate ForceRecalculate = request.ForceRecalculate
}, cancellationToken); }, cancellationToken);
steps.Add("محاسبه امتیازات هفتگی"); steps.Add("محاسبه امتیازات هفتگی");
@@ -56,7 +57,7 @@ public class TriggerWeeklyCalculationCommandHandler : IRequestHandler<TriggerWee
{ {
await _mediator.Send(new CalculateWeeklyCommissionPoolCommand await _mediator.Send(new CalculateWeeklyCommissionPoolCommand
{ {
WeekNumber = request.WeekNumber, WeekDefinitionId = request.WeekDefinitionId,
ForceRecalculate = request.ForceRecalculate ForceRecalculate = request.ForceRecalculate
}, cancellationToken); }, cancellationToken);
steps.Add("محاسبه استخر و پرداخت کاربران"); steps.Add("محاسبه استخر و پرداخت کاربران");
@@ -65,7 +66,7 @@ public class TriggerWeeklyCalculationCommandHandler : IRequestHandler<TriggerWee
return new TriggerWeeklyCalculationResponseDto return new TriggerWeeklyCalculationResponseDto
{ {
Success = true, Success = true,
Message = $"محاسبات هفته {request.WeekNumber} با موفقیت انجام شد. مراحل: {string.Join(", ", steps)}", Message = $"محاسبات هفته {request.WeekDefinitionId} با موفقیت انجام شد. مراحل: {string.Join(", ", steps)}",
ExecutionId = executionId, ExecutionId = executionId,
StartedAt = startedAt StartedAt = startedAt
}; };
@@ -0,0 +1,27 @@
using CMSMicroservice.Application.CommissionCQ.Commands.ProcessWithdrawal;
namespace CMSMicroservice.Application.CommissionCQ.Commands.TriggerWeeklyCalculation;
public class TriggerWeeklyCalculationCommandValidator : AbstractValidator<TriggerWeeklyCalculationCommand>
{
public TriggerWeeklyCalculationCommandValidator()
{
RuleFor(x => x.WeekDefinitionId)
.NotNull()
.GreaterThan(0)
.WithMessage("شماره هفته نمی‌تواند خالی باشد");
}
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
{
var result = await ValidateAsync(
ValidationContext<TriggerWeeklyCalculationCommand>.CreateWithOptions(
(TriggerWeeklyCalculationCommand)model,
x => x.IncludeProperties(propertyName)));
if (result.IsValid)
return Array.Empty<string>();
return result.Errors.Select(e => e.ErrorMessage);
};
}
@@ -8,12 +8,12 @@ public record GetAllWeeklyPoolsQuery : IRequest<GetAllWeeklyPoolsResponseDto>
/// <summary> /// <summary>
/// از هفته (فیلتر اختیاری) /// از هفته (فیلتر اختیاری)
/// </summary> /// </summary>
public string? FromWeek { get; init; } public int? FromWeekOrder { get; init; }
/// <summary> /// <summary>
/// تا هفته (فیلتر اختیاری) /// تا هفته (فیلتر اختیاری)
/// </summary> /// </summary>
public string? ToWeek { get; init; } public int? ToWeekOrder { get; init; }
/// <summary> /// <summary>
/// فقط Pool های محاسبه شده /// فقط Pool های محاسبه شده
@@ -3,25 +3,31 @@ namespace CMSMicroservice.Application.CommissionCQ.Queries.GetAllWeeklyPools;
public class GetAllWeeklyPoolsQueryHandler : IRequestHandler<GetAllWeeklyPoolsQuery, GetAllWeeklyPoolsResponseDto> public class GetAllWeeklyPoolsQueryHandler : IRequestHandler<GetAllWeeklyPoolsQuery, GetAllWeeklyPoolsResponseDto>
{ {
private readonly IApplicationDbContext _context; private readonly IApplicationDbContext _context;
private readonly IWeekDefinitionRepository _weekDefinitionRepository;
public GetAllWeeklyPoolsQueryHandler(IApplicationDbContext context) public GetAllWeeklyPoolsQueryHandler(
IApplicationDbContext context,
IWeekDefinitionRepository weekDefinitionRepository)
{ {
_context = context; _context = context;
_weekDefinitionRepository = weekDefinitionRepository;
} }
public async Task<GetAllWeeklyPoolsResponseDto> Handle(GetAllWeeklyPoolsQuery request, CancellationToken cancellationToken) public async Task<GetAllWeeklyPoolsResponseDto> Handle(GetAllWeeklyPoolsQuery request, CancellationToken cancellationToken)
{ {
var query = _context.WeeklyCommissionPools.AsNoTracking(); var query = _context.WeeklyCommissionPools
.Include(i=> i.WeekDefinition)
.AsNoTracking();
// Apply filters // Apply filters
if (!string.IsNullOrWhiteSpace(request.FromWeek)) if (request.FromWeekOrder!=null)
{ {
query = query.Where(x => string.Compare(x.WeekNumber, request.FromWeek) >= 0); query = query.Where(x => x.WeekDefinition.WeekOrder>=request.FromWeekOrder );
} }
if (!string.IsNullOrWhiteSpace(request.ToWeek)) if (request.ToWeekOrder!=null)
{ {
query = query.Where(x => string.Compare(x.WeekNumber, request.ToWeek) <= 0); query = query.Where(x =>x.WeekDefinition.WeekOrder<= request.ToWeekOrder);
} }
if (request.OnlyCalculated.HasValue && request.OnlyCalculated.Value) if (request.OnlyCalculated.HasValue && request.OnlyCalculated.Value)
@@ -30,7 +36,7 @@ public class GetAllWeeklyPoolsQueryHandler : IRequestHandler<GetAllWeeklyPoolsQu
} }
// Order by week number descending (newest first) // Order by week number descending (newest first)
query = query.OrderByDescending(x => x.WeekNumber); query = query.OrderByDescending(x => x.WeekDefinitionId);
// Count total // Count total
var totalCount = await query.CountAsync(cancellationToken); var totalCount = await query.CountAsync(cancellationToken);
@@ -42,7 +48,8 @@ public class GetAllWeeklyPoolsQueryHandler : IRequestHandler<GetAllWeeklyPoolsQu
.Select(x => new WeeklyCommissionPoolDto .Select(x => new WeeklyCommissionPoolDto
{ {
Id = x.Id, Id = x.Id,
WeekNumber = x.WeekNumber, WeekDefinitionId = x.WeekDefinitionId,
WeekDisplayName = x.WeekDefinition.PersianWeekNumber,
TotalPoolAmount = x.TotalPoolAmount, TotalPoolAmount = x.TotalPoolAmount,
TotalBalances = x.TotalBalances, TotalBalances = x.TotalBalances,
ValuePerBalance = x.ValuePerBalance, ValuePerBalance = x.ValuePerBalance,
@@ -52,6 +59,19 @@ public class GetAllWeeklyPoolsQueryHandler : IRequestHandler<GetAllWeeklyPoolsQu
}) })
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
// Populate WeekDisplayName from cache
// foreach (var pool in pools)
// {
// // WeeklyCommissionPoolDto is a record, need to create new instance with display name
// // Since records are immutable, we can't modify them directly
// }
// // Create new list with WeekDisplayName populated
// var poolsWithDisplayName = pools.Select(p => p with
// {
// WeekDisplayName = _weekDefinitionRepository.GetDisplayNameByGregorianWeekNumber(p.WeekNumber)
// }).ToList();
return new GetAllWeeklyPoolsResponseDto return new GetAllWeeklyPoolsResponseDto
{ {
MetaData = new MetaDataDto MetaData = new MetaDataDto
@@ -9,7 +9,8 @@ public record GetAllWeeklyPoolsResponseDto
public record WeeklyCommissionPoolDto public record WeeklyCommissionPoolDto
{ {
public long Id { get; init; } public long Id { get; init; }
public string WeekNumber { get; init; } = string.Empty; public long WeekDefinitionId { get; init; }
public string WeekDisplayName { get; init; } = string.Empty;
public long TotalPoolAmount { get; init; } public long TotalPoolAmount { get; init; }
public int TotalBalances { get; init; } public int TotalBalances { get; init; }
public long ValuePerBalance { get; init; } public long ValuePerBalance { get; init; }
@@ -2,29 +2,36 @@ using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Enums; using CMSMicroservice.Domain.Enums;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.Globalization; using System.Globalization;
using DateTimeConverterCL;
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetAvailableWeeks; namespace CMSMicroservice.Application.CommissionCQ.Queries.GetAvailableWeeks;
public class GetAvailableWeeksQueryHandler : IRequestHandler<GetAvailableWeeksQuery, GetAvailableWeeksResponseDto> public class GetAvailableWeeksQueryHandler : IRequestHandler<GetAvailableWeeksQuery, GetAvailableWeeksResponseDto>
{ {
private readonly IApplicationDbContext _context; private readonly IApplicationDbContext _context;
private readonly IWeekDefinitionRepository _weekDefinitionRepository;
public GetAvailableWeeksQueryHandler(IApplicationDbContext context) public GetAvailableWeeksQueryHandler(
IApplicationDbContext context,
IWeekDefinitionRepository weekDefinitionRepository)
{ {
_context = context; _context = context;
_weekDefinitionRepository = weekDefinitionRepository;
} }
public async Task<GetAvailableWeeksResponseDto> Handle( public async Task<GetAvailableWeeksResponseDto> Handle(
GetAvailableWeeksQuery request, GetAvailableWeeksQuery request,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var currentDate = DateTime.Now; var currentWeekDef = _weekDefinitionRepository.GetCurrentWeek();
var currentWeekNumber = GetWeekNumber(currentDate); // var currentWeekNumber = currentWeekDef != null
// ? $"{currentWeekDef.GregorianYear}-W{currentWeekDef.GregorianWeekNumber:D2}"
// : _weekDefinitionRepository.CalculateGregorianWeekNumber(DateTime.Now);
// دریافت هفته‌های محاسبه شده از دیتابیس // دریافت هفته‌های محاسبه شده از دیتابیس
var calculatedPools = await _context.WeeklyCommissionPools var calculatedPools = await _context.WeeklyCommissionPools
.Where(p => p.IsCalculated) .Where(p => p.IsCalculated)
.OrderByDescending(p => p.WeekNumber) .OrderByDescending(p => p.WeekDefinitionId)
.Take(request.PastWeeksCount) .Take(request.PastWeeksCount)
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
@@ -32,7 +39,7 @@ public class GetAvailableWeeksQueryHandler : IRequestHandler<GetAvailableWeeksQu
var executionLogs = await _context.WorkerExecutionLogs var executionLogs = await _context.WorkerExecutionLogs
.Where(log => log.Status == WorkerExecutionStatus.Success || .Where(log => log.Status == WorkerExecutionStatus.Success ||
log.Status == WorkerExecutionStatus.Failed) log.Status == WorkerExecutionStatus.Failed)
.GroupBy(log => log.WeekNumber) .GroupBy(log => log.WeekDefinitionId)
.Select(g => new .Select(g => new
{ {
WeekNumber = g.Key, WeekNumber = g.Key,
@@ -43,15 +50,16 @@ public class GetAvailableWeeksQueryHandler : IRequestHandler<GetAvailableWeeksQu
var allWeeks = new List<WeekInfoDto>(); var allWeeks = new List<WeekInfoDto>();
// هفته جاری // هفته جاری
var currentWeekInfo = CreateWeekInfo(currentDate, currentWeekNumber, calculatedPools, executionLogs); var currentDate = DateTime.Now;
var currentWeekInfo = CreateWeekInfo(currentDate, currentWeekDef.Id, calculatedPools, executionLogs);
// هفته‌های گذشته (12 هفته) // هفته‌های گذشته (12 هفته)
var pastWeeks = new List<WeekInfoDto>(); var pastWeeks = new List<WeekInfoDto>();
for (int i = 1; i <= request.PastWeeksCount; i++) for (int i = 1; i <= request.PastWeeksCount; i++)
{ {
var pastDate = currentDate.AddDays(-7 * i); var pastDate = currentDate.AddDays(-7 * i);
var weekNumber = GetWeekNumber(pastDate); var weekNumber = GetWeekNumberUsingRepository(pastDate);
pastWeeks.Add(CreateWeekInfo(pastDate, weekNumber, calculatedPools, executionLogs)); pastWeeks.Add(CreateWeekInfo(pastDate, currentWeekDef.Id, calculatedPools, executionLogs));
} }
// هفته‌های آینده (4 هفته) // هفته‌های آینده (4 هفته)
@@ -59,8 +67,8 @@ public class GetAvailableWeeksQueryHandler : IRequestHandler<GetAvailableWeeksQu
for (int i = 1; i <= request.FutureWeeksCount; i++) for (int i = 1; i <= request.FutureWeeksCount; i++)
{ {
var futureDate = currentDate.AddDays(7 * i); var futureDate = currentDate.AddDays(7 * i);
var weekNumber = GetWeekNumber(futureDate); var weekNumber = GetWeekNumberUsingRepository(futureDate);
futureWeeks.Add(CreateWeekInfo(futureDate, weekNumber, calculatedPools, executionLogs)); futureWeeks.Add(CreateWeekInfo(futureDate, currentWeekDef.Id, calculatedPools, executionLogs));
} }
// تفکیک به calculated و pending // تفکیک به calculated و pending
@@ -76,26 +84,54 @@ public class GetAvailableWeeksQueryHandler : IRequestHandler<GetAvailableWeeksQu
}; };
} }
private string GetWeekNumberUsingRepository(DateTime date)
{
var weekDef = _weekDefinitionRepository.GetWeekByDate(date);
if (weekDef != null)
{
return $"{weekDef.GregorianYear}-W{weekDef.GregorianWeekNumber:D2}";
}
// Fallback to manual calculation if not in cache
return _weekDefinitionRepository.CalculateGregorianWeekNumber(date);
}
private WeekInfoDto CreateWeekInfo( private WeekInfoDto CreateWeekInfo(
DateTime date, DateTime date,
string weekNumber, long weekDefinitionId,
List<Domain.Entities.Commission.WeeklyCommissionPool> calculatedPools, List<Domain.Entities.Commission.WeeklyCommissionPool> calculatedPools,
Dictionary<string, Domain.Entities.Commission.WorkerExecutionLog> executionLogs) Dictionary<long, Domain.Entities.Commission.WorkerExecutionLog> executionLogs)
{ {
// Get the actual WeekDefinition for this date
var weekDef = _weekDefinitionRepository.GetWeekByDate(date);
var actualWeekDefId = weekDef?.Id ?? weekDefinitionId;
var (startDate, endDate) = GetWeekRange(date); var (startDate, endDate) = GetWeekRange(date);
var pool = calculatedPools.FirstOrDefault(p => p.WeekNumber == weekNumber); var pool = calculatedPools.FirstOrDefault(p => p.WeekDefinitionId == actualWeekDefId);
var log = executionLogs.GetValueOrDefault(weekNumber); var log = executionLogs.GetValueOrDefault(actualWeekDefId);
var isCalculated = pool != null && pool.IsCalculated; var isCalculated = pool != null && pool.IsCalculated;
//
// // تبدیل تاریخ به شمسی برای نمایش
// var persianCalendar = new PersianCalendar();
// var startDatePersian = $"{persianCalendar.GetYear(startDate):D4}/{persianCalendar.GetMonth(startDate):D2}/{persianCalendar.GetDayOfMonth(startDate):D2}";
// var endDatePersian = $"{persianCalendar.GetYear(endDate):D4}/{persianCalendar.GetMonth(endDate):D2}/{persianCalendar.GetDayOfMonth(endDate):D2}";
// تبدیل تاریخ به شمسی برای نمایش // استفاده از DisplayName از WeekDefinition
var persianCalendar = new PersianCalendar(); var displayName = weekDef?.DisplayName ?? GetDisplayName(startDate);
var startDatePersian = $"{persianCalendar.GetYear(startDate):D4}/{persianCalendar.GetMonth(startDate):D2}/{persianCalendar.GetDayOfMonth(startDate):D2}"; string displayText;
var endDatePersian = $"{persianCalendar.GetYear(endDate):D4}/{persianCalendar.GetMonth(endDate):D2}/{persianCalendar.GetDayOfMonth(endDate):D2}";
// تبدیل weekNumber به شمسی فقط برای نمایش if (!string.IsNullOrEmpty(displayName))
var persianWeekNumber = ConvertWeekNumberToPersian(weekNumber, startDate); {
var displayText = $"{persianWeekNumber} ({startDatePersian} - {endDatePersian})"; // اگر DisplayName وجود داشت، از آن استفاده کن
displayText = $"{displayName} ({startDate.MiladiToJalali()} - {endDate.MiladiToJalali()})";
}
else
{
// تبدیل weekNumber به شمسی فقط برای نمایش (fallback)
var persianWeekNumber = _weekDefinitionRepository.GetWeekById(actualWeekDefId);
displayText = $"{persianWeekNumber?.PersianWeekNumber} ({startDate.MiladiToJalali()} - {endDate.MiladiToJalali()})";
}
if (isCalculated) if (isCalculated)
{ {
@@ -104,7 +140,8 @@ public class GetAvailableWeeksQueryHandler : IRequestHandler<GetAvailableWeeksQu
return new WeekInfoDto return new WeekInfoDto
{ {
WeekNumber = weekNumber, // میلادی (برای API) WeekDefinitionId = actualWeekDefId,
DisplayName = weekDef?.DisplayName ?? _weekDefinitionRepository.GetWeekById(actualWeekDefId)?.DisplayName ?? "", // میلادی (برای API)
StartDate = startDate, StartDate = startDate,
EndDate = endDate, EndDate = endDate,
IsCalculated = isCalculated, IsCalculated = isCalculated,
@@ -116,40 +153,16 @@ public class GetAvailableWeeksQueryHandler : IRequestHandler<GetAvailableWeeksQu
}; };
} }
private static string GetWeekNumber(DateTime date) private (DateTime startDate, DateTime endDate) GetWeekRange(DateTime date)
{ {
var year = date.Year; // First try to get from repository
var weekDef = _weekDefinitionRepository.GetWeekByDate(date);
// پیدا کردن اولین شنبه سال if (weekDef != null)
var jan1 = new DateTime(year, 1, 1);
var jan1DayOfWeek = (int)jan1.DayOfWeek; // 0=Sunday, 1=Monday, ..., 6=Saturday
// محاسبه تعداد روزهایی که باید اضافه کنیم تا به اولین شنبه برسیم
var daysToFirstSaturday = jan1DayOfWeek == 6 ? 0 : (6 - jan1DayOfWeek + 7) % 7;
var firstSaturday = jan1.AddDays(daysToFirstSaturday);
// پیدا کردن شنبه شروع هفته جاری
var currentDayOfWeek = (int)date.DayOfWeek; // 0=Sun, 1=Mon, ..., 6=Sat
var daysToCurrentSaturday = currentDayOfWeek == 6 ? 0 : (currentDayOfWeek + 1) % 7;
var weekStartSaturday = date.Date.AddDays(-daysToCurrentSaturday);
// محاسبه شماره هفته
int weekNum;
if (weekStartSaturday < firstSaturday)
{ {
weekNum = 1; // هفته اول سال return (weekDef.StartDate, weekDef.EndDate);
}
else
{
var daysSinceFirstSaturday = (weekStartSaturday - firstSaturday).Days;
weekNum = (daysSinceFirstSaturday / 7) + 1;
} }
return $"{year}-W{weekNum:D2}"; // Fallback: manual calculation
}
private static (DateTime startDate, DateTime endDate) GetWeekRange(DateTime date)
{
var dayOfWeek = (int)date.DayOfWeek; var dayOfWeek = (int)date.DayOfWeek;
// محاسبه تعداد روزهایی که باید عقب برویم تا به شنبه برسیم // محاسبه تعداد روزهایی که باید عقب برویم تا به شنبه برسیم
// شنبه = 6, یکشنبه = 0, دوشنبه = 1, ..., جمعه = 5 // شنبه = 6, یکشنبه = 0, دوشنبه = 1, ..., جمعه = 5
@@ -161,32 +174,38 @@ public class GetAvailableWeeksQueryHandler : IRequestHandler<GetAvailableWeeksQu
return (startDate, endDate); return (startDate, endDate);
} }
private static string ConvertWeekNumberToPersian(string gregorianWeekNumber, DateTime weekStartDate) private string GetDisplayName(DateTime date)
{ {
// ورودی: "2025-W48" var weekDef = _weekDefinitionRepository.GetWeekByDate(date);
var persianCalendar = new PersianCalendar(); return weekDef?.DisplayName ?? "";
var persianYear = persianCalendar.GetYear(weekStartDate);
// محاسبه شماره هفته شمسی
var jan1Persian = persianCalendar.ToDateTime(persianYear, 1, 1, 0, 0, 0, 0);
// پیدا کردن اولین شنبه سال شمسی
var jan1DayOfWeek = (int)jan1Persian.DayOfWeek;
var daysToFirstSaturday = jan1DayOfWeek == 6 ? 0 : (6 - jan1DayOfWeek + 7) % 7;
var firstSaturday = jan1Persian.AddDays(daysToFirstSaturday);
// محاسبه شماره هفته
int weekNum;
if (weekStartDate < firstSaturday)
{
weekNum = 1;
}
else
{
var daysSinceFirstSaturday = (weekStartDate - firstSaturday).Days;
weekNum = (daysSinceFirstSaturday / 7) + 1;
}
return $"{persianYear}-W{weekNum:D2}";
} }
// private static string ConvertWeekNumberToPersian(string gregorianWeekNumber, DateTime weekStartDate)
// {
// // ورودی: "2025-W48"
// var persianCalendar = new PersianCalendar();
// var persianYear = persianCalendar.GetYear(weekStartDate);
//
// // محاسبه شماره هفته شمسی
// var jan1Persian = persianCalendar.ToDateTime(persianYear, 1, 1, 0, 0, 0, 0);
//
// // پیدا کردن اولین شنبه سال شمسی
// var jan1DayOfWeek = (int)jan1Persian.DayOfWeek;
// var daysToFirstSaturday = jan1DayOfWeek == 6 ? 0 : (6 - jan1DayOfWeek + 7) % 7;
// var firstSaturday = jan1Persian.AddDays(daysToFirstSaturday);
//
// // محاسبه شماره هفته
// int weekNum;
// if (weekStartDate < firstSaturday)
// {
// weekNum = 1;
// }
// else
// {
// var daysSinceFirstSaturday = (weekStartDate - firstSaturday).Days;
// weekNum = (daysSinceFirstSaturday / 7) + 1;
// }
//
// return $"{persianYear}-W{weekNum:D2}";
// }
} }
@@ -17,8 +17,11 @@ public class GetAvailableWeeksResponseDto
public class WeekInfoDto public class WeekInfoDto
{ {
/// <summary>شناسه یکتای هفته</summary>
public required long WeekDefinitionId { get; init; }
/// <summary>شماره هفته (YYYY-Www)</summary> /// <summary>شماره هفته (YYYY-Www)</summary>
public required string WeekNumber { get; init; } public required string DisplayName { get; init; }
/// <summary>تاریخ شروع هفته</summary> /// <summary>تاریخ شروع هفته</summary>
public required DateTime StartDate { get; init; } public required DateTime StartDate { get; init; }
@@ -18,7 +18,7 @@ public record GetCommissionPayoutHistoryQuery : IRequest<GetCommissionPayoutHist
/// <summary> /// <summary>
/// شماره هفته (اختیاری) /// شماره هفته (اختیاری)
/// </summary> /// </summary>
public string? WeekNumber { get; init; } public long? WeekDefinitionId { get; init; }
/// <summary> /// <summary>
/// مرتب‌سازی /// مرتب‌سازی
@@ -3,15 +3,20 @@ namespace CMSMicroservice.Application.CommissionCQ.Queries.GetCommissionPayoutHi
public class GetCommissionPayoutHistoryQueryHandler : IRequestHandler<GetCommissionPayoutHistoryQuery, GetCommissionPayoutHistoryResponseDto> public class GetCommissionPayoutHistoryQueryHandler : IRequestHandler<GetCommissionPayoutHistoryQuery, GetCommissionPayoutHistoryResponseDto>
{ {
private readonly IApplicationDbContext _context; private readonly IApplicationDbContext _context;
private readonly IWeekDefinitionRepository _weekDefinitionRepository;
public GetCommissionPayoutHistoryQueryHandler(IApplicationDbContext context) public GetCommissionPayoutHistoryQueryHandler(
IApplicationDbContext context,
IWeekDefinitionRepository weekDefinitionRepository)
{ {
_context = context; _context = context;
_weekDefinitionRepository = weekDefinitionRepository;
} }
public async Task<GetCommissionPayoutHistoryResponseDto> Handle(GetCommissionPayoutHistoryQuery request, CancellationToken cancellationToken) public async Task<GetCommissionPayoutHistoryResponseDto> Handle(GetCommissionPayoutHistoryQuery request, CancellationToken cancellationToken)
{ {
var query = _context.CommissionPayoutHistories var query = _context.CommissionPayoutHistories
.Include(i=>i.WeekDefinition)
.AsNoTracking() .AsNoTracking()
.AsQueryable(); .AsQueryable();
@@ -26,9 +31,9 @@ public class GetCommissionPayoutHistoryQueryHandler : IRequestHandler<GetCommiss
query = query.Where(x => x.UserId == request.UserId.Value); query = query.Where(x => x.UserId == request.UserId.Value);
} }
if (!string.IsNullOrEmpty(request.WeekNumber)) if (request.WeekDefinitionId!=null)
{ {
query = query.Where(x => x.WeekNumber == request.WeekNumber); query = query.Where(x => x.WeekDefinitionId == request.WeekDefinitionId);
} }
query = query.ApplyOrder(sortBy: request.SortBy ?? "Created"); query = query.ApplyOrder(sortBy: request.SortBy ?? "Created");
@@ -42,7 +47,8 @@ public class GetCommissionPayoutHistoryQueryHandler : IRequestHandler<GetCommiss
Id = x.Id, Id = x.Id,
UserCommissionPayoutId = x.UserCommissionPayoutId, UserCommissionPayoutId = x.UserCommissionPayoutId,
UserId = x.UserId, UserId = x.UserId,
WeekNumber = x.WeekNumber, WeekDefinitionId = x.WeekDefinitionId,
WeekDisplayName=x.WeekDefinition!.DisplayName,
AmountBefore = x.AmountBefore, AmountBefore = x.AmountBefore,
AmountAfter = x.AmountAfter, AmountAfter = x.AmountAfter,
OldStatus = x.OldStatus, OldStatus = x.OldStatus,
@@ -54,6 +60,12 @@ public class GetCommissionPayoutHistoryQueryHandler : IRequestHandler<GetCommiss
}) })
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
// // Populate WeekDisplayName from cache
// foreach (var model in models)
// {
// model.WeekDisplayName = _weekDefinitionRepository.GetDisplayNameByGregorianWeekNumber(model.WeekNumber);
// }
return new GetCommissionPayoutHistoryResponseDto return new GetCommissionPayoutHistoryResponseDto
{ {
MetaData = meta, MetaData = meta,
@@ -13,11 +13,10 @@ public class GetCommissionPayoutHistoryQueryValidator : AbstractValidator<GetCom
.GreaterThan(0) .GreaterThan(0)
.WithMessage("شناسه کاربر معتبر نیست") .WithMessage("شناسه کاربر معتبر نیست")
.When(x => x.UserId.HasValue); .When(x => x.UserId.HasValue);
RuleFor(x => x.WeekDefinitionId)
RuleFor(x => x.WeekNumber) .NotNull()
.Matches(@"^\d{4}-W\d{2}$") .GreaterThan(0)
.WithMessage("فرمت شماره هفته باید YYYY-Www باشد") .WithMessage("شماره هفته نمی‌تواند خالی باشد");
.When(x => !string.IsNullOrEmpty(x.WeekNumber));
} }
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) => public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
@@ -11,7 +11,8 @@ public class GetCommissionPayoutHistoryResponseModel
public long Id { get; set; } public long Id { get; set; }
public long UserCommissionPayoutId { get; set; } public long UserCommissionPayoutId { get; set; }
public long UserId { get; set; } public long UserId { get; set; }
public string WeekNumber { get; set; } = string.Empty; public long WeekDefinitionId { get; set; }
public string WeekDisplayName { get; set; } = string.Empty;
public long AmountBefore { get; set; } public long AmountBefore { get; set; }
public long AmountAfter { get; set; } public long AmountAfter { get; set; }
public CommissionPayoutStatus? OldStatus { get; set; } public CommissionPayoutStatus? OldStatus { get; set; }
@@ -18,7 +18,7 @@ public record GetUserCommissionPayoutsQuery : IRequest<GetUserCommissionPayoutsR
/// <summary> /// <summary>
/// شماره هفته (اختیاری) /// شماره هفته (اختیاری)
/// </summary> /// </summary>
public string? WeekNumber { get; init; } public long? WeekDefinitionId { get; init; }
/// <summary> /// <summary>
/// مرتب‌سازی /// مرتب‌سازی
@@ -3,15 +3,20 @@ namespace CMSMicroservice.Application.CommissionCQ.Queries.GetUserCommissionPayo
public class GetUserCommissionPayoutsQueryHandler : IRequestHandler<GetUserCommissionPayoutsQuery, GetUserCommissionPayoutsResponseDto> public class GetUserCommissionPayoutsQueryHandler : IRequestHandler<GetUserCommissionPayoutsQuery, GetUserCommissionPayoutsResponseDto>
{ {
private readonly IApplicationDbContext _context; private readonly IApplicationDbContext _context;
private readonly IWeekDefinitionRepository _weekDefinitionRepository;
public GetUserCommissionPayoutsQueryHandler(IApplicationDbContext context) public GetUserCommissionPayoutsQueryHandler(
IApplicationDbContext context,
IWeekDefinitionRepository weekDefinitionRepository)
{ {
_context = context; _context = context;
_weekDefinitionRepository = weekDefinitionRepository;
} }
public async Task<GetUserCommissionPayoutsResponseDto> Handle(GetUserCommissionPayoutsQuery request, CancellationToken cancellationToken) public async Task<GetUserCommissionPayoutsResponseDto> Handle(GetUserCommissionPayoutsQuery request, CancellationToken cancellationToken)
{ {
var query = _context.UserCommissionPayouts var query = _context.UserCommissionPayouts
.Include(x => x.WeekDefinition)
.Include(x => x.User) .Include(x => x.User)
.AsNoTracking() .AsNoTracking()
.AsQueryable(); .AsQueryable();
@@ -27,9 +32,9 @@ public class GetUserCommissionPayoutsQueryHandler : IRequestHandler<GetUserCommi
query = query.Where(x => x.Status == request.Status.Value); query = query.Where(x => x.Status == request.Status.Value);
} }
if (!string.IsNullOrEmpty(request.WeekNumber)) if (request.WeekDefinitionId!=null)
{ {
query = query.Where(x => x.WeekNumber == request.WeekNumber); query = query.Where(x => x.WeekDefinitionId == request.WeekDefinitionId);
} }
query = query.ApplyOrder(sortBy: request.SortBy ?? "Created"); query = query.ApplyOrder(sortBy: request.SortBy ?? "Created");
@@ -44,7 +49,8 @@ public class GetUserCommissionPayoutsQueryHandler : IRequestHandler<GetUserCommi
UserId = x.UserId, UserId = x.UserId,
FirstName = x.User.FirstName, FirstName = x.User.FirstName,
LastName = x.User.LastName, LastName = x.User.LastName,
WeekNumber = x.WeekNumber, WeekDefinitionId = x.WeekDefinitionId,
WeekDisplayName = x.WeekDefinition != null ? x.WeekDefinition.DisplayName : "",
WeeklyPoolId = x.WeeklyPoolId, WeeklyPoolId = x.WeeklyPoolId,
BalancesEarned = x.BalancesEarned, BalancesEarned = x.BalancesEarned,
ValuePerBalance = x.ValuePerBalance, ValuePerBalance = x.ValuePerBalance,
@@ -58,6 +64,12 @@ public class GetUserCommissionPayoutsQueryHandler : IRequestHandler<GetUserCommi
}) })
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
// // Populate WeekDisplayName from cache
// foreach (var model in models)
// {
// model.WeekDisplayName = _weekDefinitionRepository.GetDisplayNameByGregorianWeekNumber(model.WeekNumber);
// }
return new GetUserCommissionPayoutsResponseDto return new GetUserCommissionPayoutsResponseDto
{ {
MetaData = meta, MetaData = meta,
@@ -14,10 +14,10 @@ public class GetUserCommissionPayoutsQueryValidator : AbstractValidator<GetUserC
.WithMessage("وضعیت معتبر نیست") .WithMessage("وضعیت معتبر نیست")
.When(x => x.Status.HasValue); .When(x => x.Status.HasValue);
RuleFor(x => x.WeekNumber) RuleFor(x => x.WeekDefinitionId)
.Matches(@"^\d{4}-W\d{2}$") .GreaterThan(0)
.WithMessage("فرمت شماره هفته باید YYYY-Www باشد") .WithMessage("شماره هفته نمی‌تواند صفر باشد")
.When(x => !string.IsNullOrEmpty(x.WeekNumber)); .When(x => x.WeekDefinitionId.HasValue);
} }
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) => public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
@@ -12,7 +12,8 @@ public class GetUserCommissionPayoutsResponseModel
public long UserId { get; set; } public long UserId { get; set; }
public string FirstName { get; set; } = string.Empty; public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty; public string LastName { get; set; } = string.Empty;
public string WeekNumber { get; set; } = string.Empty; public long WeekDefinitionId { get; set; }
public string WeekDisplayName { get; set; } = string.Empty;
public long WeeklyPoolId { get; set; } public long WeeklyPoolId { get; set; }
public long BalancesEarned { get; set; } public long BalancesEarned { get; set; }
public decimal ValuePerBalance { get; set; } public decimal ValuePerBalance { get; set; }
@@ -11,9 +11,10 @@ public record GetUserWeeklyBalancesQuery : IRequest<GetUserWeeklyBalancesRespons
public long? UserId { get; init; } public long? UserId { get; init; }
/// <summary> /// <summary>
/// شماره هفته (اختیاری) /// شناسه تعریف هفته (اختیاری) - روش ترجیحی
/// </summary> /// </summary>
public string? WeekNumber { get; init; } public long? WeekDefinitionId { get; init; }
/// <summary> /// <summary>
/// فقط موارد Expired نشده؟ /// فقط موارد Expired نشده؟
@@ -3,15 +3,20 @@ namespace CMSMicroservice.Application.CommissionCQ.Queries.GetUserWeeklyBalances
public class GetUserWeeklyBalancesQueryHandler : IRequestHandler<GetUserWeeklyBalancesQuery, GetUserWeeklyBalancesResponseDto> public class GetUserWeeklyBalancesQueryHandler : IRequestHandler<GetUserWeeklyBalancesQuery, GetUserWeeklyBalancesResponseDto>
{ {
private readonly IApplicationDbContext _context; private readonly IApplicationDbContext _context;
private readonly IWeekDefinitionRepository _weekDefinitionRepository;
public GetUserWeeklyBalancesQueryHandler(IApplicationDbContext context) public GetUserWeeklyBalancesQueryHandler(
IApplicationDbContext context,
IWeekDefinitionRepository weekDefinitionRepository)
{ {
_context = context; _context = context;
_weekDefinitionRepository = weekDefinitionRepository;
} }
public async Task<GetUserWeeklyBalancesResponseDto> Handle(GetUserWeeklyBalancesQuery request, CancellationToken cancellationToken) public async Task<GetUserWeeklyBalancesResponseDto> Handle(GetUserWeeklyBalancesQuery request, CancellationToken cancellationToken)
{ {
var query = _context.NetworkWeeklyBalances var query = _context.NetworkWeeklyBalances
.Include(x => x.WeekDefinition)
.AsNoTracking() .AsNoTracking()
.AsQueryable(); .AsQueryable();
@@ -21,19 +26,20 @@ public class GetUserWeeklyBalancesQueryHandler : IRequestHandler<GetUserWeeklyBa
query = query.Where(x => x.UserId == request.UserId.Value); query = query.Where(x => x.UserId == request.UserId.Value);
} }
if (!string.IsNullOrEmpty(request.WeekNumber)) // فیلتر بر اساس WeekDefinitionId (روش ترجیحی)
if (request.WeekDefinitionId.HasValue)
{ {
query = query.Where(x => x.WeekNumber == request.WeekNumber); query = query.Where(x => x.WeekDefinitionId == request.WeekDefinitionId.Value);
} }
if (request.OnlyActive.HasValue && request.OnlyActive.Value) if (request.OnlyActive.HasValue && request.OnlyActive.Value)
{ {
query = query.Where(x => !x.IsExpired); query = query.Where(x => !x.IsExpired);
} }
// نمی‌توانیم "-WeekNumber" استفاده کنیم چون WeekNumber یک string است // مرتب‌سازی بر اساس WeekDefinitionId (نزولی = جدیدترین اول)
// از Created برای مرتب‌سازی استفاده می‌کنیم (جدیدترین هفته‌ها اول) query = query.ApplyOrder(sortBy: request.SortBy ?? "-WeekDefinitionId");
query = query.ApplyOrder(sortBy: request.SortBy ?? "Created");
var meta = await query.GetMetaData(request.PaginationState, cancellationToken); var meta = await query.GetMetaData(request.PaginationState, cancellationToken);
@@ -43,7 +49,8 @@ public class GetUserWeeklyBalancesQueryHandler : IRequestHandler<GetUserWeeklyBa
{ {
Id = x.Id, Id = x.Id,
UserId = x.UserId, UserId = x.UserId,
WeekNumber = x.WeekNumber, WeekDefinitionId = x.WeekDefinitionId,
WeekDisplayName = x.WeekDefinition != null ? x.WeekDefinition.DisplayName : "",
LeftLegBalances = x.LeftLegBalances, LeftLegBalances = x.LeftLegBalances,
RightLegBalances = x.RightLegBalances, RightLegBalances = x.RightLegBalances,
TotalBalances = x.TotalBalances, TotalBalances = x.TotalBalances,
@@ -9,10 +9,10 @@ public class GetUserWeeklyBalancesQueryValidator : AbstractValidator<GetUserWeek
.WithMessage("شناسه کاربر معتبر نیست") .WithMessage("شناسه کاربر معتبر نیست")
.When(x => x.UserId.HasValue); .When(x => x.UserId.HasValue);
RuleFor(x => x.WeekNumber) RuleFor(x => x.WeekDefinitionId)
.Matches(@"^\d{4}-W\d{2}$") .GreaterThan(0)
.WithMessage("فرمت شماره هفته باید YYYY-Www باشد") .WithMessage("شماره هفته نمی‌تواند صفر باشد")
.When(x => !string.IsNullOrEmpty(x.WeekNumber)); .When(x => x.WeekDefinitionId.HasValue);
} }
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) => public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
@@ -10,7 +10,9 @@ public class GetUserWeeklyBalancesResponseModel
{ {
public long Id { get; set; } public long Id { get; set; }
public long UserId { get; set; } public long UserId { get; set; }
public string WeekNumber { get; set; } = string.Empty; public long WeekDefinitionId { get; set; }
public string WeekDisplayName { get; set; } = string.Empty;
public int LeftLegBalances { get; set; } public int LeftLegBalances { get; set; }
public int RightLegBalances { get; set; } public int RightLegBalances { get; set; }
public int TotalBalances { get; set; } public int TotalBalances { get; set; }
@@ -0,0 +1,45 @@
using CMSMicroservice.Application.Common.Models;
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWeekDefinitions;
/// <summary>
/// Query برای دریافت لیست هفته‌ها از کش (برای dropdown)
/// </summary>
public record GetWeekDefinitionsQuery : IRequest<GetWeekDefinitionsResponseDto>
{
//موقعیت صفحه بندی
public PaginationState? PaginationState { get; init; }
//مرتب سازی بر اساس
public string? SortBy { get; init; }
//فیلتر
public GetWeekDefinitionsFilter? Filter { get; init; }
}
/// <summary>
/// فیلترهای جستجوی هفته‌ها
/// </summary>
public class GetWeekDefinitionsFilter
{
//جستجوی متنی روی DisplayName (مثل "هفته یک")
public string? SearchText { get; set; }
//شماره ترتیب هفته
public int? WeekOrder { get; set; }
//شماره هفته میلادی (2025-W46)
public string? GregorianWeekNumber { get; set; }
//شماره هفته شمسی (1404-W35)
public string? PersianWeekNumber { get; set; }
//سال میلادی
public int? GregorianYear { get; set; }
//سال شمسی
public int? PersianYear { get; set; }
//فقط هفته‌های فعال
public bool? IsActive { get; set; }
}
@@ -0,0 +1,130 @@
using CMSMicroservice.Application.Common.Interfaces;
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWeekDefinitions;
/// <summary>
/// Handler برای دریافت لیست هفته‌ها از کش
/// </summary>
public class GetWeekDefinitionsQueryHandler : IRequestHandler<GetWeekDefinitionsQuery, GetWeekDefinitionsResponseDto>
{
private readonly IWeekDefinitionRepository _weekDefinitionRepository;
public GetWeekDefinitionsQueryHandler(IWeekDefinitionRepository weekDefinitionRepository)
{
_weekDefinitionRepository = weekDefinitionRepository;
}
public Task<GetWeekDefinitionsResponseDto> Handle(
GetWeekDefinitionsQuery request,
CancellationToken cancellationToken)
{
// گرفتن همه هفته‌ها از کش
var allWeeks = _weekDefinitionRepository.GetAllWeeks();
// اعمال فیلترها
var filteredWeeks = allWeeks.AsEnumerable();
if (request.Filter != null)
{
var filter = request.Filter;
// جستجوی متنی روی DisplayName
if (!string.IsNullOrWhiteSpace(filter.SearchText))
{
filteredWeeks = filteredWeeks.Where(w =>
w.DisplayName.Contains(filter.SearchText, StringComparison.OrdinalIgnoreCase));
}
// فیلتر بر اساس WeekOrder
if (filter.WeekOrder.HasValue)
{
filteredWeeks = filteredWeeks.Where(w => w.WeekOrder == filter.WeekOrder.Value);
}
// فیلتر بر اساس GregorianWeekNumber
if (!string.IsNullOrWhiteSpace(filter.GregorianWeekNumber))
{
filteredWeeks = filteredWeeks.Where(w =>
w.GregorianWeekNumber == filter.GregorianWeekNumber);
}
// فیلتر بر اساس PersianWeekNumber
if (!string.IsNullOrWhiteSpace(filter.PersianWeekNumber))
{
filteredWeeks = filteredWeeks.Where(w =>
w.PersianWeekNumber == filter.PersianWeekNumber);
}
// فیلتر بر اساس سال میلادی
if (filter.GregorianYear.HasValue)
{
filteredWeeks = filteredWeeks.Where(w => w.GregorianYear == filter.GregorianYear.Value);
}
// فیلتر بر اساس سال شمسی
if (filter.PersianYear.HasValue)
{
filteredWeeks = filteredWeeks.Where(w => w.PersianYear == filter.PersianYear.Value);
}
// فیلتر بر اساس فعال بودن
if (filter.IsActive.HasValue)
{
filteredWeeks = filteredWeeks.Where(w => w.IsActive == filter.IsActive.Value);
}
}
// مرتب‌سازی
filteredWeeks = request.SortBy?.ToLower() switch
{
"weekorder_desc" => filteredWeeks.OrderByDescending(w => w.WeekOrder),
"displayname" => filteredWeeks.OrderBy(w => w.DisplayName),
"displayname_desc" => filteredWeeks.OrderByDescending(w => w.DisplayName),
"startdate" => filteredWeeks.OrderBy(w => w.StartDate),
"startdate_desc" => filteredWeeks.OrderByDescending(w => w.StartDate),
_ => filteredWeeks.OrderBy(w => w.WeekOrder) // پیش‌فرض: weekorder
};
// تعداد کل قبل از صفحه‌بندی
var weeksList = filteredWeeks.ToList();
var totalCount = weeksList.Count;
// صفحه‌بندی
if (request.PaginationState != null)
{
var pageNumber = request.PaginationState.PageNumber > 0 ? request.PaginationState.PageNumber : 1;
var pageSize = request.PaginationState.PageSize > 0 ? request.PaginationState.PageSize : 20;
weeksList = weeksList
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToList();
}
// گرفتن هفته جاری برای مشخص کردن IsCurrentWeek
var currentWeek = _weekDefinitionRepository.GetCurrentWeek();
// تبدیل به DTO
var weekDtos = weeksList.Select(w => new WeekDefinitionItemDto
{
WeekOrder = w.WeekOrder,
DisplayName = w.DisplayName,
GregorianWeekNumber = w.GregorianWeekNumber,
PersianWeekNumber = w.PersianWeekNumber,
StartDate = w.StartDate,
EndDate = w.EndDate,
GregorianYear = w.GregorianYear,
PersianYear = w.PersianYear,
IsActive = w.IsActive,
IsCurrentWeek = currentWeek != null && w.WeekOrder == currentWeek.WeekOrder
}).ToList();
var response = new GetWeekDefinitionsResponseDto
{
Data = weekDtos,
TotalCount = totalCount
};
return Task.FromResult(response);
}
}
@@ -0,0 +1,49 @@
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWeekDefinitions;
/// <summary>
/// DTO برای پاسخ لیست هفته‌ها
/// </summary>
public class GetWeekDefinitionsResponseDto
{
//لیست هفته‌ها
public List<WeekDefinitionItemDto> Data { get; set; } = new();
//تعداد کل
public int TotalCount { get; set; }
}
/// <summary>
/// آیتم هفته برای dropdown
/// </summary>
public class WeekDefinitionItemDto
{
//شماره ترتیب هفته (1, 2, 3, ...)
public int WeekOrder { get; set; }
//نام نمایشی هفته (هفته یکم، هفته دوم، ...)
public string DisplayName { get; set; } = string.Empty;
//شماره هفته میلادی (2025-W46)
public string GregorianWeekNumber { get; set; } = string.Empty;
//شماره هفته شمسی (1404-W35)
public string PersianWeekNumber { get; set; } = string.Empty;
//تاریخ شروع هفته
public DateTime StartDate { get; set; }
//تاریخ پایان هفته
public DateTime EndDate { get; set; }
//سال میلادی
public int GregorianYear { get; set; }
//سال شمسی
public int PersianYear { get; set; }
//فعال بودن
public bool IsActive { get; set; }
//آیا هفته جاری است؟
public bool IsCurrentWeek { get; set; }
}
@@ -8,5 +8,5 @@ public record GetWeeklyCommissionPoolQuery : IRequest<WeeklyCommissionPoolDto?>
/// <summary> /// <summary>
/// شماره هفته /// شماره هفته
/// </summary> /// </summary>
public string WeekNumber { get; init; } = string.Empty; public long WeekDefinitionId { get; init; }
} }
@@ -3,21 +3,27 @@ namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWeeklyCommissionPo
public class GetWeeklyCommissionPoolQueryHandler : IRequestHandler<GetWeeklyCommissionPoolQuery, WeeklyCommissionPoolDto?> public class GetWeeklyCommissionPoolQueryHandler : IRequestHandler<GetWeeklyCommissionPoolQuery, WeeklyCommissionPoolDto?>
{ {
private readonly IApplicationDbContext _context; private readonly IApplicationDbContext _context;
private readonly IWeekDefinitionRepository _weekDefinitionRepository;
public GetWeeklyCommissionPoolQueryHandler(IApplicationDbContext context) public GetWeeklyCommissionPoolQueryHandler(
IApplicationDbContext context,
IWeekDefinitionRepository weekDefinitionRepository)
{ {
_context = context; _context = context;
_weekDefinitionRepository = weekDefinitionRepository;
} }
public async Task<WeeklyCommissionPoolDto?> Handle(GetWeeklyCommissionPoolQuery request, CancellationToken cancellationToken) public async Task<WeeklyCommissionPoolDto?> Handle(GetWeeklyCommissionPoolQuery request, CancellationToken cancellationToken)
{ {
var pool = await _context.WeeklyCommissionPools var pool = await _context.WeeklyCommissionPools
.Include(x => x.WeekDefinition)
.AsNoTracking() .AsNoTracking()
.Where(x => x.WeekNumber == request.WeekNumber) .Where(x => x.WeekDefinitionId == request.WeekDefinitionId)
.Select(x => new WeeklyCommissionPoolDto .Select(x => new WeeklyCommissionPoolDto
{ {
Id = x.Id, Id = x.Id,
WeekNumber = x.WeekNumber, WeekDefinitionId = x.WeekDefinitionId,
WeekDisplayName=x.WeekDefinition.DisplayName,
TotalPoolAmount = x.TotalPoolAmount, TotalPoolAmount = x.TotalPoolAmount,
TotalBalances = x.TotalBalances, TotalBalances = x.TotalBalances,
ValuePerBalance = x.ValuePerBalance, ValuePerBalance = x.ValuePerBalance,
@@ -27,6 +33,11 @@ public class GetWeeklyCommissionPoolQueryHandler : IRequestHandler<GetWeeklyComm
}) })
.FirstOrDefaultAsync(cancellationToken); .FirstOrDefaultAsync(cancellationToken);
return pool??new WeeklyCommissionPoolDto(); // if (pool != null)
// {
// pool.WeekDisplayName = _weekDefinitionRepository.GetDisplayNameByGregorianWeekNumber(pool.WeekNumber);
// }
return pool ?? new WeeklyCommissionPoolDto();
} }
} }
@@ -4,11 +4,9 @@ public class GetWeeklyCommissionPoolQueryValidator : AbstractValidator<GetWeekly
{ {
public GetWeeklyCommissionPoolQueryValidator() public GetWeeklyCommissionPoolQueryValidator()
{ {
RuleFor(x => x.WeekNumber) RuleFor(x => x.WeekDefinitionId)
.NotEmpty() .GreaterThan(0)
.WithMessage("شماره هفته نمی‌تواند خالی باشد") .WithMessage("شماره هفته نمی‌تواند صفر باشد");
.Matches(@"^\d{4}-W\d{2}$")
.WithMessage("فرمت شماره هفته باید YYYY-Www باشد");
} }
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) => public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
@@ -6,7 +6,8 @@ namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWeeklyCommissionPo
public class WeeklyCommissionPoolDto public class WeeklyCommissionPoolDto
{ {
public long Id { get; set; } public long Id { get; set; }
public string WeekNumber { get; set; } = string.Empty; public long WeekDefinitionId { get; set; }
public string WeekDisplayName { get; set; } = string.Empty;
public long TotalPoolAmount { get; set; } public long TotalPoolAmount { get; set; }
public long TotalBalances { get; set; } public long TotalBalances { get; set; }
public decimal ValuePerBalance { get; set; } public decimal ValuePerBalance { get; set; }
@@ -4,7 +4,7 @@ public class GetWithdrawalRequestsQuery : IRequest<GetWithdrawalRequestsResponse
{ {
public int? Status { get; set; } // CommissionPayoutStatus enum public int? Status { get; set; } // CommissionPayoutStatus enum
public long? UserId { get; set; } public long? UserId { get; set; }
public string? WeekNumber { get; set; } public long? WeekDefinitionId { get; init; }
public string? IbanNumber { get; set; } public string? IbanNumber { get; set; }
public PaginationState? PaginationState { get; set; } public PaginationState? PaginationState { get; set; }
public string? SortBy { get; set; } public string? SortBy { get; set; }
@@ -3,10 +3,14 @@ namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWithdrawalRequests
public class GetWithdrawalRequestsQueryHandler : IRequestHandler<GetWithdrawalRequestsQuery, GetWithdrawalRequestsResponseDto> public class GetWithdrawalRequestsQueryHandler : IRequestHandler<GetWithdrawalRequestsQuery, GetWithdrawalRequestsResponseDto>
{ {
private readonly IApplicationDbContext _context; private readonly IApplicationDbContext _context;
private readonly IWeekDefinitionRepository _weekDefinitionRepository;
public GetWithdrawalRequestsQueryHandler(IApplicationDbContext context) public GetWithdrawalRequestsQueryHandler(
IApplicationDbContext context,
IWeekDefinitionRepository weekDefinitionRepository)
{ {
_context = context; _context = context;
_weekDefinitionRepository = weekDefinitionRepository;
} }
public async Task<GetWithdrawalRequestsResponseDto> Handle(GetWithdrawalRequestsQuery request, CancellationToken cancellationToken) public async Task<GetWithdrawalRequestsResponseDto> Handle(GetWithdrawalRequestsQuery request, CancellationToken cancellationToken)
@@ -14,6 +18,7 @@ public class GetWithdrawalRequestsQueryHandler : IRequestHandler<GetWithdrawalRe
var query = _context.UserCommissionPayouts var query = _context.UserCommissionPayouts
.AsNoTracking() .AsNoTracking()
.Include(x => x.User) .Include(x => x.User)
.Include(x => x.WeekDefinition)
.Where(x => x.WithdrawalMethod != null) // Only requests with withdrawal method .Where(x => x.WithdrawalMethod != null) // Only requests with withdrawal method
.AsQueryable(); .AsQueryable();
@@ -28,9 +33,9 @@ public class GetWithdrawalRequestsQueryHandler : IRequestHandler<GetWithdrawalRe
query = query.Where(x => x.UserId == request.UserId.Value); query = query.Where(x => x.UserId == request.UserId.Value);
} }
if (!string.IsNullOrEmpty(request.WeekNumber)) if (request.WeekDefinitionId!= null && request.WeekDefinitionId >0)
{ {
query = query.Where(x => x.WeekNumber == request.WeekNumber); query = query.Where(x => x.WeekDefinitionId == request.WeekDefinitionId);
} }
if (!string.IsNullOrWhiteSpace(request.IbanNumber)) if (!string.IsNullOrWhiteSpace(request.IbanNumber))
@@ -51,7 +56,8 @@ public class GetWithdrawalRequestsQueryHandler : IRequestHandler<GetWithdrawalRe
Id = x.Id, Id = x.Id,
UserId = x.UserId, UserId = x.UserId,
UserName = x.User != null ? (x.User.FirstName + " " + x.User.LastName).Trim() : x.User?.Mobile ?? "N/A", UserName = x.User != null ? (x.User.FirstName + " " + x.User.LastName).Trim() : x.User?.Mobile ?? "N/A",
WeekNumber = x.WeekNumber, WeekDefinitionId = x.WeekDefinitionId,
WeekDisplayName =x.WeekDefinition.DisplayName,
Amount = x.TotalAmount, Amount = x.TotalAmount,
Status = (int)x.Status, Status = (int)x.Status,
WithdrawalMethod = x.WithdrawalMethod.HasValue ? (int)x.WithdrawalMethod.Value : 0, WithdrawalMethod = x.WithdrawalMethod.HasValue ? (int)x.WithdrawalMethod.Value : 0,
@@ -11,7 +11,8 @@ public class WithdrawalRequestModel
public long Id { get; set; } public long Id { get; set; }
public long UserId { get; set; } public long UserId { get; set; }
public string UserName { get; set; } = string.Empty; public string UserName { get; set; } = string.Empty;
public string WeekNumber { get; set; } = string.Empty; public long WeekDefinitionId { get; set; }
public string WeekDisplayName { get; set; } = string.Empty;
public long Amount { get; set; } public long Amount { get; set; }
public int Status { get; set; } // CommissionPayoutStatus enum public int Status { get; set; } // CommissionPayoutStatus enum
public int? WithdrawalMethod { get; set; } public int? WithdrawalMethod { get; set; }
@@ -4,7 +4,7 @@ namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerExecutionLog
public record GetWorkerExecutionLogsQuery : IRequest<GetWorkerExecutionLogsResponseDto> public record GetWorkerExecutionLogsQuery : IRequest<GetWorkerExecutionLogsResponseDto>
{ {
public string? WeekNumber { get; init; } public long? WeekDefinitionId { get; init; }
public string? ExecutionId { get; init; } public string? ExecutionId { get; init; }
public bool? SuccessOnly { get; init; } public bool? SuccessOnly { get; init; }
public bool? FailedOnly { get; init; } public bool? FailedOnly { get; init; }
@@ -6,10 +6,14 @@ namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerExecutionLog
public class GetWorkerExecutionLogsQueryHandler : IRequestHandler<GetWorkerExecutionLogsQuery, GetWorkerExecutionLogsResponseDto> public class GetWorkerExecutionLogsQueryHandler : IRequestHandler<GetWorkerExecutionLogsQuery, GetWorkerExecutionLogsResponseDto>
{ {
private readonly IApplicationDbContext _context; private readonly IApplicationDbContext _context;
private readonly IWeekDefinitionRepository _weekDefinitionRepository;
public GetWorkerExecutionLogsQueryHandler(IApplicationDbContext context) public GetWorkerExecutionLogsQueryHandler(
IApplicationDbContext context,
IWeekDefinitionRepository weekDefinitionRepository)
{ {
_context = context; _context = context;
_weekDefinitionRepository = weekDefinitionRepository;
} }
public async Task<GetWorkerExecutionLogsResponseDto> Handle( public async Task<GetWorkerExecutionLogsResponseDto> Handle(
@@ -17,12 +21,14 @@ public class GetWorkerExecutionLogsQueryHandler : IRequestHandler<GetWorkerExecu
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
// Query from database // Query from database
var query = _context.WorkerExecutionLogs.AsQueryable(); var query = _context.WorkerExecutionLogs
.Include(i=> i.WeekDefinition)
.AsQueryable();
// Apply filters // Apply filters
if (!string.IsNullOrEmpty(request.WeekNumber)) if (request.WeekDefinitionId!=null && request.WeekDefinitionId > 0)
{ {
query = query.Where(x => x.WeekNumber == request.WeekNumber); query = query.Where(x => x.WeekDefinitionId == request.WeekDefinitionId);
} }
if (request.SuccessOnly == true) if (request.SuccessOnly == true)
@@ -49,7 +55,8 @@ public class GetWorkerExecutionLogsQueryHandler : IRequestHandler<GetWorkerExecu
.Select(x => new WorkerExecutionLogModel .Select(x => new WorkerExecutionLogModel
{ {
ExecutionId = x.ExecutionId.ToString(), ExecutionId = x.ExecutionId.ToString(),
WeekNumber = x.WeekNumber, WeekDefinitionId = x.WeekDefinitionId,
WeekDisplayName = x.WeekDefinition.DisplayName,
Step = "Full", // We only have full execution now Step = "Full", // We only have full execution now
Success = x.Status == Domain.Entities.Commission.WorkerExecutionStatus.Success || Success = x.Status == Domain.Entities.Commission.WorkerExecutionStatus.Success ||
x.Status == Domain.Entities.Commission.WorkerExecutionStatus.SuccessWithWarnings, x.Status == Domain.Entities.Commission.WorkerExecutionStatus.SuccessWithWarnings,
@@ -62,6 +69,12 @@ public class GetWorkerExecutionLogsQueryHandler : IRequestHandler<GetWorkerExecu
}) })
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
// // Populate WeekDisplayName from cache
// foreach (var log in logs)
// {
// log.WeekDisplayName = _weekDefinitionRepository.GetDisplayNameByGregorianWeekNumber(log.WeekNumber);
// }
return new GetWorkerExecutionLogsResponseDto return new GetWorkerExecutionLogsResponseDto
{ {
MetaData = new MetaData MetaData = new MetaData
@@ -11,7 +11,8 @@ public class GetWorkerExecutionLogsResponseDto
public class WorkerExecutionLogModel public class WorkerExecutionLogModel
{ {
public string ExecutionId { get; set; } = string.Empty; public string ExecutionId { get; set; } = string.Empty;
public string WeekNumber { get; set; } = string.Empty; public long WeekDefinitionId { get; set; }
public string WeekDisplayName { get; set; } = string.Empty;
public string Step { get; set; } = string.Empty; // "Balances" | "Pool" | "Payouts" | "Full" public string Step { get; set; } = string.Empty; // "Balances" | "Pool" | "Payouts" | "Full"
public bool Success { get; set; } public bool Success { get; set; }
public string? ErrorMessage { get; set; } public string? ErrorMessage { get; set; }
@@ -5,10 +5,14 @@ namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerStatus;
public class GetWorkerStatusQueryHandler : IRequestHandler<GetWorkerStatusQuery, GetWorkerStatusResponseDto> public class GetWorkerStatusQueryHandler : IRequestHandler<GetWorkerStatusQuery, GetWorkerStatusResponseDto>
{ {
private readonly IApplicationDbContext _context; private readonly IApplicationDbContext _context;
private readonly IWeekDefinitionRepository _weekDefinitionRepository;
public GetWorkerStatusQueryHandler(IApplicationDbContext context) public GetWorkerStatusQueryHandler(
IApplicationDbContext context,
IWeekDefinitionRepository weekDefinitionRepository)
{ {
_context = context; _context = context;
_weekDefinitionRepository = weekDefinitionRepository;
} }
public async Task<GetWorkerStatusResponseDto> Handle( public async Task<GetWorkerStatusResponseDto> Handle(
@@ -20,12 +24,18 @@ public class GetWorkerStatusQueryHandler : IRequestHandler<GetWorkerStatusQuery,
await Task.CompletedTask; await Task.CompletedTask;
var currentWeek = _weekDefinitionRepository.GetCurrentWeek();
var currentWeekNumber = currentWeek != null
? $"{currentWeek.GregorianYear}-W{currentWeek.GregorianWeekNumber:D2}"
: null;
return new GetWorkerStatusResponseDto return new GetWorkerStatusResponseDto
{ {
IsRunning = false, IsRunning = false,
IsEnabled = true, IsEnabled = true,
CurrentExecutionId = null, CurrentExecutionId = null,
CurrentWeekNumber = null, CurrentWeekNumber = currentWeekNumber,
CurrentWeekDisplayName = currentWeek?.DisplayName,
CurrentStep = "Idle", CurrentStep = "Idle",
LastRunAt = DateTime.Now.AddHours(-24), LastRunAt = DateTime.Now.AddHours(-24),
NextScheduledRun = DateTime.Now.AddDays(7), NextScheduledRun = DateTime.Now.AddDays(7),
@@ -6,6 +6,7 @@ public class GetWorkerStatusResponseDto
public bool IsEnabled { get; set; } public bool IsEnabled { get; set; }
public string? CurrentExecutionId { get; set; } public string? CurrentExecutionId { get; set; }
public string? CurrentWeekNumber { get; set; } public string? CurrentWeekNumber { get; set; }
public string? CurrentWeekDisplayName { get; set; }
public string? CurrentStep { get; set; } // "Balances" | "Pool" | "Payouts" | "Idle" public string? CurrentStep { get; set; } // "Balances" | "Pool" | "Payouts" | "Idle"
public DateTime? LastRunAt { get; set; } public DateTime? LastRunAt { get; set; }
public DateTime? NextScheduledRun { get; set; } public DateTime? NextScheduledRun { get; set; }
@@ -40,6 +40,7 @@ public interface IApplicationDbContext
DbSet<UserClubFeature> UserClubFeatures { get; } DbSet<UserClubFeature> UserClubFeatures { get; }
DbSet<NetworkWeeklyBalance> NetworkWeeklyBalances { get; } DbSet<NetworkWeeklyBalance> NetworkWeeklyBalances { get; }
DbSet<NetworkMembershipHistory> NetworkMembershipHistories { get; } DbSet<NetworkMembershipHistory> NetworkMembershipHistories { get; }
DbSet<WeekDefinition> WeekDefinitions { get; }
DbSet<WeeklyCommissionPool> WeeklyCommissionPools { get; } DbSet<WeeklyCommissionPool> WeeklyCommissionPools { get; }
DbSet<UserCommissionPayout> UserCommissionPayouts { get; } DbSet<UserCommissionPayout> UserCommissionPayouts { get; }
DbSet<CommissionPayoutHistory> CommissionPayoutHistories { get; } DbSet<CommissionPayoutHistory> CommissionPayoutHistories { get; }
@@ -0,0 +1,163 @@
using CMSMicroservice.Domain.Entities;
namespace CMSMicroservice.Application.Common.Interfaces;
/// <summary>
/// اینترفیس ریپازیتوری هفته‌ها با کش در حافظه
/// این سرویس موقع استارت برنامه از دیتابیس لود می‌شود و در حافظه نگهداری می‌شود
/// </summary>
public interface IWeekDefinitionRepository
{
/// <summary>
/// گرفتن هفته جاری
/// </summary>
WeekDefinition? GetCurrentWeek();
/// <summary>
/// گرفتن هفته جاری (async برای سازگاری)
/// </summary>
Task<WeekDefinition?> GetCurrentWeekAsync(CancellationToken cancellationToken = default);
/// <summary>
/// گرفتن هفته بعد
/// </summary>
WeekDefinition? GetNextWeek();
/// <summary>
/// گرفتن هفته بعد از هفته مشخص
/// </summary>
WeekDefinition? GetNextWeek(WeekDefinition currentWeek);
/// <summary>
/// گرفتن هفته قبل
/// </summary>
WeekDefinition? GetPreviousWeek();
/// <summary>
/// گرفتن هفته قبل از هفته مشخص
/// </summary>
WeekDefinition? GetPreviousWeek(WeekDefinition currentWeek);
/// <summary>
/// گرفتن هفته بر اساس تاریخ میلادی
/// </summary>
WeekDefinition? GetWeekByDate(DateTime date);
/// <summary>
/// گرفتن هفته بر اساس شماره هفته میلادی (مثال: "2025-W46")
/// </summary>
WeekDefinition? GetWeekByGregorianWeekNumber(string gregorianWeekNumber);
/// <summary>
/// گرفتن هفته بر اساس شماره هفته شمسی (مثال: "1404-W35")
/// </summary>
WeekDefinition? GetWeekByPersianWeekNumber(string persianWeekNumber);
/// <summary>
/// گرفتن هفته بر اساس شماره ترتیبی
/// </summary>
WeekDefinition? GetWeekByOrder(int weekOrder);
/// <summary>
/// گرفتن لیست همه هفته‌ها
/// </summary>
IReadOnlyList<WeekDefinition> GetAllWeeks();
/// <summary>
/// گرفتن هفته‌های یک سال میلادی خاص
/// </summary>
IReadOnlyList<WeekDefinition> GetWeeksByGregorianYear(int year);
/// <summary>
/// گرفتن هفته‌های یک سال شمسی خاص
/// </summary>
IReadOnlyList<WeekDefinition> GetWeeksByPersianYear(int year);
/// <summary>
/// گرفتن شماره هفته میلادی جاری (مثال: "2025-W51")
/// </summary>
string GetCurrentGregorianWeekNumber();
/// <summary>
/// گرفتن شماره هفته شمسی جاری (مثال: "1404-W39")
/// </summary>
string GetCurrentPersianWeekNumber();
/// <summary>
/// گرفتن نام نمایشی هفته جاری (مثال: "هفته ششم")
/// </summary>
string GetCurrentWeekDisplayName();
/// <summary>
/// آیا تاریخ مشخص در هفته جاری است؟
/// </summary>
bool IsDateInCurrentWeek(DateTime date);
/// <summary>
/// محاسبه شماره هفته میلادی برای یک تاریخ (حتی اگر در کش نباشد)
/// </summary>
string CalculateGregorianWeekNumber(DateTime date);
/// <summary>
/// محاسبه شماره هفته شمسی برای یک تاریخ (حتی اگر در کش نباشد)
/// </summary>
string CalculatePersianWeekNumber(DateTime date);
/// <summary>
/// بارگذاری مجدد کش از دیتابیس
/// </summary>
Task ReloadCacheAsync(CancellationToken cancellationToken = default);
/// <summary>
/// گرفتن بازه تاریخی یک هفته بر اساس شماره هفته میلادی
/// </summary>
(DateTime startDate, DateTime endDate)? GetWeekDateRange(string gregorianWeekNumber);
(DateTime startDate, DateTime endDate)? GetWeekDateRange(long WeekDefinitionId);
/// <summary>
/// گرفتن شماره هفته قبل از هفته مشخص
/// </summary>
string? GetPreviousWeekNumber(string gregorianWeekNumber);
/// <summary>
/// گرفتن شماره هفته بعد از هفته مشخص
/// </summary>
string? GetNextWeekNumber(string gregorianWeekNumber);
/// <summary>
/// گرفتن DisplayName یک هفته بر اساس شماره هفته میلادی (مثل "هفته یکم")
/// </summary>
string GetDisplayNameByGregorianWeekNumber(string gregorianWeekNumber);
/// <summary>
/// جستجوی هفته‌ها بر اساس DisplayName (برای dropdown)
/// اگر فیلتر خالی باشد همه هفته‌ها برگردانده می‌شود
/// </summary>
IEnumerable<WeekDefinition> SearchWeeksByDisplayName(string? filter = null);
/// <summary>
/// گرفتن WeekDefinitionId از شماره هفته میلادی
/// </summary>
/// <param name="gregorianWeekNumber">شماره هفته میلادی (مثال: "2025-W46")</param>
/// <returns>شناسه WeekDefinition یا null اگر پیدا نشد</returns>
long? GetWeekDefinitionId(string gregorianWeekNumber);
WeekDefinition? GetWeekById(long weekDefinitionId);
/// <summary>
/// گرفتن شماره هفته میلادی از WeekDefinitionId
/// </summary>
/// <param name="weekDefinitionId">شناسه WeekDefinition</param>
/// <returns>شماره هفته میلادی یا null اگر پیدا نشد</returns>
string? GetGregorianWeekNumber(long weekDefinitionId);
/// <summary>
/// آیا کش لود شده؟
/// </summary>
bool IsCacheLoaded { get; }
/// <summary>
/// تعداد هفته‌های موجود در کش
/// </summary>
int CachedWeeksCount { get; }
}
@@ -3,10 +3,14 @@ namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkTree
public class GetNetworkTreeQueryHandler : IRequestHandler<GetNetworkTreeQuery, NetworkTreeDto?> public class GetNetworkTreeQueryHandler : IRequestHandler<GetNetworkTreeQuery, NetworkTreeDto?>
{ {
private readonly IApplicationDbContext _context; private readonly IApplicationDbContext _context;
private readonly IWeekDefinitionRepository _weekDefinitionRepository;
public GetNetworkTreeQueryHandler(IApplicationDbContext context) public GetNetworkTreeQueryHandler(
IApplicationDbContext context,
IWeekDefinitionRepository weekDefinitionRepository)
{ {
_context = context; _context = context;
_weekDefinitionRepository = weekDefinitionRepository;
} }
public async Task<NetworkTreeDto?> Handle(GetNetworkTreeQuery request, CancellationToken cancellationToken) public async Task<NetworkTreeDto?> Handle(GetNetworkTreeQuery request, CancellationToken cancellationToken)
@@ -39,11 +43,13 @@ public class GetNetworkTreeQueryHandler : IRequestHandler<GetNetworkTreeQuery, N
// محاسبه شماره هفته فعال‌سازی // محاسبه شماره هفته فعال‌سازی
string? activationWeekNumber = null; string? activationWeekNumber = null;
string? activationWeekDisplayName = null;
bool isActivatedInTargetWeek = false; bool isActivatedInTargetWeek = false;
if (user.ClubMembership?.ActivatedAt != null) if (user.ClubMembership?.ActivatedAt != null)
{ {
activationWeekNumber = CalculateWeekNumber(user.ClubMembership.ActivatedAt.Value); activationWeekNumber = CalculateWeekNumber(user.ClubMembership.ActivatedAt.Value);
activationWeekDisplayName = _weekDefinitionRepository.GetDisplayNameByGregorianWeekNumber(activationWeekNumber);
// بررسی آیا در هفته هدف فعال شده است // بررسی آیا در هفته هدف فعال شده است
if (!string.IsNullOrEmpty(request.ActivationWeekNumber)) if (!string.IsNullOrEmpty(request.ActivationWeekNumber))
@@ -63,6 +69,7 @@ public class GetNetworkTreeQueryHandler : IRequestHandler<GetNetworkTreeQuery, N
ClubActivatedAt = user.ClubMembership?.ActivatedAt, ClubActivatedAt = user.ClubMembership?.ActivatedAt,
IsClubActive = user.ClubMembership?.IsActive ?? false, IsClubActive = user.ClubMembership?.IsActive ?? false,
ActivationWeekNumber = activationWeekNumber, ActivationWeekNumber = activationWeekNumber,
ActivationWeekDisplayName = activationWeekDisplayName,
IsActivatedInTargetWeek = isActivatedInTargetWeek, IsActivatedInTargetWeek = isActivatedInTargetWeek,
UserCreated = user.Created UserCreated = user.Created
}; };
@@ -116,64 +123,19 @@ public class GetNetworkTreeQueryHandler : IRequestHandler<GetNetworkTreeQuery, N
/// <summary> /// <summary>
/// محاسبه شماره هفته از تاریخ /// محاسبه شماره هفته از تاریخ
/// </summary> /// </summary>
private static string CalculateWeekNumber(DateTime date) private string CalculateWeekNumber(DateTime date)
{ {
var year = date.Year; // First try to get from repository cache
var jan1 = new DateTime(year, 1, 1); var weekDef = _weekDefinitionRepository.GetWeekByDate(date);
var daysToFirstSaturday = (7 - (int)jan1.DayOfWeek + 7) % 7; if (weekDef != null)
if (jan1.DayOfWeek == DayOfWeek.Saturday) {
daysToFirstSaturday = 0; return $"{weekDef.GregorianYear}-W{weekDef.GregorianWeekNumber:D2}";
}
var firstSaturday = jan1.AddDays(daysToFirstSaturday); // Fallback: use repository's calculation method
var weekNum = date < firstSaturday ? 1 : ((date - firstSaturday).Days / 7) + 1; return _weekDefinitionRepository.CalculateGregorianWeekNumber(date);
return $"{year}-W{weekNum:D2}";
} }
/// <summary>
/// تبدیل شماره هفته (مثلاً 2025-W05) به تاریخ شروع و پایان هفته
/// </summary>
private static (DateTime? StartDate, DateTime? EndDate) ParseWeekNumber(string weekNumber)
{
try
{
// فرمت: YYYY-W##
var parts = weekNumber.Split('-');
if (parts.Length != 2 || !parts[1].StartsWith("W"))
return (null, null);
if (!int.TryParse(parts[0], out var year))
return (null, null);
if (!int.TryParse(parts[1].Substring(1), out var weekNum))
return (null, null);
// محاسبه اولین شنبه سال
var jan1 = new DateTime(year, 1, 1);
var daysToFirstSaturday = (7 - (int)jan1.DayOfWeek + 7) % 7;
if (jan1.DayOfWeek == DayOfWeek.Saturday)
daysToFirstSaturday = 0;
var firstSaturday = jan1.AddDays(daysToFirstSaturday);
// محاسبه تاریخ شروع هفته مورد نظر
DateTime startDate;
if (weekNum == 1)
{
startDate = firstSaturday;
}
else
{
startDate = firstSaturday.AddDays((weekNum - 1) * 7);
}
var endDate = startDate.AddDays(7);
return (startDate, endDate);
}
catch
{
return (null, null);
}
}
} }
@@ -27,6 +27,11 @@ public class NetworkTreeDto
/// </summary> /// </summary>
public string? ActivationWeekNumber { get; set; } public string? ActivationWeekNumber { get; set; }
/// <summary>
/// نام نمایشی هفته فعال‌سازی (مثل: هفته یکم)
/// </summary>
public string? ActivationWeekDisplayName { get; set; }
/// <summary> /// <summary>
/// آیا کاربر در هفته هدف فعال شده است؟ /// آیا کاربر در هفته هدف فعال شده است؟
/// </summary> /// </summary>
@@ -15,10 +15,16 @@ public class UserCommissionPayout : BaseAuditableEntity
/// </summary> /// </summary>
public virtual User User { get; set; } public virtual User User { get; set; }
/// <summary> /// <summary>
/// شماره هفته /// شناسه تعریف هفته (کلید خارجی به WeekDefinition)
/// </summary> /// </summary>
public string WeekNumber { get; set; } public long WeekDefinitionId { get; set; }
/// <summary>
/// WeekDefinition Navigation Property
/// </summary>
public virtual WeekDefinition WeekDefinition { get; set; }
/// <summary> /// <summary>
/// شناسه استخر هفتگی /// شناسه استخر هفتگی
@@ -5,10 +5,16 @@ namespace CMSMicroservice.Domain.Entities.Commission;
/// </summary> /// </summary>
public class WeeklyCommissionPool : BaseAuditableEntity public class WeeklyCommissionPool : BaseAuditableEntity
{ {
/// <summary> /// <summary>
/// شماره هفته (مثال: "2025-W48") /// شناسه تعریف هفته (کلید خارجی به WeekDefinition)
/// </summary> /// </summary>
public string WeekNumber { get; set; } public long WeekDefinitionId { get; set; }
/// <summary>
/// WeekDefinition Navigation Property
/// </summary>
public virtual WeekDefinition WeekDefinition { get; set; }
/// <summary> /// <summary>
/// مجموع مبلغ جمع‌شده در استخر (ریال) /// مجموع مبلغ جمع‌شده در استخر (ریال)
@@ -12,10 +12,17 @@ public class WorkerExecutionLog : BaseAuditableEntity
/// </summary> /// </summary>
public Guid ExecutionId { get; set; } public Guid ExecutionId { get; set; }
/// <summary> /// <summary>
/// شماره هفته (مثلاً 2025-W48) /// شناسه تعریف هفته (کلید خارجی به WeekDefinition)
/// </summary> /// </summary>
public string WeekNumber { get; set; } = string.Empty; public long WeekDefinitionId { get; set; }
/// <summary>
/// WeekDefinition Navigation Property
/// </summary>
public virtual WeekDefinition WeekDefinition { get; set; }
/// <summary> /// <summary>
/// زمان شروع اجرا /// زمان شروع اجرا
@@ -20,10 +20,16 @@ public class CommissionPayoutHistory : BaseAuditableEntity
/// </summary> /// </summary>
public long UserId { get; set; } public long UserId { get; set; }
/// <summary> /// <summary>
/// شماره هفته /// شناسه تعریف هفته (کلید خارجی به WeekDefinition)
/// </summary> /// </summary>
public string WeekNumber { get; set; } public long WeekDefinitionId { get; set; }
/// <summary>
/// WeekDefinition Navigation Property
/// </summary>
public virtual WeekDefinition WeekDefinition { get; set; }
/// <summary> /// <summary>
/// مبلغ قبل از تغییر /// مبلغ قبل از تغییر
@@ -16,9 +16,14 @@ public class NetworkWeeklyBalance : BaseAuditableEntity
public virtual User User { get; set; } public virtual User User { get; set; }
/// <summary> /// <summary>
/// شماره هفته (مثال: "2025-W48") /// شناسه تعریف هفته (کلید خارجی به WeekDefinition)
/// </summary> /// </summary>
public string WeekNumber { get; set; } public long WeekDefinitionId { get; set; }
/// <summary>
/// WeekDefinition Navigation Property
/// </summary>
public virtual WeekDefinition WeekDefinition { get; set; }
/// <summary> /// <summary>
/// تعداد اعضای جدید شاخه چپ در این هفته /// تعداد اعضای جدید شاخه چپ در این هفته
@@ -0,0 +1,67 @@
using CMSMicroservice.Domain.Entities.Commission;
using CMSMicroservice.Domain.Entities.History;
using CMSMicroservice.Domain.Entities.Network;
namespace CMSMicroservice.Domain.Entities;
/// <summary>
/// تعریف هفته‌ها برای نمایش در سیستم
/// این جدول شامل اطلاعات کامل هر هفته شامل شماره نمایشی، شماره میلادی، شماره شمسی و بازه تاریخی است
/// </summary>
public class WeekDefinition : BaseAuditableEntity
{
/// <summary>
/// شماره ترتیبی هفته (1، 2، 3، ...)
/// برای مرتب‌سازی و محاسبات استفاده می‌شود
/// </summary>
public int WeekOrder { get; set; }
/// <summary>
/// نام نمایشی هفته (مثال: "هفته یکم"، "هفته دوم"، "هفته سوم")
/// این متن برای نمایش به کاربر نهایی (ادمین و مشتری) استفاده می‌شود
/// </summary>
public string DisplayName { get; set; } = string.Empty;
/// <summary>
/// شماره هفته میلادی (مثال: "2025-W01", "2025-W02")
/// فرمت ISO 8601
/// </summary>
public string GregorianWeekNumber { get; set; } = string.Empty;
/// <summary>
/// شماره هفته شمسی (مثال: "1403-W01", "1403-W02")
/// </summary>
public string PersianWeekNumber { get; set; } = string.Empty;
/// <summary>
/// تاریخ شروع هفته (میلادی) - شنبه
/// </summary>
public DateTime StartDate { get; set; }
/// <summary>
/// تاریخ پایان هفته (میلادی) - جمعه
/// </summary>
public DateTime EndDate { get; set; }
/// <summary>
/// سال میلادی
/// </summary>
public int GregorianYear { get; set; }
/// <summary>
/// سال شمسی
/// </summary>
public int PersianYear { get; set; }
/// <summary>
/// آیا این هفته فعال است؟
/// </summary>
public bool IsActive { get; set; } = true;
// Navigation Properties
public virtual ICollection<NetworkWeeklyBalance>? NetworkWeeklyBalances { get; set; }
public virtual ICollection<WeeklyCommissionPool>? WeeklyCommissionPools { get; set; }
public virtual ICollection<UserCommissionPayout>? UserCommissionPayouts { get; set; }
public virtual ICollection<WorkerExecutionLog>? WorkerExecutionLogs { get; set; }
public virtual ICollection<CommissionPayoutHistory>? CommissionPayoutHistories { get; set; }
}
@@ -21,16 +21,19 @@ public class WeeklyCommissionJob
private readonly IMediator _mediator; private readonly IMediator _mediator;
private readonly ILogger<WeeklyCommissionJob> _logger; private readonly ILogger<WeeklyCommissionJob> _logger;
private readonly IApplicationDbContext _context; private readonly IApplicationDbContext _context;
private readonly IWeekDefinitionRepository _weekRepository;
private readonly ResiliencePipeline _retryPipeline; private readonly ResiliencePipeline _retryPipeline;
public WeeklyCommissionJob( public WeeklyCommissionJob(
IMediator mediator, IMediator mediator,
ILogger<WeeklyCommissionJob> logger, ILogger<WeeklyCommissionJob> logger,
IApplicationDbContext context) IApplicationDbContext context,
IWeekDefinitionRepository weekRepository)
{ {
_mediator = mediator; _mediator = mediator;
_logger = logger; _logger = logger;
_context = context; _context = context;
_weekRepository = weekRepository;
// Polly Retry: 3 attempts, exponential backoff (5min → 10min → 20min) // Polly Retry: 3 attempts, exponential backoff (5min → 10min → 20min)
_retryPipeline = new ResiliencePipelineBuilder() _retryPipeline = new ResiliencePipelineBuilder()
@@ -57,38 +60,42 @@ public class WeeklyCommissionJob
/// Execute weekly commission calculation with retry logic /// Execute weekly commission calculation with retry logic
/// Called by Hangfire scheduler or manually triggered /// Called by Hangfire scheduler or manually triggered
/// </summary> /// </summary>
/// <param name="weekNumber">Week number in YYYY-Www format (e.g., 2025-W48). If null, uses previous week.</param> /// <param name="weekDefinitionId">شناسه هفته. اگر null باشد، هفته قبلی محاسبه می‌شود</param>
/// <param name="cancellationToken">Cancellation token</param> /// <param name="cancellationToken">Cancellation token</param>
public async Task ExecuteAsync(string? weekNumber = null, CancellationToken cancellationToken = default) public async Task ExecuteAsync(long? weekDefinitionId = null, CancellationToken cancellationToken = default)
{ {
var executionId = Guid.NewGuid(); var executionId = Guid.NewGuid();
var startTime = DateTime.Now; var startTime = DateTime.Now;
// Use provided week number or calculate for PREVIOUS week (completed week) // Use provided WeekDefinitionId or calculate for PREVIOUS week (completed week)
string targetWeekNumber; long targetWeekDefinitionId;
if (!string.IsNullOrWhiteSpace(weekNumber)) if (weekDefinitionId.HasValue && weekDefinitionId.Value > 0)
{ {
targetWeekNumber = weekNumber; targetWeekDefinitionId = weekDefinitionId.Value;
_logger.LogInformation("📅 Using manually specified week: {WeekNumber}", targetWeekNumber); _logger.LogInformation("📅 Using manually specified WeekDefinitionId: {WeekDefinitionId}", targetWeekDefinitionId);
} }
else else
{ {
var previousWeek = DateTime.Now.AddDays(-7); var previousWeek = DateTime.Now.AddDays(-7);
targetWeekNumber = GetWeekNumber(previousWeek); var weekDef = _weekRepository.GetWeekByDate(previousWeek);
_logger.LogInformation("📅 Using previous week (auto-calculated): {WeekNumber}", targetWeekNumber); if (weekDef == null)
{
throw new InvalidOperationException($"هفته برای تاریخ {previousWeek:yyyy-MM-dd} تعریف نشده است");
}
targetWeekDefinitionId = weekDef.Id;
_logger.LogInformation("📅 Using previous week (auto-calculated): WeekDefinitionId={WeekDefinitionId}, WeekNumber={WeekNumber}",
targetWeekDefinitionId, weekDef.GregorianWeekNumber);
} }
var previousWeekNumber = targetWeekNumber;
_logger.LogInformation( _logger.LogInformation(
"🚀 [{ExecutionId}] Starting weekly commission calculation for {WeekNumber}", "🚀 [{ExecutionId}] Starting weekly commission calculation for WeekDefinitionId={WeekDefinitionId}",
executionId, previousWeekNumber); executionId, targetWeekDefinitionId);
// Create execution log entry // Create execution log entry
var log = new WorkerExecutionLog var log = new WorkerExecutionLog
{ {
ExecutionId = executionId, ExecutionId = executionId,
WeekNumber = previousWeekNumber, WeekDefinitionId = targetWeekDefinitionId,
StartedAt = startTime, StartedAt = startTime,
Status = WorkerExecutionStatus.Running Status = WorkerExecutionStatus.Running
}; };
@@ -100,7 +107,7 @@ public class WeeklyCommissionJob
// Execute with retry pipeline // Execute with retry pipeline
await _retryPipeline.ExecuteAsync(async ct => await _retryPipeline.ExecuteAsync(async ct =>
{ {
await ExecuteWeeklyCalculationAsync(executionId, previousWeekNumber, ct); await ExecuteWeeklyCalculationAsync(executionId, targetWeekDefinitionId, ct);
}, cancellationToken); }, cancellationToken);
// Update log on success // Update log on success
@@ -113,9 +120,9 @@ public class WeeklyCommissionJob
// Get counts from database // Get counts from database
var balancesCount = await _context.NetworkWeeklyBalances var balancesCount = await _context.NetworkWeeklyBalances
.CountAsync(x => x.WeekNumber == previousWeekNumber, cancellationToken); .CountAsync(x => x.WeekDefinitionId == targetWeekDefinitionId, cancellationToken);
var payoutsCount = await _context.UserCommissionPayouts var payoutsCount = await _context.UserCommissionPayouts
.CountAsync(x => x.WeekNumber == previousWeekNumber, cancellationToken); .CountAsync(x => x.WeekDefinitionId == targetWeekDefinitionId, cancellationToken);
log.ProcessedCount = balancesCount + payoutsCount; log.ProcessedCount = balancesCount + payoutsCount;
@@ -149,21 +156,28 @@ public class WeeklyCommissionJob
private async Task ExecuteWeeklyCalculationAsync( private async Task ExecuteWeeklyCalculationAsync(
Guid executionId, Guid executionId,
string weekNumber, long weekDefinitionId,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
// Check idempotency: Skip if already calculated // Check idempotency: Skip if already calculated
var existingPool = await _context.WeeklyCommissionPools var existingPool = await _context.WeeklyCommissionPools
.FirstOrDefaultAsync(x => x.WeekNumber == weekNumber, cancellationToken); .FirstOrDefaultAsync(x => x.WeekDefinitionId == weekDefinitionId, cancellationToken);
if (existingPool != null && existingPool.IsCalculated) if (existingPool != null && existingPool.IsCalculated)
{ {
_logger.LogWarning( _logger.LogWarning(
"⚠️ [{ExecutionId}] Week {WeekNumber} already calculated. Skipping.", "⚠️ [{ExecutionId}] WeekDefinitionId={WeekDefinitionId} already calculated. Skipping.",
executionId, weekNumber); executionId, weekDefinitionId);
return; return;
} }
// دریافت WeekNumber برای command ها (فعلاً هنوز از WeekNumber استفاده می‌کنند)
var GregorianWeekNumber = _weekRepository.GetGregorianWeekNumber(weekDefinitionId);
if (string.IsNullOrEmpty(GregorianWeekNumber))
{
throw new InvalidOperationException($"WeekDefinitionId={weekDefinitionId} یافت نشد");
}
using var transaction = new System.Transactions.TransactionScope( using var transaction = new System.Transactions.TransactionScope(
System.Transactions.TransactionScopeOption.Required, System.Transactions.TransactionScopeOption.Required,
new System.Transactions.TransactionOptions new System.Transactions.TransactionOptions
@@ -182,41 +196,14 @@ public class WeeklyCommissionJob
await _mediator.Send(new TriggerWeeklyCalculationCommand await _mediator.Send(new TriggerWeeklyCalculationCommand
{ {
WeekNumber = weekNumber, WeekDefinitionId = weekDefinitionId,
ForceRecalculate = false ForceRecalculate = false
}, cancellationToken); }, cancellationToken);
// await _mediator.Send(new CalculateWeeklyBalancesCommand
// {
// WeekNumber = weekNumber,
// ForceRecalculate = false
// }, cancellationToken);
//
// // Step 2: Calculate global commission pool
// _logger.LogInformation(
// "💰 [{ExecutionId}] Step 2/3: Calculating commission pool...",
// executionId);
//
// await _mediator.Send(new CalculateWeeklyCommissionPoolCommand
// {
// WeekNumber = weekNumber,
// ForceRecalculate = false
// }, cancellationToken);
//
// // Step 3: Distribute commissions to users
// _logger.LogInformation(
// "💸 [{ExecutionId}] Step 3/3: Processing user payouts...",
// executionId);
//
// await _mediator.Send(new ProcessUserPayoutsCommand
// {
// WeekNumber = weekNumber,
// ForceReprocess = false
// }, cancellationToken);
transaction.Complete(); transaction.Complete();
_logger.LogInformation( _logger.LogInformation(
"✅ [{ExecutionId}] All 2 steps completed successfully", "✅ [{ExecutionId}] All steps completed successfully",
executionId); executionId);
} }
catch (Exception ex) catch (Exception ex)
@@ -227,24 +214,4 @@ public class WeeklyCommissionJob
throw; throw;
} }
} }
/// <summary>
/// Get ISO 8601 week number (YYYY-Www format)
/// </summary>
private static string GetWeekNumber(DateTime date)
{
var calendar = System.Globalization.CultureInfo.InvariantCulture.Calendar;
var weekNumber = calendar.GetWeekOfYear(
date,
System.Globalization.CalendarWeekRule.FirstDay,
DayOfWeek.Saturday);
var year = date.Year;
if (weekNumber >= 52 && date.Month == 1)
year--;
else if (weekNumber == 1 && date.Month == 12)
year++;
return $"{year}-W{weekNumber:D2}";
}
} }
@@ -6,6 +6,7 @@ using CMSMicroservice.Infrastructure.BackgroundJobs;
using CMSMicroservice.Infrastructure.Services.Monitoring; using CMSMicroservice.Infrastructure.Services.Monitoring;
using CMSMicroservice.Infrastructure.Configuration; using CMSMicroservice.Infrastructure.Configuration;
using CMSMicroservice.Infrastructure.Services.Payment; using CMSMicroservice.Infrastructure.Services.Payment;
using CMSMicroservice.Infrastructure.Repositories;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
@@ -83,6 +84,10 @@ public static class ConfigureServices
services.AddScoped<IApplicationDbContext>(p => p.GetRequiredService<ApplicationDbContext>()); services.AddScoped<IApplicationDbContext>(p => p.GetRequiredService<ApplicationDbContext>());
// Week Definition Repository - کش در حافظه برای هفته‌ها (Singleton برای کش، با IServiceScopeFactory برای دسترسی به DbContext)
services.AddSingleton<WeekDefinitionRepository>();
services.AddSingleton<IWeekDefinitionRepository>(sp => sp.GetRequiredService<WeekDefinitionRepository>());
// Background Workers - Deprecated: Using Hangfire instead // Background Workers - Deprecated: Using Hangfire instead
// services.AddHostedService<WeeklyNetworkCommissionWorker>(); // services.AddHostedService<WeeklyNetworkCommissionWorker>();
services.AddScoped<WeeklyCommissionJob>(); // Hangfire Job (Scoped for DI) services.AddScoped<WeeklyCommissionJob>(); // Hangfire Job (Scoped for DI)
@@ -0,0 +1,255 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using CMSMicroservice.Domain.Entities;
using CMSMicroservice.Infrastructure.Persistence;
using System.Globalization;
using System.Collections.Generic;
namespace CMSMicroservice.Infrastructure.Data.Seeding;
/// <summary>
/// Seeder برای پر کردن جدول WeekDefinitions
/// هفته اول سیستم: 2025-W46 میلادی = 1404-W35 شمسی
/// شروع هفته شمسی: شنبه | شروع هفته میلادی ISO: دوشنبه
/// </summary>
public class WeekDefinitionSeeder
{
private readonly ApplicationDbContext _context;
private readonly ILogger<WeekDefinitionSeeder> _logger;
private readonly PersianCalendar _persianCalendar;
/// <summary>
/// تاریخ شروع هفته اول سیستم (شنبه 17 آبان 1404 = 8 نوامبر 2025)
/// این شنبه در هفته 46 میلادی (ISO) و هفته 35 شمسی قرار دارد
/// </summary>
private static readonly DateTime BaseWeekStartDate = new DateTime(2025, 11, 8); // شنبه 8 نوامبر 2025
public WeekDefinitionSeeder(
ApplicationDbContext context,
ILogger<WeekDefinitionSeeder> logger)
{
_context = context;
_logger = logger;
_persianCalendar = new PersianCalendar();
}
/// <summary>
/// Seed هفته‌ها از هفته مبدأ تا یک سال بعد
/// </summary>
public async Task SeedAsync(CancellationToken cancellationToken = default)
{
_logger.LogInformation("=== WeekDefinitionSeeder: Starting ===");
_logger.LogInformation("Base week starts at: {BaseDate:yyyy-MM-dd} (Saturday)", BaseWeekStartDate);
try
{
var existingCount = await _context.WeekDefinitions.CountAsync(cancellationToken);
if (existingCount > 0)
{
_logger.LogInformation("WeekDefinitions already has {Count} records. Checking for missing weeks...", existingCount);
}
// شروع از هفته مبدأ
var startDate = BaseWeekStartDate;
// پایان تا یک سال بعد از الان
var endDate = GetEndOfWeek(DateTime.Today.AddYears(1));
var currentDate = startDate;
var addedCount = 0;
while (currentDate <= endDate)
{
var weekStart = currentDate; // شنبه
var weekEnd = currentDate.AddDays(6); // جمعه
// محاسبه شماره ترتیبی هفته (از 1 شروع می‌شود)
var weekOrder = CalculateWeekOrder(weekStart);
// نام نمایشی: هفته یکم، هفته دوم، ...
var displayName = GetPersianOrdinalWeekName(weekOrder);
// شماره هفته میلادی ISO (بر اساس دوشنبه)
var gregorianWeekNumber = GetISOWeekNumber(weekStart);
// شماره هفته شمسی (بر اساس شنبه)
var persianWeekNumber = GetPersianWeekNumber(weekStart);
// چک کنیم که این هفته قبلاً ثبت نشده باشه
var exists = await _context.WeekDefinitions
.AnyAsync(w => w.StartDate == weekStart, cancellationToken);
if (!exists)
{
var weekDefinition = new WeekDefinition
{
WeekOrder = weekOrder,
DisplayName = displayName,
GregorianWeekNumber = gregorianWeekNumber,
PersianWeekNumber = persianWeekNumber,
StartDate = weekStart,
EndDate = weekEnd,
GregorianYear = weekStart.Year,
PersianYear = _persianCalendar.GetYear(weekStart),
IsActive = true
};
_context.WeekDefinitions.Add(weekDefinition);
addedCount++;
_logger.LogDebug("Added: {DisplayName} | {GregorianWeek} | {PersianWeek} | {Start:yyyy-MM-dd} - {End:yyyy-MM-dd}",
displayName, gregorianWeekNumber, persianWeekNumber, weekStart, weekEnd);
}
// رفتن به هفته بعد (7 روز)
currentDate = currentDate.AddDays(7);
}
if (addedCount > 0)
{
await _context.SaveChangesAsync(cancellationToken);
_logger.LogInformation("Successfully added {Count} week definitions", addedCount);
}
else
{
_logger.LogInformation("No new week definitions needed");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error seeding WeekDefinitions");
throw;
}
_logger.LogInformation("=== WeekDefinitionSeeder: Completed ===");
}
/// <summary>
/// محاسبه شماره ترتیبی هفته بر اساس فاصله از هفته مبدأ
/// هفته مبدأ = 1، هفته بعد = 2، ...
/// </summary>
private static int CalculateWeekOrder(DateTime weekStartDate)
{
var daysDiff = (weekStartDate - BaseWeekStartDate).Days;
var weeksDiff = daysDiff / 7;
return weeksDiff + 1;
}
/// <summary>
/// تبدیل عدد به نام ترتیبی فارسی
/// 1 -> هفته یکم، 2 -> هفته دوم، ...
/// </summary>
private static string GetPersianOrdinalWeekName(int weekOrder)
{
var ordinals = new Dictionary<int, string>
{
{ 1, "یکم" }, { 2, "دوم" }, { 3, "سوم" }, { 4, "چهارم" }, { 5, "پنجم" },
{ 6, "ششم" }, { 7, "هفتم" }, { 8, "هشتم" }, { 9, "نهم" }, { 10, "دهم" },
{ 11, "یازدهم" }, { 12, "دوازدهم" }, { 13, "سیزدهم" }, { 14, "چهاردهم" }, { 15, "پانزدهم" },
{ 16, "شانزدهم" }, { 17, "هفدهم" }, { 18, "هجدهم" }, { 19, "نوزدهم" }, { 20, "بیستم" },
{ 21, "بیست و یکم" }, { 22, "بیست و دوم" }, { 23, "بیست و سوم" }, { 24, "بیست و چهارم" }, { 25, "بیست و پنجم" },
{ 26, "بیست و ششم" }, { 27, "بیست و هفتم" }, { 28, "بیست و هشتم" }, { 29, "بیست و نهم" }, { 30, "سی‌ام" },
{ 31, "سی و یکم" }, { 32, "سی و دوم" }, { 33, "سی و سوم" }, { 34, "سی و چهارم" }, { 35, "سی و پنجم" },
{ 36, "سی و ششم" }, { 37, "سی و هفتم" }, { 38, "سی و هشتم" }, { 39, "سی و نهم" }, { 40, "چهلم" },
{ 41, "چهل و یکم" }, { 42, "چهل و دوم" }, { 43, "چهل و سوم" }, { 44, "چهل و چهارم" }, { 45, "چهل و پنجم" },
{ 46, "چهل و ششم" }, { 47, "چهل و هفتم" }, { 48, "چهل و هشتم" }, { 49, "چهل و نهم" }, { 50, "پنجاهم" },
{ 51, "پنجاه و یکم" }, { 52, "پنجاه و دوم" }, { 53, "پنجاه و سوم" }, { 54, "پنجاه و چهارم" }, { 55, "پنجاه و پنجم" },
{ 56, "پنجاه و ششم" }, { 57, "پنجاه و هفتم" }, { 58, "پنجاه و هشتم" }, { 59, "پنجاه و نهم" }, { 60, "شصتم" }
};
if (ordinals.TryGetValue(weekOrder, out var ordinal))
{
return $"هفته {ordinal}";
}
// برای اعداد بزرگتر از 60
return $"هفته {weekOrder}";
}
/// <summary>
/// پیدا کردن شنبه این هفته (شروع هفته شمسی)
/// </summary>
private static DateTime GetStartOfWeek(DateTime date)
{
var diff = (7 + (date.DayOfWeek - DayOfWeek.Saturday)) % 7;
return date.AddDays(-diff).Date;
}
/// <summary>
/// پیدا کردن جمعه این هفته (پایان هفته شمسی)
/// </summary>
private static DateTime GetEndOfWeek(DateTime date)
{
return GetStartOfWeek(date).AddDays(6);
}
/// <summary>
/// شماره هفته میلادی ISO 8601
/// هفته ISO از دوشنبه شروع می‌شود
/// برای تاریخ شنبه، باید دوشنبه همان هفته را پیدا کنیم (2 روز بعد)
/// </summary>
private static string GetISOWeekNumber(DateTime saturdayDate)
{
// شنبه + 2 روز = دوشنبه همان هفته ISO
var mondayOfWeek = saturdayDate.AddDays(2);
// محاسبه شماره هفته ISO
var cal = CultureInfo.InvariantCulture.Calendar;
var weekNumber = cal.GetWeekOfYear(mondayOfWeek, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
var year = mondayOfWeek.Year;
// تصحیح سال برای هفته‌های لبه‌ای
if (weekNumber == 1 && mondayOfWeek.Month == 12)
{
year++;
}
else if (weekNumber >= 52 && mondayOfWeek.Month == 1)
{
year--;
}
return $"{year}-W{weekNumber:D2}";
}
/// <summary>
/// شماره هفته شمسی
/// هفته شمسی از شنبه شروع می‌شود
/// </summary>
private string GetPersianWeekNumber(DateTime saturdayDate)
{
var persianYear = _persianCalendar.GetYear(saturdayDate);
var dayOfYear = _persianCalendar.GetDayOfYear(saturdayDate);
// اول فروردین این سال
var firstDayOfYear = _persianCalendar.ToDateTime(persianYear, 1, 1, 0, 0, 0, 0);
// پیدا کردن اولین شنبه سال
var daysUntilFirstSaturday = ((int)DayOfWeek.Saturday - (int)firstDayOfYear.DayOfWeek + 7) % 7;
// اگر اول فروردین شنبه باشه، daysUntilFirstSaturday = 0
// در غیر این صورت روزهای قبل از اولین شنبه، هفته 0 یا هفته آخر سال قبل هستند
int weekNumber;
if (daysUntilFirstSaturday == 0)
{
// اول فروردین شنبه است
weekNumber = ((dayOfYear - 1) / 7) + 1;
}
else
{
// روزهای قبل از اولین شنبه
if (dayOfYear <= daysUntilFirstSaturday)
{
// این روزها متعلق به آخرین هفته سال قبل هستند
// اما برای سادگی، هفته 1 در نظر می‌گیریم
weekNumber = 1;
}
else
{
weekNumber = ((dayOfYear - daysUntilFirstSaturday - 1) / 7) + 1;
}
}
return $"{persianYear}-W{weekNumber:D2}";
}
}
@@ -98,6 +98,9 @@ public class ApplicationDbContext : DbContext, IApplicationDbContext
public DbSet<NetworkWeeklyBalance> NetworkWeeklyBalances => Set<NetworkWeeklyBalance>(); public DbSet<NetworkWeeklyBalance> NetworkWeeklyBalances => Set<NetworkWeeklyBalance>();
public DbSet<NetworkMembershipHistory> NetworkMembershipHistories => Set<NetworkMembershipHistory>(); public DbSet<NetworkMembershipHistory> NetworkMembershipHistories => Set<NetworkMembershipHistory>();
// Week Definitions
public DbSet<WeekDefinition> WeekDefinitions => Set<WeekDefinition>();
// Commission // Commission
public DbSet<WeeklyCommissionPool> WeeklyCommissionPools => Set<WeeklyCommissionPool>(); public DbSet<WeeklyCommissionPool> WeeklyCommissionPools => Set<WeeklyCommissionPool>();
public DbSet<UserCommissionPayout> UserCommissionPayouts => Set<UserCommissionPayout>(); public DbSet<UserCommissionPayout> UserCommissionPayouts => Set<UserCommissionPayout>();
@@ -18,7 +18,7 @@ public class CommissionPayoutHistoryConfiguration : IEntityTypeConfiguration<Com
builder.Property(entity => entity.UserCommissionPayoutId).IsRequired(); builder.Property(entity => entity.UserCommissionPayoutId).IsRequired();
builder.Property(entity => entity.UserId).IsRequired(); builder.Property(entity => entity.UserId).IsRequired();
builder.Property(entity => entity.WeekNumber).IsRequired().HasMaxLength(20); builder.Property(entity => entity.WeekDefinitionId).IsRequired();
builder.Property(entity => entity.AmountBefore).IsRequired(); builder.Property(entity => entity.AmountBefore).IsRequired();
builder.Property(entity => entity.AmountAfter).IsRequired(); builder.Property(entity => entity.AmountAfter).IsRequired();
builder.Property(entity => entity.OldStatus).IsRequired(); builder.Property(entity => entity.OldStatus).IsRequired();
@@ -33,6 +33,13 @@ public class CommissionPayoutHistoryConfiguration : IEntityTypeConfiguration<Com
.HasForeignKey(entity => entity.UserCommissionPayoutId) .HasForeignKey(entity => entity.UserCommissionPayoutId)
.OnDelete(DeleteBehavior.Restrict); .OnDelete(DeleteBehavior.Restrict);
// رابطه با WeekDefinition (Required FK)
builder.HasOne(entity => entity.WeekDefinition)
.WithMany(wd => wd.CommissionPayoutHistories)
.HasForeignKey(entity => entity.WeekDefinitionId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
// Index برای UserId و Created // Index برای UserId و Created
builder.HasIndex(e => new { e.UserId, e.Created }) builder.HasIndex(e => new { e.UserId, e.Created })
.HasDatabaseName("IX_CommissionPayoutHistory_UserId_Created"); .HasDatabaseName("IX_CommissionPayoutHistory_UserId_Created");
@@ -41,9 +48,9 @@ public class CommissionPayoutHistoryConfiguration : IEntityTypeConfiguration<Com
builder.HasIndex(e => e.UserCommissionPayoutId) builder.HasIndex(e => e.UserCommissionPayoutId)
.HasDatabaseName("IX_CommissionPayoutHistory_PayoutId"); .HasDatabaseName("IX_CommissionPayoutHistory_PayoutId");
// Index برای WeekNumber // Index برای WeekDefinitionId
builder.HasIndex(e => e.WeekNumber) builder.HasIndex(e => e.WeekDefinitionId)
.HasDatabaseName("IX_CommissionPayoutHistory_WeekNumber"); .HasDatabaseName("IX_CommissionPayoutHistory_WeekDefinitionId");
// Index برای Action // Index برای Action
builder.HasIndex(e => e.Action) builder.HasIndex(e => e.Action)
@@ -17,7 +17,7 @@ public class NetworkWeeklyBalanceConfiguration : IEntityTypeConfiguration<Networ
builder.Property(entity => entity.Id).UseIdentityColumn(); builder.Property(entity => entity.Id).UseIdentityColumn();
builder.Property(entity => entity.UserId).IsRequired(); builder.Property(entity => entity.UserId).IsRequired();
builder.Property(entity => entity.WeekNumber).IsRequired().HasMaxLength(20); builder.Property(entity => entity.WeekDefinitionId).IsRequired();
builder.Property(entity => entity.LeftLegBalances).IsRequired(); builder.Property(entity => entity.LeftLegBalances).IsRequired();
builder.Property(entity => entity.RightLegBalances).IsRequired(); builder.Property(entity => entity.RightLegBalances).IsRequired();
builder.Property(entity => entity.TotalBalances).IsRequired(); builder.Property(entity => entity.TotalBalances).IsRequired();
@@ -31,14 +31,20 @@ public class NetworkWeeklyBalanceConfiguration : IEntityTypeConfiguration<Networ
.HasForeignKey(entity => entity.UserId) .HasForeignKey(entity => entity.UserId)
.OnDelete(DeleteBehavior.Restrict); .OnDelete(DeleteBehavior.Restrict);
// Composite Index برای UserId و WeekNumber // رابطه با WeekDefinition
builder.HasIndex(e => new { e.UserId, e.WeekNumber }) builder.HasOne(entity => entity.WeekDefinition)
.IsUnique() .WithMany(wd => wd.NetworkWeeklyBalances)
.HasDatabaseName("IX_NetworkWeeklyBalance_UserId_WeekNumber"); .HasForeignKey(entity => entity.WeekDefinitionId)
.OnDelete(DeleteBehavior.Restrict);
// Index برای WeekNumber // Composite Index برای UserId و WeekDefinitionId
builder.HasIndex(e => e.WeekNumber) builder.HasIndex(e => new { e.UserId, e.WeekDefinitionId })
.HasDatabaseName("IX_NetworkWeeklyBalance_WeekNumber"); .IsUnique()
.HasDatabaseName("IX_NetworkWeeklyBalance_UserId_WeekDefinitionId");
// Index برای WeekDefinitionId
builder.HasIndex(e => e.WeekDefinitionId)
.HasDatabaseName("IX_NetworkWeeklyBalance_WeekDefinitionId");
// Index برای IsExpired // Index برای IsExpired
builder.HasIndex(e => e.IsExpired) builder.HasIndex(e => e.IsExpired)
@@ -17,7 +17,7 @@ public class UserCommissionPayoutConfiguration : IEntityTypeConfiguration<UserCo
builder.Property(entity => entity.Id).UseIdentityColumn(); builder.Property(entity => entity.Id).UseIdentityColumn();
builder.Property(entity => entity.UserId).IsRequired(); builder.Property(entity => entity.UserId).IsRequired();
builder.Property(entity => entity.WeekNumber).IsRequired().HasMaxLength(20); builder.Property(entity => entity.WeekDefinitionId).IsRequired();
builder.Property(entity => entity.WeeklyPoolId).IsRequired(); builder.Property(entity => entity.WeeklyPoolId).IsRequired();
builder.Property(entity => entity.BalancesEarned).IsRequired(); builder.Property(entity => entity.BalancesEarned).IsRequired();
builder.Property(entity => entity.ValuePerBalance).IsRequired(); builder.Property(entity => entity.ValuePerBalance).IsRequired();
@@ -43,10 +43,17 @@ public class UserCommissionPayoutConfiguration : IEntityTypeConfiguration<UserCo
.HasForeignKey(entity => entity.WeeklyPoolId) .HasForeignKey(entity => entity.WeeklyPoolId)
.OnDelete(DeleteBehavior.Restrict); .OnDelete(DeleteBehavior.Restrict);
// Composite Index برای UserId و WeekNumber // رابطه با WeekDefinition (Required FK)
builder.HasIndex(e => new { e.UserId, e.WeekNumber }) builder.HasOne(entity => entity.WeekDefinition)
.WithMany(wd => wd.UserCommissionPayouts)
.HasForeignKey(entity => entity.WeekDefinitionId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
// Composite Index برای UserId و WeekDefinitionId
builder.HasIndex(e => new { e.UserId, e.WeekDefinitionId })
.IsUnique() .IsUnique()
.HasDatabaseName("IX_UserCommissionPayout_UserId_WeekNumber"); .HasDatabaseName("IX_UserCommissionPayout_UserId_WeekDefinitionId");
// Index برای WeeklyPoolId // Index برای WeeklyPoolId
builder.HasIndex(e => e.WeeklyPoolId) builder.HasIndex(e => e.WeeklyPoolId)
@@ -56,8 +63,5 @@ public class UserCommissionPayoutConfiguration : IEntityTypeConfiguration<UserCo
builder.HasIndex(e => e.Status) builder.HasIndex(e => e.Status)
.HasDatabaseName("IX_UserCommissionPayout_Status"); .HasDatabaseName("IX_UserCommissionPayout_Status");
// Index برای WeekNumber
builder.HasIndex(e => e.WeekNumber)
.HasDatabaseName("IX_UserCommissionPayout_WeekNumber");
} }
} }
@@ -0,0 +1,51 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace CMSMicroservice.Infrastructure.Persistence.Configurations;
/// <summary>
/// تنظیمات جدول تعریف هفته‌ها
/// </summary>
public class WeekDefinitionConfiguration : IEntityTypeConfiguration<WeekDefinition>
{
public void Configure(EntityTypeBuilder<WeekDefinition> builder)
{
builder.HasQueryFilter(p => !p.IsDeleted);
builder.Ignore(entity => entity.DomainEvents);
builder.HasKey(entity => entity.Id);
builder.Property(entity => entity.Id).UseIdentityColumn();
builder.Property(entity => entity.WeekOrder).IsRequired();
builder.Property(entity => entity.DisplayName).IsRequired().HasMaxLength(50);
builder.Property(entity => entity.GregorianWeekNumber).IsRequired().HasMaxLength(20);
builder.Property(entity => entity.PersianWeekNumber).IsRequired().HasMaxLength(20);
builder.Property(entity => entity.StartDate).IsRequired();
builder.Property(entity => entity.EndDate).IsRequired();
builder.Property(entity => entity.GregorianYear).IsRequired();
builder.Property(entity => entity.PersianYear).IsRequired();
builder.Property(entity => entity.IsActive).IsRequired().HasDefaultValue(true);
// Unique Index برای GregorianWeekNumber
builder.HasIndex(e => e.GregorianWeekNumber)
.IsUnique()
.HasDatabaseName("IX_WeekDefinition_GregorianWeekNumber");
// Index برای PersianWeekNumber
builder.HasIndex(e => e.PersianWeekNumber)
.HasDatabaseName("IX_WeekDefinition_PersianWeekNumber");
// Index برای StartDate
builder.HasIndex(e => e.StartDate)
.HasDatabaseName("IX_WeekDefinition_StartDate");
// Index برای سال‌ها
builder.HasIndex(e => e.GregorianYear)
.HasDatabaseName("IX_WeekDefinition_GregorianYear");
builder.HasIndex(e => e.PersianYear)
.HasDatabaseName("IX_WeekDefinition_PersianYear");
builder.ToTable("WeekDefinitions");
}
}
@@ -16,17 +16,24 @@ public class WeeklyCommissionPoolConfiguration : IEntityTypeConfiguration<Weekly
builder.HasKey(entity => entity.Id); builder.HasKey(entity => entity.Id);
builder.Property(entity => entity.Id).UseIdentityColumn(); builder.Property(entity => entity.Id).UseIdentityColumn();
builder.Property(entity => entity.WeekNumber).IsRequired().HasMaxLength(20); builder.Property(entity => entity.WeekDefinitionId).IsRequired();
builder.Property(entity => entity.TotalPoolAmount).IsRequired(); builder.Property(entity => entity.TotalPoolAmount).IsRequired();
builder.Property(entity => entity.TotalBalances).IsRequired(); builder.Property(entity => entity.TotalBalances).IsRequired();
builder.Property(entity => entity.ValuePerBalance).IsRequired(); builder.Property(entity => entity.ValuePerBalance).IsRequired();
builder.Property(entity => entity.IsCalculated).IsRequired(); builder.Property(entity => entity.IsCalculated).IsRequired();
builder.Property(entity => entity.CalculatedAt).IsRequired(false); builder.Property(entity => entity.CalculatedAt).IsRequired(false);
// Index یونیک برای WeekNumber // رابطه با WeekDefinition (Required FK)
builder.HasIndex(e => e.WeekNumber) builder.HasOne(entity => entity.WeekDefinition)
.WithMany(wd => wd.WeeklyCommissionPools)
.HasForeignKey(entity => entity.WeekDefinitionId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
// Index یونیک برای WeekDefinitionId
builder.HasIndex(e => e.WeekDefinitionId)
.IsUnique() .IsUnique()
.HasDatabaseName("IX_WeeklyCommissionPool_WeekNumber"); .HasDatabaseName("IX_WeeklyCommissionPool_WeekDefinitionId");
// Index برای IsCalculated // Index برای IsCalculated
builder.HasIndex(e => e.IsCalculated) builder.HasIndex(e => e.IsCalculated)
@@ -15,8 +15,7 @@ public class WorkerExecutionLogConfiguration : IEntityTypeConfiguration<WorkerEx
builder.Property(x => x.ExecutionId) builder.Property(x => x.ExecutionId)
.IsRequired(); .IsRequired();
builder.Property(x => x.WeekNumber) builder.Property(x => x.WeekDefinitionId)
.HasMaxLength(10)
.IsRequired(); .IsRequired();
builder.Property(x => x.StartedAt) builder.Property(x => x.StartedAt)
@@ -31,8 +30,15 @@ public class WorkerExecutionLogConfiguration : IEntityTypeConfiguration<WorkerEx
builder.Property(x => x.Details) builder.Property(x => x.Details)
.HasColumnType("nvarchar(max)"); .HasColumnType("nvarchar(max)");
// Index for querying by week // رابطه با WeekDefinition (Required FK)
builder.HasIndex(x => x.WeekNumber); builder.HasOne(x => x.WeekDefinition)
.WithMany(wd => wd.WorkerExecutionLogs)
.HasForeignKey(x => x.WeekDefinitionId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
// Index for querying by WeekDefinitionId
builder.HasIndex(x => x.WeekDefinitionId);
// Index for querying by execution time // Index for querying by execution time
builder.HasIndex(x => x.StartedAt); builder.HasIndex(x => x.StartedAt);
@@ -0,0 +1,81 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace CMSMicroservice.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class AddWeekDefinitionTable : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "WeekDefinitions",
schema: "CMS",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
WeekOrder = table.Column<int>(type: "int", nullable: false),
DisplayName = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
GregorianWeekNumber = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: false),
PersianWeekNumber = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: false),
StartDate = table.Column<DateTime>(type: "datetime2", nullable: false),
EndDate = table.Column<DateTime>(type: "datetime2", nullable: false),
GregorianYear = table.Column<int>(type: "int", nullable: false),
PersianYear = table.Column<int>(type: "int", nullable: false),
IsActive = table.Column<bool>(type: "bit", nullable: false, defaultValue: true),
Created = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatedBy = table.Column<string>(type: "nvarchar(max)", nullable: true),
LastModified = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifiedBy = table.Column<string>(type: "nvarchar(max)", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_WeekDefinitions", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_WeekDefinition_GregorianWeekNumber",
schema: "CMS",
table: "WeekDefinitions",
column: "GregorianWeekNumber",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_WeekDefinition_GregorianYear",
schema: "CMS",
table: "WeekDefinitions",
column: "GregorianYear");
migrationBuilder.CreateIndex(
name: "IX_WeekDefinition_PersianWeekNumber",
schema: "CMS",
table: "WeekDefinitions",
column: "PersianWeekNumber");
migrationBuilder.CreateIndex(
name: "IX_WeekDefinition_PersianYear",
schema: "CMS",
table: "WeekDefinitions",
column: "PersianYear");
migrationBuilder.CreateIndex(
name: "IX_WeekDefinition_StartDate",
schema: "CMS",
table: "WeekDefinitions",
column: "StartDate");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "WeekDefinitions",
schema: "CMS");
}
}
}
@@ -0,0 +1,296 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace CMSMicroservice.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class AddWeekDefinitionIdToCommissionTables : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<long>(
name: "WeekDefinitionId",
schema: "CMS",
table: "WorkerExecutionLogs",
type: "bigint",
nullable: true);
migrationBuilder.AlterColumn<string>(
name: "WeekNumber",
schema: "CMS",
table: "WeeklyCommissionPools",
type: "nvarchar(10)",
maxLength: 10,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(20)",
oldMaxLength: 20);
migrationBuilder.AddColumn<long>(
name: "WeekDefinitionId",
schema: "CMS",
table: "WeeklyCommissionPools",
type: "bigint",
nullable: true);
migrationBuilder.AlterColumn<string>(
name: "WeekNumber",
schema: "CMS",
table: "UserCommissionPayouts",
type: "nvarchar(10)",
maxLength: 10,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(20)",
oldMaxLength: 20);
migrationBuilder.AddColumn<long>(
name: "WeekDefinitionId",
schema: "CMS",
table: "UserCommissionPayouts",
type: "bigint",
nullable: true);
migrationBuilder.AlterColumn<string>(
name: "WeekNumber",
schema: "CMS",
table: "NetworkWeeklyBalances",
type: "nvarchar(10)",
maxLength: 10,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(20)",
oldMaxLength: 20);
migrationBuilder.AddColumn<long>(
name: "WeekDefinitionId",
schema: "CMS",
table: "NetworkWeeklyBalances",
type: "bigint",
nullable: true);
migrationBuilder.AlterColumn<string>(
name: "WeekNumber",
schema: "CMS",
table: "CommissionPayoutHistories",
type: "nvarchar(10)",
maxLength: 10,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(20)",
oldMaxLength: 20);
migrationBuilder.AddColumn<long>(
name: "WeekDefinitionId",
schema: "CMS",
table: "CommissionPayoutHistories",
type: "bigint",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_WorkerExecutionLogs_WeekDefinitionId",
schema: "CMS",
table: "WorkerExecutionLogs",
column: "WeekDefinitionId");
migrationBuilder.CreateIndex(
name: "IX_WeeklyCommissionPool_WeekDefinitionId",
schema: "CMS",
table: "WeeklyCommissionPools",
column: "WeekDefinitionId");
migrationBuilder.CreateIndex(
name: "IX_UserCommissionPayout_WeekDefinitionId",
schema: "CMS",
table: "UserCommissionPayouts",
column: "WeekDefinitionId");
migrationBuilder.CreateIndex(
name: "IX_NetworkWeeklyBalance_WeekDefinitionId",
schema: "CMS",
table: "NetworkWeeklyBalances",
column: "WeekDefinitionId");
migrationBuilder.CreateIndex(
name: "IX_CommissionPayoutHistory_WeekDefinitionId",
schema: "CMS",
table: "CommissionPayoutHistories",
column: "WeekDefinitionId");
migrationBuilder.AddForeignKey(
name: "FK_CommissionPayoutHistories_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "CommissionPayoutHistories",
column: "WeekDefinitionId",
principalSchema: "CMS",
principalTable: "WeekDefinitions",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_NetworkWeeklyBalances_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "NetworkWeeklyBalances",
column: "WeekDefinitionId",
principalSchema: "CMS",
principalTable: "WeekDefinitions",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_UserCommissionPayouts_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "UserCommissionPayouts",
column: "WeekDefinitionId",
principalSchema: "CMS",
principalTable: "WeekDefinitions",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_WeeklyCommissionPools_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "WeeklyCommissionPools",
column: "WeekDefinitionId",
principalSchema: "CMS",
principalTable: "WeekDefinitions",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_WorkerExecutionLogs_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "WorkerExecutionLogs",
column: "WeekDefinitionId",
principalSchema: "CMS",
principalTable: "WeekDefinitions",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_CommissionPayoutHistories_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "CommissionPayoutHistories");
migrationBuilder.DropForeignKey(
name: "FK_NetworkWeeklyBalances_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "NetworkWeeklyBalances");
migrationBuilder.DropForeignKey(
name: "FK_UserCommissionPayouts_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "UserCommissionPayouts");
migrationBuilder.DropForeignKey(
name: "FK_WeeklyCommissionPools_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "WeeklyCommissionPools");
migrationBuilder.DropForeignKey(
name: "FK_WorkerExecutionLogs_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "WorkerExecutionLogs");
migrationBuilder.DropIndex(
name: "IX_WorkerExecutionLogs_WeekDefinitionId",
schema: "CMS",
table: "WorkerExecutionLogs");
migrationBuilder.DropIndex(
name: "IX_WeeklyCommissionPool_WeekDefinitionId",
schema: "CMS",
table: "WeeklyCommissionPools");
migrationBuilder.DropIndex(
name: "IX_UserCommissionPayout_WeekDefinitionId",
schema: "CMS",
table: "UserCommissionPayouts");
migrationBuilder.DropIndex(
name: "IX_NetworkWeeklyBalance_WeekDefinitionId",
schema: "CMS",
table: "NetworkWeeklyBalances");
migrationBuilder.DropIndex(
name: "IX_CommissionPayoutHistory_WeekDefinitionId",
schema: "CMS",
table: "CommissionPayoutHistories");
migrationBuilder.DropColumn(
name: "WeekDefinitionId",
schema: "CMS",
table: "WorkerExecutionLogs");
migrationBuilder.DropColumn(
name: "WeekDefinitionId",
schema: "CMS",
table: "WeeklyCommissionPools");
migrationBuilder.DropColumn(
name: "WeekDefinitionId",
schema: "CMS",
table: "UserCommissionPayouts");
migrationBuilder.DropColumn(
name: "WeekDefinitionId",
schema: "CMS",
table: "NetworkWeeklyBalances");
migrationBuilder.DropColumn(
name: "WeekDefinitionId",
schema: "CMS",
table: "CommissionPayoutHistories");
migrationBuilder.AlterColumn<string>(
name: "WeekNumber",
schema: "CMS",
table: "WeeklyCommissionPools",
type: "nvarchar(20)",
maxLength: 20,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(10)",
oldMaxLength: 10);
migrationBuilder.AlterColumn<string>(
name: "WeekNumber",
schema: "CMS",
table: "UserCommissionPayouts",
type: "nvarchar(20)",
maxLength: 20,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(10)",
oldMaxLength: 10);
migrationBuilder.AlterColumn<string>(
name: "WeekNumber",
schema: "CMS",
table: "NetworkWeeklyBalances",
type: "nvarchar(20)",
maxLength: 20,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(10)",
oldMaxLength: 10);
migrationBuilder.AlterColumn<string>(
name: "WeekNumber",
schema: "CMS",
table: "CommissionPayoutHistories",
type: "nvarchar(20)",
maxLength: 20,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(10)",
oldMaxLength: 10);
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,477 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace CMSMicroservice.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class u16 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_CommissionPayoutHistories_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "CommissionPayoutHistories");
migrationBuilder.DropForeignKey(
name: "FK_NetworkWeeklyBalances_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "NetworkWeeklyBalances");
migrationBuilder.DropForeignKey(
name: "FK_UserCommissionPayouts_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "UserCommissionPayouts");
migrationBuilder.DropForeignKey(
name: "FK_WeeklyCommissionPools_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "WeeklyCommissionPools");
migrationBuilder.DropForeignKey(
name: "FK_WorkerExecutionLogs_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "WorkerExecutionLogs");
migrationBuilder.DropIndex(
name: "IX_WorkerExecutionLogs_WeekNumber",
schema: "CMS",
table: "WorkerExecutionLogs");
migrationBuilder.DropIndex(
name: "IX_WeeklyCommissionPool_WeekDefinitionId",
schema: "CMS",
table: "WeeklyCommissionPools");
migrationBuilder.DropIndex(
name: "IX_WeeklyCommissionPool_WeekNumber",
schema: "CMS",
table: "WeeklyCommissionPools");
migrationBuilder.DropIndex(
name: "IX_UserCommissionPayout_UserId_WeekNumber",
schema: "CMS",
table: "UserCommissionPayouts");
migrationBuilder.DropIndex(
name: "IX_UserCommissionPayout_WeekNumber",
schema: "CMS",
table: "UserCommissionPayouts");
migrationBuilder.DropIndex(
name: "IX_NetworkWeeklyBalance_UserId_WeekNumber",
schema: "CMS",
table: "NetworkWeeklyBalances");
migrationBuilder.DropIndex(
name: "IX_NetworkWeeklyBalance_WeekNumber",
schema: "CMS",
table: "NetworkWeeklyBalances");
migrationBuilder.DropIndex(
name: "IX_CommissionPayoutHistory_WeekNumber",
schema: "CMS",
table: "CommissionPayoutHistories");
migrationBuilder.DropColumn(
name: "WeekNumber",
schema: "CMS",
table: "WorkerExecutionLogs");
migrationBuilder.DropColumn(
name: "WeekNumber",
schema: "CMS",
table: "WeeklyCommissionPools");
migrationBuilder.DropColumn(
name: "WeekNumber",
schema: "CMS",
table: "UserCommissionPayouts");
migrationBuilder.DropColumn(
name: "WeekNumber",
schema: "CMS",
table: "NetworkWeeklyBalances");
migrationBuilder.DropColumn(
name: "WeekNumber",
schema: "CMS",
table: "CommissionPayoutHistories");
migrationBuilder.RenameIndex(
name: "IX_UserCommissionPayout_WeekDefinitionId",
schema: "CMS",
table: "UserCommissionPayouts",
newName: "IX_UserCommissionPayouts_WeekDefinitionId");
migrationBuilder.AlterColumn<long>(
name: "WeekDefinitionId",
schema: "CMS",
table: "WorkerExecutionLogs",
type: "bigint",
nullable: false,
defaultValue: 0L,
oldClrType: typeof(long),
oldType: "bigint",
oldNullable: true);
migrationBuilder.AlterColumn<long>(
name: "WeekDefinitionId",
schema: "CMS",
table: "WeeklyCommissionPools",
type: "bigint",
nullable: false,
defaultValue: 0L,
oldClrType: typeof(long),
oldType: "bigint",
oldNullable: true);
migrationBuilder.AlterColumn<long>(
name: "WeekDefinitionId",
schema: "CMS",
table: "UserCommissionPayouts",
type: "bigint",
nullable: false,
defaultValue: 0L,
oldClrType: typeof(long),
oldType: "bigint",
oldNullable: true);
migrationBuilder.AlterColumn<long>(
name: "WeekDefinitionId",
schema: "CMS",
table: "NetworkWeeklyBalances",
type: "bigint",
nullable: false,
defaultValue: 0L,
oldClrType: typeof(long),
oldType: "bigint",
oldNullable: true);
migrationBuilder.AlterColumn<long>(
name: "WeekDefinitionId",
schema: "CMS",
table: "CommissionPayoutHistories",
type: "bigint",
nullable: false,
defaultValue: 0L,
oldClrType: typeof(long),
oldType: "bigint",
oldNullable: true);
migrationBuilder.CreateIndex(
name: "IX_WeeklyCommissionPool_WeekDefinitionId",
schema: "CMS",
table: "WeeklyCommissionPools",
column: "WeekDefinitionId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_UserCommissionPayout_UserId_WeekDefinitionId",
schema: "CMS",
table: "UserCommissionPayouts",
columns: new[] { "UserId", "WeekDefinitionId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_NetworkWeeklyBalance_UserId_WeekDefinitionId",
schema: "CMS",
table: "NetworkWeeklyBalances",
columns: new[] { "UserId", "WeekDefinitionId" },
unique: true);
migrationBuilder.AddForeignKey(
name: "FK_CommissionPayoutHistories_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "CommissionPayoutHistories",
column: "WeekDefinitionId",
principalSchema: "CMS",
principalTable: "WeekDefinitions",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
migrationBuilder.AddForeignKey(
name: "FK_NetworkWeeklyBalances_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "NetworkWeeklyBalances",
column: "WeekDefinitionId",
principalSchema: "CMS",
principalTable: "WeekDefinitions",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
migrationBuilder.AddForeignKey(
name: "FK_UserCommissionPayouts_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "UserCommissionPayouts",
column: "WeekDefinitionId",
principalSchema: "CMS",
principalTable: "WeekDefinitions",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
migrationBuilder.AddForeignKey(
name: "FK_WeeklyCommissionPools_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "WeeklyCommissionPools",
column: "WeekDefinitionId",
principalSchema: "CMS",
principalTable: "WeekDefinitions",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
migrationBuilder.AddForeignKey(
name: "FK_WorkerExecutionLogs_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "WorkerExecutionLogs",
column: "WeekDefinitionId",
principalSchema: "CMS",
principalTable: "WeekDefinitions",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_CommissionPayoutHistories_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "CommissionPayoutHistories");
migrationBuilder.DropForeignKey(
name: "FK_NetworkWeeklyBalances_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "NetworkWeeklyBalances");
migrationBuilder.DropForeignKey(
name: "FK_UserCommissionPayouts_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "UserCommissionPayouts");
migrationBuilder.DropForeignKey(
name: "FK_WeeklyCommissionPools_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "WeeklyCommissionPools");
migrationBuilder.DropForeignKey(
name: "FK_WorkerExecutionLogs_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "WorkerExecutionLogs");
migrationBuilder.DropIndex(
name: "IX_WeeklyCommissionPool_WeekDefinitionId",
schema: "CMS",
table: "WeeklyCommissionPools");
migrationBuilder.DropIndex(
name: "IX_UserCommissionPayout_UserId_WeekDefinitionId",
schema: "CMS",
table: "UserCommissionPayouts");
migrationBuilder.DropIndex(
name: "IX_NetworkWeeklyBalance_UserId_WeekDefinitionId",
schema: "CMS",
table: "NetworkWeeklyBalances");
migrationBuilder.RenameIndex(
name: "IX_UserCommissionPayouts_WeekDefinitionId",
schema: "CMS",
table: "UserCommissionPayouts",
newName: "IX_UserCommissionPayout_WeekDefinitionId");
migrationBuilder.AlterColumn<long>(
name: "WeekDefinitionId",
schema: "CMS",
table: "WorkerExecutionLogs",
type: "bigint",
nullable: true,
oldClrType: typeof(long),
oldType: "bigint");
migrationBuilder.AddColumn<string>(
name: "WeekNumber",
schema: "CMS",
table: "WorkerExecutionLogs",
type: "nvarchar(10)",
maxLength: 10,
nullable: false,
defaultValue: "");
migrationBuilder.AlterColumn<long>(
name: "WeekDefinitionId",
schema: "CMS",
table: "WeeklyCommissionPools",
type: "bigint",
nullable: true,
oldClrType: typeof(long),
oldType: "bigint");
migrationBuilder.AddColumn<string>(
name: "WeekNumber",
schema: "CMS",
table: "WeeklyCommissionPools",
type: "nvarchar(10)",
maxLength: 10,
nullable: false,
defaultValue: "");
migrationBuilder.AlterColumn<long>(
name: "WeekDefinitionId",
schema: "CMS",
table: "UserCommissionPayouts",
type: "bigint",
nullable: true,
oldClrType: typeof(long),
oldType: "bigint");
migrationBuilder.AddColumn<string>(
name: "WeekNumber",
schema: "CMS",
table: "UserCommissionPayouts",
type: "nvarchar(10)",
maxLength: 10,
nullable: false,
defaultValue: "");
migrationBuilder.AlterColumn<long>(
name: "WeekDefinitionId",
schema: "CMS",
table: "NetworkWeeklyBalances",
type: "bigint",
nullable: true,
oldClrType: typeof(long),
oldType: "bigint");
migrationBuilder.AddColumn<string>(
name: "WeekNumber",
schema: "CMS",
table: "NetworkWeeklyBalances",
type: "nvarchar(10)",
maxLength: 10,
nullable: false,
defaultValue: "");
migrationBuilder.AlterColumn<long>(
name: "WeekDefinitionId",
schema: "CMS",
table: "CommissionPayoutHistories",
type: "bigint",
nullable: true,
oldClrType: typeof(long),
oldType: "bigint");
migrationBuilder.AddColumn<string>(
name: "WeekNumber",
schema: "CMS",
table: "CommissionPayoutHistories",
type: "nvarchar(10)",
maxLength: 10,
nullable: false,
defaultValue: "");
migrationBuilder.CreateIndex(
name: "IX_WorkerExecutionLogs_WeekNumber",
schema: "CMS",
table: "WorkerExecutionLogs",
column: "WeekNumber");
migrationBuilder.CreateIndex(
name: "IX_WeeklyCommissionPool_WeekDefinitionId",
schema: "CMS",
table: "WeeklyCommissionPools",
column: "WeekDefinitionId");
migrationBuilder.CreateIndex(
name: "IX_WeeklyCommissionPool_WeekNumber",
schema: "CMS",
table: "WeeklyCommissionPools",
column: "WeekNumber",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_UserCommissionPayout_UserId_WeekNumber",
schema: "CMS",
table: "UserCommissionPayouts",
columns: new[] { "UserId", "WeekNumber" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_UserCommissionPayout_WeekNumber",
schema: "CMS",
table: "UserCommissionPayouts",
column: "WeekNumber");
migrationBuilder.CreateIndex(
name: "IX_NetworkWeeklyBalance_UserId_WeekNumber",
schema: "CMS",
table: "NetworkWeeklyBalances",
columns: new[] { "UserId", "WeekNumber" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_NetworkWeeklyBalance_WeekNumber",
schema: "CMS",
table: "NetworkWeeklyBalances",
column: "WeekNumber");
migrationBuilder.CreateIndex(
name: "IX_CommissionPayoutHistory_WeekNumber",
schema: "CMS",
table: "CommissionPayoutHistories",
column: "WeekNumber");
migrationBuilder.AddForeignKey(
name: "FK_CommissionPayoutHistories_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "CommissionPayoutHistories",
column: "WeekDefinitionId",
principalSchema: "CMS",
principalTable: "WeekDefinitions",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_NetworkWeeklyBalances_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "NetworkWeeklyBalances",
column: "WeekDefinitionId",
principalSchema: "CMS",
principalTable: "WeekDefinitions",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_UserCommissionPayouts_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "UserCommissionPayouts",
column: "WeekDefinitionId",
principalSchema: "CMS",
principalTable: "WeekDefinitions",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_WeeklyCommissionPools_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "WeeklyCommissionPools",
column: "WeekDefinitionId",
principalSchema: "CMS",
principalTable: "WeekDefinitions",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_WorkerExecutionLogs_WeekDefinitions_WeekDefinitionId",
schema: "CMS",
table: "WorkerExecutionLogs",
column: "WeekDefinitionId",
principalSchema: "CMS",
principalTable: "WeekDefinitions",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace CMSMicroservice.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class u17 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}
@@ -301,10 +301,8 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.Property<long>("ValuePerBalance") b.Property<long>("ValuePerBalance")
.HasColumnType("bigint"); .HasColumnType("bigint");
b.Property<string>("WeekNumber") b.Property<long>("WeekDefinitionId")
.IsRequired() .HasColumnType("bigint");
.HasMaxLength(20)
.HasColumnType("nvarchar(20)");
b.Property<long>("WeeklyPoolId") b.Property<long>("WeeklyPoolId")
.HasColumnType("bigint"); .HasColumnType("bigint");
@@ -320,15 +318,14 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.HasIndex("Status") b.HasIndex("Status")
.HasDatabaseName("IX_UserCommissionPayout_Status"); .HasDatabaseName("IX_UserCommissionPayout_Status");
b.HasIndex("WeekNumber") b.HasIndex("WeekDefinitionId");
.HasDatabaseName("IX_UserCommissionPayout_WeekNumber");
b.HasIndex("WeeklyPoolId") b.HasIndex("WeeklyPoolId")
.HasDatabaseName("IX_UserCommissionPayout_WeeklyPoolId"); .HasDatabaseName("IX_UserCommissionPayout_WeeklyPoolId");
b.HasIndex("UserId", "WeekNumber") b.HasIndex("UserId", "WeekDefinitionId")
.IsUnique() .IsUnique()
.HasDatabaseName("IX_UserCommissionPayout_UserId_WeekNumber"); .HasDatabaseName("IX_UserCommissionPayout_UserId_WeekDefinitionId");
b.ToTable("UserCommissionPayouts", "CMS"); b.ToTable("UserCommissionPayouts", "CMS");
}); });
@@ -371,19 +368,17 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.Property<long>("ValuePerBalance") b.Property<long>("ValuePerBalance")
.HasColumnType("bigint"); .HasColumnType("bigint");
b.Property<string>("WeekNumber") b.Property<long>("WeekDefinitionId")
.IsRequired() .HasColumnType("bigint");
.HasMaxLength(20)
.HasColumnType("nvarchar(20)");
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("IsCalculated") b.HasIndex("IsCalculated")
.HasDatabaseName("IX_WeeklyCommissionPool_IsCalculated"); .HasDatabaseName("IX_WeeklyCommissionPool_IsCalculated");
b.HasIndex("WeekNumber") b.HasIndex("WeekDefinitionId")
.IsUnique() .IsUnique()
.HasDatabaseName("IX_WeeklyCommissionPool_WeekNumber"); .HasDatabaseName("IX_WeeklyCommissionPool_WeekDefinitionId");
b.ToTable("WeeklyCommissionPools", "CMS"); b.ToTable("WeeklyCommissionPools", "CMS");
}); });
@@ -442,10 +437,8 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.Property<int>("Status") b.Property<int>("Status")
.HasColumnType("int"); .HasColumnType("int");
b.Property<string>("WeekNumber") b.Property<long>("WeekDefinitionId")
.IsRequired() .HasColumnType("bigint");
.HasMaxLength(10)
.HasColumnType("nvarchar(10)");
b.HasKey("Id"); b.HasKey("Id");
@@ -453,7 +446,7 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.HasIndex("Status"); b.HasIndex("Status");
b.HasIndex("WeekNumber"); b.HasIndex("WeekDefinitionId");
b.ToTable("WorkerExecutionLogs", "CMS"); b.ToTable("WorkerExecutionLogs", "CMS");
}); });
@@ -1379,10 +1372,8 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.Property<long>("UserId") b.Property<long>("UserId")
.HasColumnType("bigint"); .HasColumnType("bigint");
b.Property<string>("WeekNumber") b.Property<long>("WeekDefinitionId")
.IsRequired() .HasColumnType("bigint");
.HasMaxLength(20)
.HasColumnType("nvarchar(20)");
b.HasKey("Id"); b.HasKey("Id");
@@ -1392,8 +1383,8 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.HasIndex("UserCommissionPayoutId") b.HasIndex("UserCommissionPayoutId")
.HasDatabaseName("IX_CommissionPayoutHistory_PayoutId"); .HasDatabaseName("IX_CommissionPayoutHistory_PayoutId");
b.HasIndex("WeekNumber") b.HasIndex("WeekDefinitionId")
.HasDatabaseName("IX_CommissionPayoutHistory_WeekNumber"); .HasDatabaseName("IX_CommissionPayoutHistory_WeekDefinitionId");
b.HasIndex("UserId", "Created") b.HasIndex("UserId", "Created")
.HasDatabaseName("IX_CommissionPayoutHistory_UserId_Created"); .HasDatabaseName("IX_CommissionPayoutHistory_UserId_Created");
@@ -1598,10 +1589,8 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.Property<long>("UserId") b.Property<long>("UserId")
.HasColumnType("bigint"); .HasColumnType("bigint");
b.Property<string>("WeekNumber") b.Property<long>("WeekDefinitionId")
.IsRequired() .HasColumnType("bigint");
.HasMaxLength(20)
.HasColumnType("nvarchar(20)");
b.Property<long>("WeeklyPoolContribution") b.Property<long>("WeeklyPoolContribution")
.HasColumnType("bigint"); .HasColumnType("bigint");
@@ -1611,12 +1600,12 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.HasIndex("IsExpired") b.HasIndex("IsExpired")
.HasDatabaseName("IX_NetworkWeeklyBalance_IsExpired"); .HasDatabaseName("IX_NetworkWeeklyBalance_IsExpired");
b.HasIndex("WeekNumber") b.HasIndex("WeekDefinitionId")
.HasDatabaseName("IX_NetworkWeeklyBalance_WeekNumber"); .HasDatabaseName("IX_NetworkWeeklyBalance_WeekDefinitionId");
b.HasIndex("UserId", "WeekNumber") b.HasIndex("UserId", "WeekDefinitionId")
.IsUnique() .IsUnique()
.HasDatabaseName("IX_NetworkWeeklyBalance_UserId_WeekNumber"); .HasDatabaseName("IX_NetworkWeeklyBalance_UserId_WeekDefinitionId");
b.ToTable("NetworkWeeklyBalances", "CMS"); b.ToTable("NetworkWeeklyBalances", "CMS");
}); });
@@ -2841,6 +2830,85 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.ToTable("UserWalletChangeLogs", "CMS"); b.ToTable("UserWalletChangeLogs", "CMS");
}); });
modelBuilder.Entity("CMSMicroservice.Domain.Entities.WeekDefinition", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
b.Property<DateTime>("Created")
.HasColumnType("datetime2");
b.Property<string>("CreatedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("DisplayName")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<DateTime>("EndDate")
.HasColumnType("datetime2");
b.Property<string>("GregorianWeekNumber")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("nvarchar(20)");
b.Property<int>("GregorianYear")
.HasColumnType("int");
b.Property<bool>("IsActive")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(true);
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<DateTime?>("LastModified")
.HasColumnType("datetime2");
b.Property<string>("LastModifiedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("PersianWeekNumber")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("nvarchar(20)");
b.Property<int>("PersianYear")
.HasColumnType("int");
b.Property<DateTime>("StartDate")
.HasColumnType("datetime2");
b.Property<int>("WeekOrder")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("GregorianWeekNumber")
.IsUnique()
.HasDatabaseName("IX_WeekDefinition_GregorianWeekNumber");
b.HasIndex("GregorianYear")
.HasDatabaseName("IX_WeekDefinition_GregorianYear");
b.HasIndex("PersianWeekNumber")
.HasDatabaseName("IX_WeekDefinition_PersianWeekNumber");
b.HasIndex("PersianYear")
.HasDatabaseName("IX_WeekDefinition_PersianYear");
b.HasIndex("StartDate")
.HasDatabaseName("IX_WeekDefinition_StartDate");
b.ToTable("WeekDefinitions", "CMS");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Category", b => modelBuilder.Entity("CMSMicroservice.Domain.Entities.Category", b =>
{ {
b.HasOne("CMSMicroservice.Domain.Entities.Category", "Parent") b.HasOne("CMSMicroservice.Domain.Entities.Category", "Parent")
@@ -2896,6 +2964,12 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
.OnDelete(DeleteBehavior.Restrict) .OnDelete(DeleteBehavior.Restrict)
.IsRequired(); .IsRequired();
b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition")
.WithMany("UserCommissionPayouts")
.HasForeignKey("WeekDefinitionId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", "WeeklyPool") b.HasOne("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", "WeeklyPool")
.WithMany("UserCommissionPayouts") .WithMany("UserCommissionPayouts")
.HasForeignKey("WeeklyPoolId") .HasForeignKey("WeeklyPoolId")
@@ -2904,9 +2978,33 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.Navigation("User"); b.Navigation("User");
b.Navigation("WeekDefinition");
b.Navigation("WeeklyPool"); b.Navigation("WeeklyPool");
}); });
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b =>
{
b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition")
.WithMany("WeeklyCommissionPools")
.HasForeignKey("WeekDefinitionId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("WeekDefinition");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WorkerExecutionLog", b =>
{
b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition")
.WithMany("WorkerExecutionLogs")
.HasForeignKey("WeekDefinitionId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("WeekDefinition");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.DayaLoanContract", b => modelBuilder.Entity("CMSMicroservice.Domain.Entities.DayaLoanContract", b =>
{ {
b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction")
@@ -3077,7 +3175,15 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
.OnDelete(DeleteBehavior.Restrict) .OnDelete(DeleteBehavior.Restrict)
.IsRequired(); .IsRequired();
b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition")
.WithMany("CommissionPayoutHistories")
.HasForeignKey("WeekDefinitionId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("UserCommissionPayout"); b.Navigation("UserCommissionPayout");
b.Navigation("WeekDefinition");
}); });
modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.SystemConfigurationHistory", b => modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.SystemConfigurationHistory", b =>
@@ -3099,7 +3205,15 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
.OnDelete(DeleteBehavior.Restrict) .OnDelete(DeleteBehavior.Restrict)
.IsRequired(); .IsRequired();
b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition")
.WithMany("NetworkWeeklyBalances")
.HasForeignKey("WeekDefinitionId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("User"); b.Navigation("User");
b.Navigation("WeekDefinition");
}); });
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Order.OrderVAT", b => modelBuilder.Entity("CMSMicroservice.Domain.Entities.Order.OrderVAT", b =>
@@ -3511,6 +3625,19 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
{ {
b.Navigation("UserWalletChangeLogs"); b.Navigation("UserWalletChangeLogs");
}); });
modelBuilder.Entity("CMSMicroservice.Domain.Entities.WeekDefinition", b =>
{
b.Navigation("CommissionPayoutHistories");
b.Navigation("NetworkWeeklyBalances");
b.Navigation("UserCommissionPayouts");
b.Navigation("WeeklyCommissionPools");
b.Navigation("WorkerExecutionLogs");
});
#pragma warning restore 612, 618 #pragma warning restore 612, 618
} }
} }
@@ -0,0 +1,359 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities;
using CMSMicroservice.Infrastructure.Persistence;
namespace CMSMicroservice.Infrastructure.Repositories;
/// <summary>
/// ریپازیتوری هفته‌ها با کش در حافظه
/// موقع استارت برنامه از دیتابیس لود می‌شود و در حافظه نگهداری می‌شود
/// Thread-safe است و می‌توان از آن در محیط‌های multi-threaded استفاده کرد
/// Singleton است و از IServiceScopeFactory برای دسترسی به DbContext استفاده می‌کند
/// </summary>
public class WeekDefinitionRepository : IWeekDefinitionRepository
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILogger<WeekDefinitionRepository> _logger;
private readonly PersianCalendar _persianCalendar;
// کش در حافظه
private readonly ConcurrentDictionary<string, WeekDefinition> _cacheByGregorianWeek = new();
private readonly ConcurrentDictionary<string, WeekDefinition> _cacheByPersianWeek = new();
private readonly ConcurrentDictionary<int, WeekDefinition> _cacheByOrder = new();
private List<WeekDefinition> _allWeeks = new();
private readonly object _cacheLock = new();
private bool _isCacheLoaded;
public WeekDefinitionRepository(
IServiceScopeFactory scopeFactory,
ILogger<WeekDefinitionRepository> logger)
{
_scopeFactory = scopeFactory;
_logger = logger;
_persianCalendar = new PersianCalendar();
}
public bool IsCacheLoaded => _isCacheLoaded;
public int CachedWeeksCount => _allWeeks.Count;
#region Load Cache
/// <summary>
/// بارگذاری اولیه کش از دیتابیس
/// این متد باید موقع استارت برنامه فراخوانی شود
/// </summary>
public async Task ReloadCacheAsync(CancellationToken cancellationToken = default)
{
_logger.LogInformation("Loading WeekDefinitions into memory cache...");
try
{
using var scope = _scopeFactory.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
var weeks = await context.WeekDefinitions
.AsNoTracking()
.Where(w => w.IsActive)
.OrderBy(w => w.WeekOrder)
.ToListAsync(cancellationToken);
lock (_cacheLock)
{
_cacheByGregorianWeek.Clear();
_cacheByPersianWeek.Clear();
_cacheByOrder.Clear();
_allWeeks = weeks;
foreach (var week in weeks)
{
_cacheByGregorianWeek.TryAdd(week.GregorianWeekNumber, week);
_cacheByPersianWeek.TryAdd(week.PersianWeekNumber, week);
_cacheByOrder.TryAdd(week.WeekOrder, week);
}
_isCacheLoaded = true;
}
_logger.LogInformation("Successfully loaded {Count} week definitions into cache", weeks.Count);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error loading WeekDefinitions into cache");
throw;
}
}
#endregion
#region Current Week Methods
public WeekDefinition? GetCurrentWeek()
{
EnsureCacheLoaded();
return GetWeekByDate(DateTime.Today);
}
public Task<WeekDefinition?> GetCurrentWeekAsync(CancellationToken cancellationToken = default)
{
return Task.FromResult(GetCurrentWeek());
}
public string GetCurrentGregorianWeekNumber()
{
var currentWeek = GetCurrentWeek();
return currentWeek?.GregorianWeekNumber ?? CalculateGregorianWeekNumber(DateTime.Today);
}
public string GetCurrentPersianWeekNumber()
{
var currentWeek = GetCurrentWeek();
return currentWeek?.PersianWeekNumber ?? CalculatePersianWeekNumber(DateTime.Today);
}
public string GetCurrentWeekDisplayName()
{
var currentWeek = GetCurrentWeek();
return currentWeek?.DisplayName ?? "نامشخص";
}
public bool IsDateInCurrentWeek(DateTime date)
{
var currentWeek = GetCurrentWeek();
if (currentWeek == null) return false;
return date.Date >= currentWeek.StartDate.Date && date.Date <= currentWeek.EndDate.Date;
}
#endregion
#region Navigation Methods
public WeekDefinition? GetNextWeek()
{
var currentWeek = GetCurrentWeek();
if (currentWeek == null) return null;
return GetNextWeek(currentWeek);
}
public WeekDefinition? GetNextWeek(WeekDefinition currentWeek)
{
EnsureCacheLoaded();
return _cacheByOrder.TryGetValue(currentWeek.WeekOrder + 1, out var nextWeek) ? nextWeek : null;
}
public WeekDefinition? GetPreviousWeek()
{
var currentWeek = GetCurrentWeek();
if (currentWeek == null) return null;
return GetPreviousWeek(currentWeek);
}
public WeekDefinition? GetPreviousWeek(WeekDefinition currentWeek)
{
EnsureCacheLoaded();
if (currentWeek.WeekOrder <= 1) return null;
return _cacheByOrder.TryGetValue(currentWeek.WeekOrder - 1, out var prevWeek) ? prevWeek : null;
}
#endregion
#region Lookup Methods
public WeekDefinition? GetWeekByDate(DateTime date)
{
EnsureCacheLoaded();
// شنبه اول هفته (تقویم شمسی)
var saturday = GetStartOfWeek(date);
return _allWeeks.FirstOrDefault(w =>
w.StartDate.Date == saturday.Date);
}
public WeekDefinition? GetWeekByGregorianWeekNumber(string gregorianWeekNumber)
{
EnsureCacheLoaded();
return _cacheByGregorianWeek.TryGetValue(gregorianWeekNumber, out var week) ? week : null;
}
public WeekDefinition? GetWeekByPersianWeekNumber(string persianWeekNumber)
{
EnsureCacheLoaded();
return _cacheByPersianWeek.TryGetValue(persianWeekNumber, out var week) ? week : null;
}
public WeekDefinition? GetWeekByOrder(int weekOrder)
{
EnsureCacheLoaded();
return _cacheByOrder.TryGetValue(weekOrder, out var week) ? week : null;
}
public IReadOnlyList<WeekDefinition> GetAllWeeks()
{
EnsureCacheLoaded();
return _allWeeks.AsReadOnly();
}
public IReadOnlyList<WeekDefinition> GetWeeksByGregorianYear(int year)
{
EnsureCacheLoaded();
return _allWeeks.Where(w => w.GregorianYear == year).ToList().AsReadOnly();
}
public IReadOnlyList<WeekDefinition> GetWeeksByPersianYear(int year)
{
EnsureCacheLoaded();
return _allWeeks.Where(w => w.PersianYear == year).ToList().AsReadOnly();
}
public (DateTime startDate, DateTime endDate)? GetWeekDateRange(string gregorianWeekNumber)
{
EnsureCacheLoaded();
var week = GetWeekByGregorianWeekNumber(gregorianWeekNumber);
if (week == null) return null;
return (week.StartDate, week.EndDate.AddHours(23).AddMinutes(59).AddSeconds(59));
}
public (DateTime startDate, DateTime endDate)? GetWeekDateRange(long WeekDefinitionId)
{
EnsureCacheLoaded();
var week = GetWeekById(WeekDefinitionId);
if (week == null) return null;
return (week.StartDate, week.EndDate.AddHours(23).AddMinutes(59).AddSeconds(59));
}
public string? GetPreviousWeekNumber(string gregorianWeekNumber)
{
EnsureCacheLoaded();
var currentWeek = GetWeekByGregorianWeekNumber(gregorianWeekNumber);
if (currentWeek == null) return null;
var previousWeek = GetWeekByOrder(currentWeek.WeekOrder - 1);
return previousWeek?.GregorianWeekNumber;
}
public string? GetNextWeekNumber(string gregorianWeekNumber)
{
EnsureCacheLoaded();
var currentWeek = GetWeekByGregorianWeekNumber(gregorianWeekNumber);
if (currentWeek == null) return null;
var nextWeek = GetWeekByOrder(currentWeek.WeekOrder + 1);
return nextWeek?.GregorianWeekNumber;
}
public string GetDisplayNameByGregorianWeekNumber(string gregorianWeekNumber)
{
EnsureCacheLoaded();
var week = GetWeekByGregorianWeekNumber(gregorianWeekNumber);
return week?.DisplayName ?? gregorianWeekNumber; // اگر پیدا نشد، خود شماره میلادی رو برگردون
}
public IEnumerable<WeekDefinition> SearchWeeksByDisplayName(string? filter = null)
{
EnsureCacheLoaded();
var weeks = _cacheByOrder.Values.OrderBy(w => w.WeekOrder);
if (string.IsNullOrWhiteSpace(filter))
{
return weeks;
}
return weeks.Where(w => w.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase));
}
public long? GetWeekDefinitionId(string gregorianWeekNumber)
{
EnsureCacheLoaded();
var week = GetWeekByGregorianWeekNumber(gregorianWeekNumber);
return week?.Id;
}
public string? GetGregorianWeekNumber(long weekDefinitionId)
{
EnsureCacheLoaded();
var week = _allWeeks.FirstOrDefault(w => w.Id == weekDefinitionId);
return week?.GregorianWeekNumber;
}
public WeekDefinition? GetWeekById(long weekDefinitionId)
{
EnsureCacheLoaded();
var week = _allWeeks.FirstOrDefault(w => w.Id == weekDefinitionId);
return week;
}
#endregion
#region Calculation Methods (Fallback when not in cache)
public string CalculateGregorianWeekNumber(DateTime date)
{
// پیدا کردن شنبه این هفته
var saturday = GetStartOfWeek(date);
// دوشنبه همان هفته ISO
var monday = saturday.AddDays(2);
var cal = CultureInfo.InvariantCulture.Calendar;
var weekNumber = cal.GetWeekOfYear(monday, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
var year = monday.Year;
if (weekNumber == 1 && monday.Month == 12) year++;
else if (weekNumber >= 52 && monday.Month == 1) year--;
return $"{year}-W{weekNumber:D2}";
}
public string CalculatePersianWeekNumber(DateTime date)
{
var saturday = GetStartOfWeek(date);
var persianYear = _persianCalendar.GetYear(saturday);
var dayOfYear = _persianCalendar.GetDayOfYear(saturday);
var firstDayOfYear = _persianCalendar.ToDateTime(persianYear, 1, 1, 0, 0, 0, 0);
var daysUntilFirstSaturday = ((int)DayOfWeek.Saturday - (int)firstDayOfYear.DayOfWeek + 7) % 7;
int weekNumber;
if (daysUntilFirstSaturday == 0)
{
weekNumber = ((dayOfYear - 1) / 7) + 1;
}
else
{
if (dayOfYear <= daysUntilFirstSaturday)
weekNumber = 1;
else
weekNumber = ((dayOfYear - daysUntilFirstSaturday - 1) / 7) + 1;
}
return $"{persianYear}-W{weekNumber:D2}";
}
#endregion
#region Helper Methods
private void EnsureCacheLoaded()
{
if (!_isCacheLoaded)
{
_logger.LogWarning("WeekDefinition cache is not loaded. Some methods may return null.");
}
}
/// <summary>
/// پیدا کردن شنبه این هفته (شروع هفته شمسی)
/// </summary>
private static DateTime GetStartOfWeek(DateTime date)
{
var diff = (7 + (date.DayOfWeek - DayOfWeek.Saturday)) % 7;
return date.AddDays(-diff).Date;
}
#endregion
}
@@ -111,6 +111,13 @@ service CommissionContract
}; };
}; };
// Week Definitions (for dropdowns)
rpc GetWeekDefinitions(GetWeekDefinitionsRequest) returns (GetWeekDefinitionsResponse){
option (google.api.http) = {
get: "/Commission/GetWeekDefinitions"
};
};
// Financial Reports // Financial Reports
rpc GetWithdrawalReports(GetWithdrawalReportsRequest) returns (GetWithdrawalReportsResponse){ rpc GetWithdrawalReports(GetWithdrawalReportsRequest) returns (GetWithdrawalReportsResponse){
option (google.api.http) = { option (google.api.http) = {
@@ -124,20 +131,20 @@ service CommissionContract
// CalculateWeeklyBalances Command // CalculateWeeklyBalances Command
message CalculateWeeklyBalancesRequest message CalculateWeeklyBalancesRequest
{ {
string week_number = 1; // Format: "YYYY-Www" (e.g., "2025-W01") int64 week_definition_id = 1; // Format: "YYYY-Www" (e.g., "2025-W01")
bool force_recalculate = 2; bool force_recalculate = 2;
} }
// CalculateWeeklyCommissionPool Command // CalculateWeeklyCommissionPool Command
message CalculateWeeklyCommissionPoolRequest message CalculateWeeklyCommissionPoolRequest
{ {
string week_number = 1; int64 week_definition_id = 1;
} }
// ProcessUserPayouts Command // ProcessUserPayouts Command
message ProcessUserPayoutsRequest message ProcessUserPayoutsRequest
{ {
string week_number = 1; int64 week_definition_id = 1;
bool force_reprocess = 2; bool force_reprocess = 2;
} }
@@ -176,19 +183,19 @@ message RejectWithdrawalRequest
// GetWeeklyCommissionPool Query // GetWeeklyCommissionPool Query
message GetWeeklyCommissionPoolRequest message GetWeeklyCommissionPoolRequest
{ {
string week_number = 1; int64 week_definition_id = 1;
} }
message GetWeeklyCommissionPoolResponse message GetWeeklyCommissionPoolResponse
{ {
int64 id = 1; int64 id = 1;
string week_number = 2; int64 week_definition_id = 2;
int64 total_pool_amount = 3; // Rials int64 total_pool_amount = 4; // Rials
int32 total_balances = 4; int32 total_balances = 5;
int64 value_per_balance = 5; // Rials per balance int64 value_per_balance = 6; // Rials per balance
bool is_calculated = 6; bool is_calculated = 7;
google.protobuf.Timestamp calculated_at = 7; google.protobuf.Timestamp calculated_at = 8;
google.protobuf.Timestamp created = 8; google.protobuf.Timestamp created = 9;
} }
// GetUserCommissionPayouts Query // GetUserCommissionPayouts Query
@@ -196,9 +203,9 @@ message GetUserCommissionPayoutsRequest
{ {
google.protobuf.Int64Value user_id = 1; google.protobuf.Int64Value user_id = 1;
google.protobuf.Int32Value status = 2; // CommissionPayoutStatus enum google.protobuf.Int32Value status = 2; // CommissionPayoutStatus enum
google.protobuf.StringValue week_number = 3; google.protobuf.Int64Value week_definition_id = 3; // Preferred filter
int32 page_index = 4; int32 page_index = 5;
int32 page_size = 5; int32 page_size = 6;
} }
message GetUserCommissionPayoutsResponse message GetUserCommissionPayoutsResponse
@@ -210,19 +217,20 @@ message GetUserCommissionPayoutsResponse
message UserCommissionPayoutModel message UserCommissionPayoutModel
{ {
int64 id = 1; int64 id = 1;
int64 user_id = 2; int64 week_definition_id = 2;
string user_name = 3; int64 user_id = 3;
string week_number = 4; string user_name = 4;
int64 weekly_pool_id = 5; string week_display_name = 5;
int64 balances_earned = 6; int64 weekly_pool_id = 6;
int64 value_per_balance = 7; int64 balances_earned = 7;
int64 total_amount = 8; int64 value_per_balance = 8;
int32 status = 9; // CommissionPayoutStatus enum int64 total_amount = 9;
google.protobuf.Timestamp paid_at = 10; int32 status = 10; // CommissionPayoutStatus enum
google.protobuf.Int32Value withdrawal_method = 11; google.protobuf.Timestamp paid_at = 11;
string iban_number = 12; google.protobuf.Int32Value withdrawal_method = 12;
google.protobuf.Timestamp withdrawn_at = 13; string iban_number = 13;
google.protobuf.Timestamp created = 14; google.protobuf.Timestamp withdrawn_at = 14;
google.protobuf.Timestamp created = 15;
} }
// GetCommissionPayoutHistory Query // GetCommissionPayoutHistory Query
@@ -230,9 +238,9 @@ message GetCommissionPayoutHistoryRequest
{ {
google.protobuf.Int64Value payout_id = 1; google.protobuf.Int64Value payout_id = 1;
google.protobuf.Int64Value user_id = 2; google.protobuf.Int64Value user_id = 2;
google.protobuf.StringValue week_number = 3; google.protobuf.Int64Value week_definition_id = 3; // Preferred filter
int32 page_index = 4; int32 page_index = 5;
int32 page_size = 5; int32 page_size = 6;
} }
message GetCommissionPayoutHistoryResponse message GetCommissionPayoutHistoryResponse
@@ -246,14 +254,14 @@ message CommissionPayoutHistoryModel
int64 id = 1; int64 id = 1;
int64 payout_id = 2; int64 payout_id = 2;
int64 user_id = 3; int64 user_id = 3;
string week_number = 4; int64 week_definition_id = 4;
int64 amount_before = 5; int64 amount_before = 6;
int64 amount_after = 6; int64 amount_after = 7;
int32 old_status = 7; // CommissionPayoutStatus enum int32 old_status = 8; // CommissionPayoutStatus enum
int32 new_status = 8; int32 new_status = 9;
int32 action = 9; // CommissionPayoutAction enum int32 action = 10; // CommissionPayoutAction enum
string performed_by = 10; string performed_by = 11;
string reason = 11; string reason = 12;
google.protobuf.Timestamp created = 13; google.protobuf.Timestamp created = 13;
} }
@@ -275,15 +283,16 @@ message GetAvailableWeeksResponse
message WeekInfo message WeekInfo
{ {
string week_number = 1; // YYYY-Www format int64 week_definition_id = 1; // FK to WeekDefinition
google.protobuf.Timestamp start_date = 2; string display_name = 2;
google.protobuf.Timestamp end_date = 3; google.protobuf.Timestamp start_date = 3;
bool is_calculated = 4; google.protobuf.Timestamp end_date = 4;
google.protobuf.Timestamp calculated_at = 5; bool is_calculated = 5;
string last_execution_status = 6; google.protobuf.Timestamp calculated_at = 6;
int64 total_pool_amount = 7; string last_execution_status = 7;
int32 eligible_users_count = 8; int64 total_pool_amount = 8;
string display_text = 9; // نمایش فارسی برای UI int32 eligible_users_count = 9;
string display_text = 10; // نمایش فارسی برای UI
} }
@@ -291,10 +300,10 @@ message WeekInfo
message GetUserWeeklyBalancesRequest message GetUserWeeklyBalancesRequest
{ {
google.protobuf.Int64Value user_id = 1; google.protobuf.Int64Value user_id = 1;
google.protobuf.StringValue week_number = 2; google.protobuf.Int64Value week_definition_id = 2; // Preferred filter
bool only_active = 3; // Only non-expired balances bool only_active = 4; // Only non-expired balances
int32 page_index = 4; int32 page_index = 5;
int32 page_size = 5; int32 page_size = 6;
} }
message GetUserWeeklyBalancesResponse message GetUserWeeklyBalancesResponse
@@ -307,14 +316,15 @@ message UserWeeklyBalanceModel
{ {
int64 id = 1; int64 id = 1;
int64 user_id = 2; int64 user_id = 2;
string week_number = 3; int64 week_definition_id = 3;
int32 left_leg_balances = 4; string week_display_name = 4;
int32 right_leg_balances = 5; int32 left_leg_balances = 5;
int32 total_balances = 6; int32 right_leg_balances = 6;
int64 weekly_pool_contribution = 7; int32 total_balances = 7;
google.protobuf.Timestamp calculated_at = 8; int64 weekly_pool_contribution = 8;
bool is_expired = 9; google.protobuf.Timestamp calculated_at = 9;
google.protobuf.Timestamp created = 10; bool is_expired = 10;
google.protobuf.Timestamp created = 11;
} }
// GetAllWeeklyPools Query // GetAllWeeklyPools Query
@@ -336,13 +346,13 @@ message GetAllWeeklyPoolsResponse
message WeeklyCommissionPoolModel message WeeklyCommissionPoolModel
{ {
int64 id = 1; int64 id = 1;
string week_number = 2; int64 week_definition_id = 2;
int64 total_pool_amount = 3; int64 total_pool_amount = 4;
int32 total_balances = 4; int32 total_balances = 5;
int64 value_per_balance = 5; int64 value_per_balance = 6;
bool is_calculated = 6; bool is_calculated = 7;
google.protobuf.Timestamp calculated_at = 7; google.protobuf.Timestamp calculated_at = 8;
google.protobuf.Timestamp created = 8; google.protobuf.Timestamp created = 9;
} }
// GetWithdrawalRequests Query // GetWithdrawalRequests Query
@@ -350,10 +360,10 @@ message GetWithdrawalRequestsRequest
{ {
google.protobuf.Int32Value status = 1; // CommissionPayoutStatus enum: Pending=1, Approved=2, Rejected=3 google.protobuf.Int32Value status = 1; // CommissionPayoutStatus enum: Pending=1, Approved=2, Rejected=3
google.protobuf.Int64Value user_id = 2; google.protobuf.Int64Value user_id = 2;
google.protobuf.StringValue week_number = 3; google.protobuf.Int64Value week_definition_id = 3; // Preferred filter
int32 page_index = 4; int32 page_index = 5;
int32 page_size = 5; int32 page_size = 6;
string iban_number = 6; string iban_number = 7;
} }
message GetWithdrawalRequestsResponse message GetWithdrawalRequestsResponse
@@ -367,7 +377,7 @@ message GetWithdrawalRequestsResponse
// TriggerWeeklyCalculation Command // TriggerWeeklyCalculation Command
message TriggerWeeklyCalculationRequest message TriggerWeeklyCalculationRequest
{ {
string week_number = 1; // Format: "YYYY-Www" (e.g., "2025-W48") int64 week_definition_id = 1; // Format: "YYYY-Www" (e.g., "2025-W48")
bool force_recalculate = 2; // اگر true باشد، محاسبات قبلی را حذف و دوباره محاسبه می‌کند bool force_recalculate = 2; // اگر true باشد، محاسبات قبلی را حذف و دوباره محاسبه می‌کند
bool skip_balances = 3; // Skip balance calculation (only pool and payouts) bool skip_balances = 3; // Skip balance calculation (only pool and payouts)
bool skip_pool = 4; // Skip pool calculation (only balances and payouts) bool skip_pool = 4; // Skip pool calculation (only balances and payouts)
@@ -405,12 +415,12 @@ message GetWorkerStatusResponse
// GetWorkerExecutionLogs Query // GetWorkerExecutionLogs Query
message GetWorkerExecutionLogsRequest message GetWorkerExecutionLogsRequest
{ {
google.protobuf.StringValue week_number = 1; // Filter by week google.protobuf.Int64Value week_definition_id = 1; // Preferred filter
google.protobuf.StringValue execution_id = 2; // Filter by specific execution google.protobuf.StringValue execution_id = 3; // Filter by specific execution
google.protobuf.BoolValue success_only = 3; // Show only successful runs google.protobuf.BoolValue success_only = 4; // Show only successful runs
google.protobuf.BoolValue failed_only = 4; // Show only failed runs google.protobuf.BoolValue failed_only = 5; // Show only failed runs
int32 page_index = 5; int32 page_index = 6;
int32 page_size = 6; int32 page_size = 7;
} }
message GetWorkerExecutionLogsResponse message GetWorkerExecutionLogsResponse
@@ -422,15 +432,15 @@ message GetWorkerExecutionLogsResponse
message WorkerExecutionLogModel message WorkerExecutionLogModel
{ {
string execution_id = 1; string execution_id = 1;
string week_number = 2; int64 week_definition_id = 2;
string step = 3; // "Balances" | "Pool" | "Payouts" | "Full" string step = 4; // "Balances" | "Pool" | "Payouts" | "Full"
bool success = 4; bool success = 5;
google.protobuf.StringValue error_message = 5; google.protobuf.StringValue error_message = 6;
google.protobuf.Timestamp started_at = 6; google.protobuf.Timestamp started_at = 7;
google.protobuf.Timestamp completed_at = 7; google.protobuf.Timestamp completed_at = 8;
int64 duration_ms = 8; // Duration in milliseconds int64 duration_ms = 9; // Duration in milliseconds
int32 records_processed = 9; int32 records_processed = 10;
google.protobuf.StringValue details = 10; // JSON or text details google.protobuf.StringValue details = 11; // JSON or text details
} }
// GetWithdrawalReports Query // GetWithdrawalReports Query
@@ -482,17 +492,69 @@ message WithdrawalRequestModel
int64 id = 1; int64 id = 1;
int64 user_id = 2; int64 user_id = 2;
string user_name = 3; string user_name = 3;
string week_number = 4; int64 week_definition_id = 4;
int64 amount = 5; int64 amount = 6;
int32 status = 6; // CommissionPayoutStatus enum int32 status = 7; // CommissionPayoutStatus enum
int32 withdrawal_method = 7; // WithdrawalMethod enum int32 withdrawal_method = 8; // WithdrawalMethod enum
string iban_number = 8; string iban_number = 9;
google.protobuf.Timestamp requested_at = 9; google.protobuf.Timestamp requested_at = 10;
google.protobuf.Timestamp processed_at = 10; google.protobuf.Timestamp processed_at = 11;
string processed_by = 11; string processed_by = 12;
string reason = 12; string reason = 13;
google.protobuf.Timestamp created = 13; google.protobuf.Timestamp created = 14;
string bank_reference_id = 14; string bank_reference_id = 15;
string bank_tracking_code = 15; string bank_tracking_code = 16;
string payment_failure_reason = 16; string payment_failure_reason = 17;
}
// ============ Week Definitions (for dropdowns) ============
// GetWeekDefinitions Query - برای دریافت لیست هفته‌ها برای dropdown
message GetWeekDefinitionsRequest
{
//موقعیت صفحه بندی
messages.PaginationState pagination_state = 1;
//مرتب سازی بر اساس
google.protobuf.StringValue sort_by = 2;
//فیلتر
GetWeekDefinitionsFilter filter = 3;
}
message GetWeekDefinitionsFilter
{
//جستجوی متنی روی DisplayName
google.protobuf.StringValue search_text = 1;
//شماره ترتیب هفته
google.protobuf.Int32Value week_order = 2;
//شماره هفته میلادی
google.protobuf.StringValue gregorian_week_number = 3;
//شماره هفته شمسی
google.protobuf.StringValue persian_week_number = 4;
//سال میلادی
google.protobuf.Int32Value gregorian_year = 5;
//سال شمسی
google.protobuf.Int32Value persian_year = 6;
//فقط هفته‌های فعال
google.protobuf.BoolValue is_active = 7;
}
message GetWeekDefinitionsResponse
{
repeated WeekDefinitionItem data = 1;
int32 total_count = 2;
}
message WeekDefinitionItem
{
int64 id = 1; // WeekDefinitionId
int32 week_order = 2;
string display_name = 3; // هفته یکم، هفته دوم، ...
string gregorian_week_number = 4; // 2025-W46
string persian_week_number = 5; // 1404-W35
google.protobuf.Timestamp start_date = 6;
google.protobuf.Timestamp end_date = 7;
int32 gregorian_year = 8;
int32 persian_year = 9;
bool is_active = 10;
bool is_current_week = 11;
} }
@@ -1,8 +1,12 @@
using CMSMicroservice.Application.CommissionCQ.Queries.GetUserWeeklyBalances;
using CMSMicroservice.Application.CommissionCQ.Queries.GetAvailableWeeks; using CMSMicroservice.Application.CommissionCQ.Queries.GetAvailableWeeks;
using CMSMicroservice.Application.CommissionCQ.Queries.GetUserCommissionPayouts; using CMSMicroservice.Application.CommissionCQ.Queries.GetUserCommissionPayouts;
using CMSMicroservice.Application.CommissionCQ.Queries.GetWeekDefinitions;
using CMSMicroservice.Application.Common.Models;
using CMSMicroservice.Protobuf.Protos.Commission; using CMSMicroservice.Protobuf.Protos.Commission;
using Google.Protobuf.WellKnownTypes; using Google.Protobuf.WellKnownTypes;
using Mapster; using Mapster;
using AppWeekFilter = CMSMicroservice.Application.CommissionCQ.Queries.GetWeekDefinitions.GetWeekDefinitionsFilter;
namespace CMSMicroservice.WebApi.Common.Mappings; namespace CMSMicroservice.WebApi.Common.Mappings;
@@ -24,7 +28,8 @@ public class CommissionProfile : IRegister
// WeekInfo Mapping // WeekInfo Mapping
config.NewConfig<WeekInfoDto, WeekInfo>() config.NewConfig<WeekInfoDto, WeekInfo>()
.Map(dest => dest.WeekNumber, src => src.WeekNumber) .Map(dest => dest.WeekDefinitionId, src => src.WeekDefinitionId)
.Map(dest => dest.DisplayName, src => src.DisplayName)
.Map(dest => dest.StartDate, src => Timestamp.FromDateTime(src.StartDate.ToUniversalTime())) .Map(dest => dest.StartDate, src => Timestamp.FromDateTime(src.StartDate.ToUniversalTime()))
.Map(dest => dest.EndDate, src => Timestamp.FromDateTime(src.EndDate.ToUniversalTime())) .Map(dest => dest.EndDate, src => Timestamp.FromDateTime(src.EndDate.ToUniversalTime()))
.Map(dest => dest.IsCalculated, src => src.IsCalculated) .Map(dest => dest.IsCalculated, src => src.IsCalculated)
@@ -40,9 +45,10 @@ public class CommissionProfile : IRegister
// GetUserCommissionPayouts Response Model Mapping // GetUserCommissionPayouts Response Model Mapping
config.NewConfig<GetUserCommissionPayoutsResponseModel, UserCommissionPayoutModel>() config.NewConfig<GetUserCommissionPayoutsResponseModel, UserCommissionPayoutModel>()
.Map(dest => dest.Id, src => src.Id) .Map(dest => dest.Id, src => src.Id)
.Map(dest => dest.WeekDefinitionId, src => src.WeekDefinitionId)
.Map(dest => dest.UserId, src => src.UserId) .Map(dest => dest.UserId, src => src.UserId)
.Map(dest => dest.UserName, src => $"{src.FirstName} {src.LastName}") .Map(dest => dest.UserName, src => $"{src.FirstName} {src.LastName}")
.Map(dest => dest.WeekNumber, src => src.WeekNumber) .Map(dest => dest.WeekDisplayName, src => src.WeekDisplayName)
.Map(dest => dest.WeeklyPoolId, src => src.WeeklyPoolId) .Map(dest => dest.WeeklyPoolId, src => src.WeeklyPoolId)
.Map(dest => dest.BalancesEarned, src => src.BalancesEarned) .Map(dest => dest.BalancesEarned, src => src.BalancesEarned)
.Map(dest => dest.ValuePerBalance, src => src.ValuePerBalance) .Map(dest => dest.ValuePerBalance, src => src.ValuePerBalance)
@@ -59,5 +65,58 @@ public class CommissionProfile : IRegister
? Timestamp.FromDateTime(src.WithdrawnAt.Value.ToUniversalTime()) ? Timestamp.FromDateTime(src.WithdrawnAt.Value.ToUniversalTime())
: null) : null)
.Map(dest => dest.Created, src => Timestamp.FromDateTimeOffset(src.Created)); .Map(dest => dest.Created, src => Timestamp.FromDateTimeOffset(src.Created));
// GetWeekDefinitions Request Mapping
config.NewConfig<GetWeekDefinitionsRequest, GetWeekDefinitionsQuery>()
.Map(dest => dest.PaginationState, src => src.PaginationState != null
? new PaginationState { PageNumber = src.PaginationState.PageNumber, PageSize = src.PaginationState.PageSize }
: null)
.Map(dest => dest.SortBy, src => src.SortBy)
.Map(dest => dest.Filter, src => src.Filter != null
? new AppWeekFilter
{
SearchText = src.Filter.SearchText,
WeekOrder = src.Filter.WeekOrder,
GregorianWeekNumber = src.Filter.GregorianWeekNumber,
PersianWeekNumber = src.Filter.PersianWeekNumber,
GregorianYear = src.Filter.GregorianYear,
PersianYear = src.Filter.PersianYear,
IsActive = src.Filter.IsActive
}
: null);
// GetWeekDefinitions Response Mapping
config.NewConfig<GetWeekDefinitionsResponseDto, GetWeekDefinitionsResponse>()
.Map(dest => dest.TotalCount, src => src.TotalCount)
.Map(dest => dest.Data, src => src.Data);
// WeekDefinitionItem Mapping
config.NewConfig<WeekDefinitionItemDto, WeekDefinitionItem>()
.Map(dest => dest.WeekOrder, src => src.WeekOrder)
.Map(dest => dest.DisplayName, src => src.DisplayName)
.Map(dest => dest.GregorianWeekNumber, src => src.GregorianWeekNumber)
.Map(dest => dest.PersianWeekNumber, src => src.PersianWeekNumber)
.Map(dest => dest.StartDate, src => Timestamp.FromDateTime(DateTime.SpecifyKind(src.StartDate, DateTimeKind.Utc)))
.Map(dest => dest.EndDate, src => Timestamp.FromDateTime(DateTime.SpecifyKind(src.EndDate, DateTimeKind.Utc)))
.Map(dest => dest.GregorianYear, src => src.GregorianYear)
.Map(dest => dest.PersianYear, src => src.PersianYear)
.Map(dest => dest.IsActive, src => src.IsActive)
.Map(dest => dest.IsCurrentWeek, src => src.IsCurrentWeek);
// GetUserWeeklyBalances Response Model Mapping
config.NewConfig<GetUserWeeklyBalancesResponseModel, UserWeeklyBalanceModel>()
.Map(dest => dest.Id, src => src.Id)
.Map(dest => dest.UserId, src => src.UserId)
.Map(dest => dest.WeekDefinitionId, src => src.WeekDefinitionId)
.Map(dest => dest.WeekDisplayName, src => src.WeekDisplayName)
.Map(dest => dest.LeftLegBalances, src => src.LeftLegBalances)
.Map(dest => dest.RightLegBalances, src => src.RightLegBalances)
.Map(dest => dest.TotalBalances, src => src.TotalBalances)
.Map(dest => dest.WeeklyPoolContribution, src => src.WeeklyPoolContribution)
.Map(dest => dest.CalculatedAt, src => src.CalculatedAt.HasValue
? Timestamp.FromDateTime(DateTime.SpecifyKind(src.CalculatedAt.Value, DateTimeKind.Utc))
: null)
.Map(dest => dest.IsExpired, src => src.IsExpired)
.Map(dest => dest.Created, src => Timestamp.FromDateTimeOffset(src.Created));
} }
} }
@@ -34,13 +34,13 @@ public class AdminController : ControllerBase
/// <param name="weekNumber">Week number in YYYY-Www format (e.g., 2025-W48). If null, uses previous week.</param> /// <param name="weekNumber">Week number in YYYY-Www format (e.g., 2025-W48). If null, uses previous week.</param>
/// <returns>Job ID for tracking</returns> /// <returns>Job ID for tracking</returns>
[HttpPost("trigger-weekly-calculation")] [HttpPost("trigger-weekly-calculation")]
public IActionResult TriggerWeeklyCalculation([FromQuery] string? weekNumber = null) public IActionResult TriggerWeeklyCalculation([FromQuery] long? weekDefinitionId = null)
{ {
_logger.LogInformation("🔧 Manual trigger requested by admin for week: {WeekNumber}", weekNumber ?? "previous"); _logger.LogInformation("🔧 Manual trigger requested by admin for WeekDefinitionId: {WeekDefinitionId}", weekDefinitionId?.ToString() ?? "previous");
// Enqueue immediate job execution with specified week number // Enqueue immediate job execution with specified week
var jobId = _backgroundJobClient.Enqueue<WeeklyCommissionJob>( var jobId = _backgroundJobClient.Enqueue<WeeklyCommissionJob>(
job => job.ExecuteAsync(weekNumber, CancellationToken.None)); job => job.ExecuteAsync(weekDefinitionId, CancellationToken.None));
_logger.LogInformation("✅ Job enqueued with ID: {JobId}", jobId); _logger.LogInformation("✅ Job enqueued with ID: {JobId}", jobId);
+10
View File
@@ -1,5 +1,6 @@
using CMSMicroservice.Infrastructure.Persistence; using CMSMicroservice.Infrastructure.Persistence;
using CMSMicroservice.Infrastructure.Data.Seeding; using CMSMicroservice.Infrastructure.Data.Seeding;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.WebApi.Hubs; using CMSMicroservice.WebApi.Hubs;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -144,6 +145,11 @@ if (app.Environment.IsDevelopment())
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
var migrationSeeder = new NetworkParentIdMigrationSeeder(dbContext, migrationLogger); var migrationSeeder = new NetworkParentIdMigrationSeeder(dbContext, migrationLogger);
await migrationSeeder.SeedAsync(); await migrationSeeder.SeedAsync();
// Seed WeekDefinitions (هفته‌ها)
var weekSeederLogger = scope.ServiceProvider.GetRequiredService<ILogger<WeekDefinitionSeeder>>();
var weekSeeder = new WeekDefinitionSeeder(dbContext, weekSeederLogger);
await weekSeeder.SeedAsync();
} }
} }
else else
@@ -152,6 +158,10 @@ else
app.UseHsts(); app.UseHsts();
} }
// Load WeekDefinition cache (برای هر دو محیط Development و Production)
var weekDefinitionRepository = app.Services.GetRequiredService<IWeekDefinitionRepository>();
await weekDefinitionRepository.ReloadCacheAsync();
app.UseRouting(); app.UseRouting();
app.UseCors("AllowAll"); app.UseCors("AllowAll");
@@ -18,6 +18,7 @@ using CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerStatus;
using CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerExecutionLogs; using CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerExecutionLogs;
using CMSMicroservice.Application.CommissionCQ.Queries.GetWithdrawalReports; using CMSMicroservice.Application.CommissionCQ.Queries.GetWithdrawalReports;
using CMSMicroservice.Application.CommissionCQ.Queries.GetAvailableWeeks; using CMSMicroservice.Application.CommissionCQ.Queries.GetAvailableWeeks;
using CMSMicroservice.Application.CommissionCQ.Queries.GetWeekDefinitions;
namespace CMSMicroservice.WebApi.Services; namespace CMSMicroservice.WebApi.Services;
@@ -118,6 +119,11 @@ public class CommissionService : CommissionContract.CommissionContractBase
return await _dispatchRequestToCQRS.Handle<GetAvailableWeeksRequest, GetAvailableWeeksQuery, GetAvailableWeeksResponse>(request, context); return await _dispatchRequestToCQRS.Handle<GetAvailableWeeksRequest, GetAvailableWeeksQuery, GetAvailableWeeksResponse>(request, context);
} }
public override async Task<GetWeekDefinitionsResponse> GetWeekDefinitions(GetWeekDefinitionsRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetWeekDefinitionsRequest, GetWeekDefinitionsQuery, GetWeekDefinitionsResponse>(request, context);
}
public override async Task<GetWithdrawalReportsResponse> GetWithdrawalReports(GetWithdrawalReportsRequest request, ServerCallContext context) public override async Task<GetWithdrawalReportsResponse> GetWithdrawalReports(GetWithdrawalReportsRequest request, ServerCallContext context)
{ {
return await _dispatchRequestToCQRS.Handle<GetWithdrawalReportsRequest, GetWithdrawalReportsQuery, GetWithdrawalReportsResponse>(request, context); return await _dispatchRequestToCQRS.Handle<GetWithdrawalReportsRequest, GetWithdrawalReportsQuery, GetWithdrawalReportsResponse>(request, context);