feat: refactor week number handling to use WeekDefinitionId across multiple entities and queries
This commit is contained in:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user