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
@@ -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
}