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
@@ -21,16 +21,19 @@ public class WeeklyCommissionJob
private readonly IMediator _mediator;
private readonly ILogger<WeeklyCommissionJob> _logger;
private readonly IApplicationDbContext _context;
private readonly IWeekDefinitionRepository _weekRepository;
private readonly ResiliencePipeline _retryPipeline;
public WeeklyCommissionJob(
IMediator mediator,
ILogger<WeeklyCommissionJob> logger,
IApplicationDbContext context)
IApplicationDbContext context,
IWeekDefinitionRepository weekRepository)
{
_mediator = mediator;
_logger = logger;
_context = context;
_weekRepository = weekRepository;
// Polly Retry: 3 attempts, exponential backoff (5min → 10min → 20min)
_retryPipeline = new ResiliencePipelineBuilder()
@@ -57,38 +60,42 @@ public class WeeklyCommissionJob
/// Execute weekly commission calculation with retry logic
/// Called by Hangfire scheduler or manually triggered
/// </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>
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 startTime = DateTime.Now;
// Use provided week number or calculate for PREVIOUS week (completed week)
string targetWeekNumber;
if (!string.IsNullOrWhiteSpace(weekNumber))
// Use provided WeekDefinitionId or calculate for PREVIOUS week (completed week)
long targetWeekDefinitionId;
if (weekDefinitionId.HasValue && weekDefinitionId.Value > 0)
{
targetWeekNumber = weekNumber;
_logger.LogInformation("📅 Using manually specified week: {WeekNumber}", targetWeekNumber);
targetWeekDefinitionId = weekDefinitionId.Value;
_logger.LogInformation("📅 Using manually specified WeekDefinitionId: {WeekDefinitionId}", targetWeekDefinitionId);
}
else
{
var previousWeek = DateTime.Now.AddDays(-7);
targetWeekNumber = GetWeekNumber(previousWeek);
_logger.LogInformation("📅 Using previous week (auto-calculated): {WeekNumber}", targetWeekNumber);
var weekDef = _weekRepository.GetWeekByDate(previousWeek);
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(
"🚀 [{ExecutionId}] Starting weekly commission calculation for {WeekNumber}",
executionId, previousWeekNumber);
"🚀 [{ExecutionId}] Starting weekly commission calculation for WeekDefinitionId={WeekDefinitionId}",
executionId, targetWeekDefinitionId);
// Create execution log entry
var log = new WorkerExecutionLog
{
ExecutionId = executionId,
WeekNumber = previousWeekNumber,
WeekDefinitionId = targetWeekDefinitionId,
StartedAt = startTime,
Status = WorkerExecutionStatus.Running
};
@@ -100,7 +107,7 @@ public class WeeklyCommissionJob
// Execute with retry pipeline
await _retryPipeline.ExecuteAsync(async ct =>
{
await ExecuteWeeklyCalculationAsync(executionId, previousWeekNumber, ct);
await ExecuteWeeklyCalculationAsync(executionId, targetWeekDefinitionId, ct);
}, cancellationToken);
// Update log on success
@@ -113,9 +120,9 @@ public class WeeklyCommissionJob
// Get counts from database
var balancesCount = await _context.NetworkWeeklyBalances
.CountAsync(x => x.WeekNumber == previousWeekNumber, cancellationToken);
.CountAsync(x => x.WeekDefinitionId == targetWeekDefinitionId, cancellationToken);
var payoutsCount = await _context.UserCommissionPayouts
.CountAsync(x => x.WeekNumber == previousWeekNumber, cancellationToken);
.CountAsync(x => x.WeekDefinitionId == targetWeekDefinitionId, cancellationToken);
log.ProcessedCount = balancesCount + payoutsCount;
@@ -149,21 +156,28 @@ public class WeeklyCommissionJob
private async Task ExecuteWeeklyCalculationAsync(
Guid executionId,
string weekNumber,
long weekDefinitionId,
CancellationToken cancellationToken)
{
// Check idempotency: Skip if already calculated
var existingPool = await _context.WeeklyCommissionPools
.FirstOrDefaultAsync(x => x.WeekNumber == weekNumber, cancellationToken);
.FirstOrDefaultAsync(x => x.WeekDefinitionId == weekDefinitionId, cancellationToken);
if (existingPool != null && existingPool.IsCalculated)
{
_logger.LogWarning(
"⚠️ [{ExecutionId}] Week {WeekNumber} already calculated. Skipping.",
executionId, weekNumber);
"⚠️ [{ExecutionId}] WeekDefinitionId={WeekDefinitionId} already calculated. Skipping.",
executionId, weekDefinitionId);
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(
System.Transactions.TransactionScopeOption.Required,
new System.Transactions.TransactionOptions
@@ -182,41 +196,14 @@ public class WeeklyCommissionJob
await _mediator.Send(new TriggerWeeklyCalculationCommand
{
WeekNumber = weekNumber,
WeekDefinitionId = weekDefinitionId,
ForceRecalculate = false
}, 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();
_logger.LogInformation(
"✅ [{ExecutionId}] All 2 steps completed successfully",
"✅ [{ExecutionId}] All steps completed successfully",
executionId);
}
catch (Exception ex)
@@ -227,24 +214,4 @@ public class WeeklyCommissionJob
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}";
}
}