@@ -0,0 +1,517 @@
using Microsoft.Extensions.Logging ;
using PARR.DAL.Contracts ;
using PARR.DAL.DomainModels ;
using PARR.DAL.Services.Interfaces ;
using PARR.DAL.TransformServices ;
namespace PARR.DAL.NextRunServices.Subservices
{
internal class EsppScheduleTransformService : IEsppScheduleTransformService
{
private readonly IEsppSchTypeConfigService esppSchTypeConfigService ;
//private readonly INextRunModifierService nextRunModifierService;
private readonly ILogger < EsppScheduleTransformService > logger ;
private static readonly Dictionary < string , int > monthDict = new Dictionary < string , int > ( )
{
{ "Январь" , 1 } ,
{ "Февраль" , 2 } ,
{ "Март" , 3 } ,
{ "Апрель" , 4 } ,
{ "Май" , 5 } ,
{ "Июнь" , 6 } ,
{ "Июль" , 7 } ,
{ "Август" , 8 } ,
{ "Сентябрь" , 9 } ,
{ "Октябрь" , 10 } ,
{ "Ноябрь" , 11 } ,
{ "Декабрь" , 12 }
} ;
private static readonly Dictionary < string , int > orderDict = new Dictionary < string , int > ( )
{
{ "Первый" , 1 } ,
{ "Второй" , 2 } ,
{ "Третий" , 3 } ,
{ "Четвертый" , 4 } ,
{ "Последний" , 5 }
} ;
private static readonly Dictionary < string , int > dwDict = new Dictionary < string , int > ( )
{
{ "Понедельник" , 1 } ,
{ "Вторник" , 2 } ,
{ "Среда" , 3 } ,
{ "Четверг" , 4 } ,
{ "Пятница" , 5 } ,
{ "Суббота" , 6 } ,
{ "Воскресенье" , 0 }
} ;
public EsppScheduleTransformService (
ILogger < EsppScheduleTransformService > logger ,
IEsppSchTypeConfigService esppSchTypeConfigService
//INextRunModifierService nextRunModifierService
)
{
this . esppSchTypeConfigService = esppSchTypeConfigService ;
//this.nextRunModifierService = nextRunModifierService;
this . logger = logger ;
}
public async Task < DateTimeOffset > GetNextDateAsync ( Guid jobGroupId , DateTimeOffset referenceDate )
{
var esppSchedule = await GetEsppScheduleAsync ( jobGroupId ) ;
return GetNextDate ( esppSchedule , referenceDate ) ;
}
private DateTimeOffset GetNextDate ( EsppScheduleDto esppSchedule , DateTimeOffset referenceDate )
{
var nextRun = referenceDate ;
//---------
// Для рассчета по М С К времени, потому что в ЮТС может быть еще В С К , а по М С К это уже ПНД
var offsetReferenceDate = referenceDate . ToOffset ( new TimeSpan ( 3 , 0 , 0 ) ) ;
//---------
switch ( esppSchedule . TypeSchedule . Id )
{
case ( int ) EsppSchTypeScheduleEnum . Regularly :
nextRun = GetNextDateRegularly ( esppSchedule . Values , offsetReferenceDate ) ;
break ;
case ( int ) EsppSchTypeScheduleEnum . Weekly :
nextRun = GetNextDateWeekly ( esppSchedule . Values , offsetReferenceDate ) ;
break ;
case ( int ) EsppSchTypeScheduleEnum . Monthly :
nextRun = GetNextDateMonthly ( esppSchedule . Values , offsetReferenceDate ) ;
break ;
case ( int ) EsppSchTypeScheduleEnum . Monthly2 :
nextRun = GetNextDateMonthly2 ( esppSchedule . Values , offsetReferenceDate ) ;
break ;
case ( int ) EsppSchTypeScheduleEnum . Annually :
nextRun = GetNextDateAnnually ( esppSchedule . Values , offsetReferenceDate ) ;
break ;
case ( int ) EsppSchTypeScheduleEnum . Annually2 :
nextRun = GetNextDateAnnually2 ( esppSchedule . Values , offsetReferenceDate ) ;
break ;
}
// Возвращаем обратно в ЮТС
nextRun = nextRun . ToOffset ( TimeSpan . Zero ) ;
//возможно для автораспределения нужно nextRun = nextRunModifierService.GetWorkDayAsync(nextRun).GetAwaiter().GetResult();
// тут может быть 21 час по М С К , то это предыдущие сутки, нужно отнять 1 день
//if (!DateResolver.IsCurrentDayRelativeMskTime(nextRun.Hour))
// nextRun = nextRun.AddDays(-1);
return nextRun ;
}
//private List<DateTimeOffset> GetNextSchedule(EsppScheduleDto esppSchedule, DateTimeOffset referenceDate)
//{
// // расписание на оставшееся на сегодня время. е г о так мало...
// referenceDate = referenceDate.UtcDateTime;
// var schedules = new List<DateTimeOffset>();
// var curTime = referenceDate;
// //Если ещё не произошло то добавляем расписание, возможно это новое AiW и он ещё ниразу не запускался.
// //lastRun это будущее
// if (referenceDate >= DateTimeOffset.UtcNow && referenceDate <= DateTimeOffset.UtcNow.EndOfDay())
// schedules.Add(referenceDate);
// while (curTime < DateTimeOffset.UtcNow.EndOfDay())
// {
// switch (esppSchedule.TypeSchedule.Id)
// {
// case (int)EsppSchTypeScheduleEnum.Regularly:
// curTime = GetNextDateRegularly(esppSchedule.Values, curTime);
// break;
// case (int)EsppSchTypeScheduleEnum.Weekly:
// curTime = GetNextDateWeekly(esppSchedule.Values, curTime);
// break;
// case (int)EsppSchTypeScheduleEnum.Monthly:
// curTime = GetNextDateMonthly(esppSchedule.Values, curTime);
// break;
// case (int)EsppSchTypeScheduleEnum.Monthly2:
// curTime = GetNextDateMonthly2(esppSchedule.Values, curTime);
// break;
// case (int)EsppSchTypeScheduleEnum.Annually:
// curTime = GetNextDateAnnually(esppSchedule.Values, curTime);
// break;
// case (int)EsppSchTypeScheduleEnum.Annually2:
// curTime = GetNextDateAnnually2(esppSchedule.Values, curTime);
// break;
// }
// schedules.Add(curTime);
// }
// schedules.RemoveAll(s => s > DateTimeOffset.UtcNow.EndOfDay());
// return schedules.OrderBy(t => t).ToList();
//}
//public async Task<List<DateTimeOffset>> GetNextScheduleAsync(Guid jobGroupId, DateTimeOffset referenceDate)
//{
// //TODO: тут не проверен переход через выходные дни!!! Переход не используется, так как тут не рассчитываем NextRun.
// //В общем проверить, когда будем тестировать агента, что с датами все ок
// var esppSchedule = await GetEsppScheduleAsync(jobGroupId);
// var nextSchedule = GetNextSchedule(esppSchedule, referenceDate);
// return nextSchedule;
//}
private async Task < EsppScheduleDto > GetEsppScheduleAsync ( Guid jobGroupId )
{
var esppSchedule = await esppSchTypeConfigService . GetEsppScheduleDtoAsync ( jobGroupId ) ;
if ( esppSchedule = = null )
{
logger . LogError ( $"Н е найдено расписание в БД, jobId: {jobGroupId}" ) ;
throw new Exception ( $"Н е найдено расписание в БД, jobId: {jobGroupId}" ) ;
}
return esppSchedule ;
}
private DateTimeOffset GetNextDateRegularly ( List < EsppScheduleValDto > values , DateTimeOffset referenceDate )
{
var regDict = new Dictionary < string , int >
{
{ "Каждый час" , 1 } ,
{ "Каждые 2 часа" , 2 } ,
{ "Каждые 3 часа" , 3 } ,
{ "Каждые 4 часа" , 4 } ,
{ "Каждые 6 часов" , 6 } ,
{ "Каждые 12 часов" , 12 } ,
{ "Ежедневно" , 24 } ,
{ "Каждые 72 часа" , 72 } ,
{ "Каждые 96 часов" , 96 } ,
{ "Каждые 2 недели" , 336 } ,
{ "Каждые 60 дней" , 1440 } ,
{ "Каждые 80 дней" , 1920 } ,
{ "Каждые 90 дней" , 2160 } ,
{ "Каждые полгода" , 4380 } ,
{ "Каждые 1,5 года" , 13140 } ,
{ "Каждые 3 года" , 26280 } ,
} ;
if ( ! regDict . TryGetValue ( values [ 0 ] . Value . Value , out int regNum ) )
{
logger . LogError ( "Неизвестное значение: {Value}" , values [ 0 ] . Value . Value ) ;
throw new ArgumentException ( $"Неизвестное значение: {values[0].Value.Value}" ) ;
}
var calcDay = referenceDate ;
//TODO: вот это повторяется от метода к методу
//Если итоговая дата указывает на прошлое, то повторяем расчёт и уходим в рекурсию
if ( calcDay < DateTimeOffset . UtcNow )
calcDay = GetNextDateRegularly ( values , calcDay . AddHours ( regNum ) ) ;
return calcDay ;
}
private DateTimeOffset GetNextDateWeekly ( List < EsppScheduleValDto > values , DateTimeOffset referenceDate )
{
if ( ! dwDict . TryGetValue ( values [ 0 ] . Value . Value , out int dwNum ) )
{
logger . LogError ( "Неизвестное значение дня недели: {Value}" , values [ 0 ] . Value . Value ) ;
throw new ArgumentException ( $"Неизвестное значение: {values[0].Value.Value}" ) ;
}
var calcDay = referenceDate ;
if ( calcDay < DateTimeOffset . UtcNow )
calcDay = new DateTimeOffset ( DateTimeOffset . UtcNow . Year , DateTimeOffset . UtcNow . Month , DateTimeOffset . UtcNow . Day , referenceDate . Hour , referenceDate . Minute , referenceDate . Second , referenceDate . Offset ) ;
if ( ( ( int ) calcDay . DayOfWeek ) ! = dwNum )
calcDay = OffsetToDayOfWeek ( dwNum , calcDay ) ;
//TODO: вот это повторяется от метода к методу
//Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
if ( calcDay < DateTimeOffset . UtcNow )
calcDay = GetNextDateWeekly ( values , calcDay . AddDays ( 7 ) ) ; //а вот тут что-то новенькое
return calcDay ;
}
private DateTimeOffset GetNextDateMonthly ( List < EsppScheduleValDto > values , DateTimeOffset referenceDate )
{
if ( ! int . TryParse ( values [ 0 ] . Value . Value , out int dayNum ) )
{
logger . LogError ( "Неизвестное значение дня месяца: {Value}" , values [ 0 ] . Value . Value ) ;
throw new ArgumentException ( $"Неизвестное значение: {values[0].Value.Value}" ) ;
}
var calcDay = new DateTimeOffset ( referenceDate . Year , referenceDate . Month , dayNum , referenceDate . Hour , referenceDate . Minute , referenceDate . Second , referenceDate . Offset ) ;
if ( referenceDate < DateTimeOffset . UtcNow )
calcDay = new DateTimeOffset ( DateTimeOffset . UtcNow . Year , DateTimeOffset . UtcNow . Month , dayNum , referenceDate . Hour , referenceDate . Minute , referenceDate . Second , referenceDate . Offset ) ;
//TODO: вот это повторяется от метода к методу
//Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
if ( calcDay < DateTimeOffset . UtcNow )
calcDay = GetNextDateMonthly ( values , calcDay . AddMonths ( 1 ) ) ;
return calcDay ;
}
private DateTimeOffset GetNextDateMonthly2 ( List < EsppScheduleValDto > values , DateTimeOffset referenceDate )
{
if ( ! orderDict . TryGetValue ( values [ 0 ] . Value . Value , out int orderNum ) )
{
logger . LogError ( "Неизвестное значение порядка дня: {Value}" , values [ 0 ] . Value . Value ) ;
throw new ArgumentException ( $"Неизвестное значение: {values[0].Value.Value}" ) ;
}
if ( ! dwDict . TryGetValue ( values [ 1 ] . Value . Value , out int dwNum ) )
{
logger . LogError ( "Неизвестное значение дня недели: {Value}" , values [ 1 ] . Value . Value ) ;
throw new ArgumentException ( $"Неизвестное значение: {values[1].Value.Value}" ) ;
}
var startDay = referenceDate < DateTimeOffset . UtcNow
? new DateTimeOffset ( DateTimeOffset . UtcNow . Year , DateTimeOffset . UtcNow . Month , 1 , referenceDate . Hour , referenceDate . Minute , referenceDate . Second , referenceDate . Offset )
: new DateTimeOffset ( referenceDate . Year , referenceDate . Month , 1 , referenceDate . Hour , referenceDate . Minute , referenceDate . Second , referenceDate . Offset ) ;
var daysList = FindAllDaysInMonth ( dwNum , startDay ) ;
DateTimeOffset calcDay ;
if ( orderNum = = 5 ) // Последний
{
calcDay = daysList . Last ( ) ;
}
else
{
if ( orderNum > daysList . Count )
{
logger . LogError ( "Запрошенный порядковый день недели ({OrderNum}) не существует в месяце {Month}/{Year}. Найдено только {Count} дней." , orderNum , startDay . Month , startDay . Year , daysList . Count ) ;
throw new InvalidOperationException ( $"Запрошенный порядковый день недели ({orderNum}) не существует в месяце {startDay.Month}/{startDay.Year}. Найдено только {daysList.Count} дней." ) ;
}
else
calcDay = daysList [ orderNum - 1 ] ;
}
// Если дата <= текущей, переходим на следующий месяц
if ( calcDay < = DateTimeOffset . UtcNow )
return GetNextDateMonthly2 ( values , startDay . AddMonths ( 1 ) ) ;
return calcDay ;
}
private DateTimeOffset GetNextDateAnnually ( List < EsppScheduleValDto > values , DateTimeOffset referenceDate )
{
if ( ! monthDict . TryGetValue ( values [ 0 ] . Value . Value , out int monthNum ) )
{
logger . LogError ( "Неизвестное значение номера месяца: {Value}" , values [ 0 ] . Value . Value ) ;
throw new ArgumentException ( $"Неизвестное значение: {values[0].Value.Value}" ) ;
}
if ( ! int . TryParse ( values [ 1 ] . Value . Value , out int dayNum ) )
{
logger . LogError ( "Неизвестное значение дня месяца: {Value}" , values [ 0 ] . Value . Value ) ;
throw new ArgumentException ( $"Неизвестное значение: {values[0].Value.Value}" ) ;
}
var year = ( referenceDate . Year < DateTimeOffset . UtcNow . Year ) ? DateTimeOffset . UtcNow . Year : referenceDate . Year ;
var calcDay = new DateTimeOffset (
year , monthNum , dayNum ,
referenceDate . Hour , referenceDate . Minute , referenceDate . Second , referenceDate . Offset ) ;
//TODO: вот это повторяется от метода к методу
//Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
if ( calcDay < DateTimeOffset . UtcNow )
calcDay = GetNextDateAnnually ( values , calcDay . AddYears ( 1 ) ) ;
return calcDay ;
}
private DateTimeOffset GetNextDateAnnually2 ( List < EsppScheduleValDto > values , DateTimeOffset referenceDate )
{
var monthADict = new Dictionary < string , int >
{
{ "Января" , 1 } ,
{ "Февраля" , 2 } ,
{ "Марта" , 3 } ,
{ "Апреля" , 4 } ,
{ "Мая" , 5 } ,
{ "Июня" , 6 } ,
{ "Июля" , 7 } ,
{ "Августа" , 8 } ,
{ "Сентября" , 9 } ,
{ "Октября" , 10 } ,
{ "Ноября" , 11 } ,
{ "Декабря" , 12 }
} ;
if ( ! monthADict . TryGetValue ( values [ 2 ] . Value . Value , out int monthNum ) )
{
logger . LogError ( "Неизвестное значение номера месяца: {Value}" , values [ 2 ] . Value . Value ) ;
throw new ArgumentException ( $"Неизвестное значение: {values[2].Value.Value}" ) ;
}
if ( ! orderDict . TryGetValue ( values [ 0 ] . Value . Value , out int orderNum ) )
{
logger . LogError ( "Неизвестное значение порядка дня: {Value}" , values [ 0 ] . Value . Value ) ;
throw new ArgumentException ( $"Неизвестное значение: {values[0].Value.Value}" ) ;
}
if ( ! dwDict . TryGetValue ( values [ 1 ] . Value . Value , out int dwNum ) )
{
logger . LogError ( "Неизвестное значение дня недели: {Value}" , values [ 1 ] . Value . Value ) ;
throw new ArgumentException ( $"Неизвестное значение: {values[1].Value.Value}" ) ;
}
var year = ( referenceDate . Year < DateTimeOffset . UtcNow . Year ) ? DateTimeOffset . UtcNow . Year : referenceDate . Year ;
if ( year = = DateTimeOffset . UtcNow . Year & & referenceDate . Month < DateTimeOffset . UtcNow . Month )
year + = 1 ;
var calcDay = new DateTimeOffset (
year , monthNum , 1 ,
referenceDate . Hour , referenceDate . Minute , referenceDate . Second , referenceDate . Offset ) ;
List < DateTimeOffset > daysList = FindAllDaysInMonth ( dwNum , calcDay ) ;
if ( orderNum = = 5 ) //5-последний
calcDay = daysList . Last ( ) ;
else
calcDay = daysList [ orderNum - 1 ] ;
//TODO: вот это повторяется от метода к методу
//Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
if ( calcDay < DateTimeOffset . UtcNow )
calcDay = GetNextDateAnnually2 ( values , calcDay . AddYears ( 1 ) ) ;
return calcDay ;
}
private List < DateTimeOffset > FindAllDaysInMonth ( int dwNum , DateTimeOffset startDay )
{
var calcDay = startDay ;
if ( ( ( int ) calcDay . DayOfWeek ) ! = dwNum )
calcDay = OffsetToDayOfWeek ( dwNum , calcDay ) ;
var daysList = new List < DateTimeOffset > ( ) ;
var monthNum = calcDay . Month ;
while ( calcDay . Month = = monthNum )
{
daysList . Add ( calcDay ) ;
calcDay = calcDay . AddDays ( 7 ) ;
}
return daysList ;
}
private DateTimeOffset OffsetToDayOfWeek ( int dwNum , DateTimeOffset calcDay )
{
var daysOffset = dwNum - ( ( int ) calcDay . DayOfWeek ) ;
if ( daysOffset < 0 )
daysOffset = 7 + daysOffset ;
return calcDay . AddDays ( daysOffset ) ;
}
//public DateTimeOffset GetNextDateForDistributionRun(DateTimeOffset lastRun, DistributionPeriodTypeEnum periodType, string distributionPeriod)
//{
// var nextRun = lastRun;
// switch (periodType)
// {
// case (DistributionPeriodTypeEnum.Day):
// nextRun = lastRun.AddDays(ParseInt(distributionPeriod));
// break;
// case (DistributionPeriodTypeEnum.Month):
// nextRun = lastRun.AddMonths(ParseInt(distributionPeriod));
// break;
// case (DistributionPeriodTypeEnum.Year):
// nextRun = lastRun.AddYears(ParseInt(distributionPeriod));
// break;
// }
// return nextRunModifierService.GetWorkDayAsync(nextRun).GetAwaiter().GetResult();
//}
//private DateTimeOffset GetPrevDateForDistributionRun(DateTimeOffset lastRun, DistributionPeriodTypeEnum periodType, string distributionPeriod)
//{
// switch (periodType)
// {
// case (DistributionPeriodTypeEnum.Day):
// return lastRun.AddDays(-ParseInt(distributionPeriod));
// case (DistributionPeriodTypeEnum.Month):
// return lastRun.AddMonths(-ParseInt(distributionPeriod));
// case (DistributionPeriodTypeEnum.Year):
// return lastRun.AddYears(-ParseInt(distributionPeriod));
// }
// return lastRun;
//}
//public async Task<DateOnly> GetStartPeriodForDateAsync(Guid jobGroupId, DateTimeOffset date, DateTimeOffset referenceDate, DistributionPeriodTypeEnum periodType, string distributionPeriod)
//{
// var esppSchedule = await GetEsppScheduleAsync(jobGroupId);
// var currentStartPeriod = referenceDate;
// var currentEndPeriod = GetNextDateForDistributionRun(currentStartPeriod, periodType, distributionPeriod);
// while (!(date >= currentStartPeriod && date < currentEndPeriod))
// {
// if (date > currentEndPeriod)
// {
// currentStartPeriod = currentEndPeriod;
// currentEndPeriod = GetNextDateForDistributionRun(currentStartPeriod, periodType, distributionPeriod);
// }
// else
// {
// currentEndPeriod = currentStartPeriod;
// currentStartPeriod = GetPrevDateForDistributionRun(currentStartPeriod, periodType, distributionPeriod);
// }
// }
// return DateOnly.FromDateTime(currentStartPeriod.DateTime);
//}
//private int ParseInt(string value)
//{
// try
// {
// return int.Parse(value);
// }
// catch (Exception ex)
// {
// logger.LogError(ex, $"{nameof(this.GetType)}, получение конца периода. Н е смог распарсить string в int для значения {value}.");
// return 0;
// }
//}
//private DistributionPeriodTypeEnum ParseDistributionPeriodType(string value)
//{
// var result = (DistributionPeriodTypeEnum)Enum.Parse(typeof(DistributionPeriodTypeEnum), value);
// return result;
//}
}
}