360 lines
12 KiB
C#
360 lines
12 KiB
C#
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
|
|
}
|