519 lines
23 KiB
C#
519 lines
23 KiB
C#
using Microsoft.Extensions.Logging;
|
||
using PARR.Constants;
|
||
using PARR.DAL.Contracts;
|
||
using PARR.DAL.DomainModels;
|
||
using PARR.DAL.Extensions;
|
||
using PARR.DAL.Services.Interfaces;
|
||
|
||
namespace PARR.DAL.TransformServices
|
||
{
|
||
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;
|
||
//}
|
||
|
||
}
|
||
}
|