Files
parr_api/PARR.DAL/NextRunServices/Subservices/EsppScheduleTransformService.cs

452 lines
22 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Microsoft.Extensions.Logging;
using PARR.DAL.Contracts;
using PARR.DAL.DomainModels;
using PARR.DAL.Services.Interfaces;
using System.Text.Json;
namespace PARR.DAL.NextRunServices.Subservices
{
internal class EsppScheduleTransformService : IEsppScheduleTransformService
{
private readonly IEsppSchTypeConfigService esppSchTypeConfigService;
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
)
{
this.esppSchTypeConfigService = esppSchTypeConfigService;
this.logger = logger;
}
public async Task<DateTimeOffset> GetNextDateAsync(Guid jobGroupId, DateTimeOffset referenceDate, TimeSpan? offset = null)
{
var esppSchedule = await GetEsppScheduleAsync(jobGroupId);
return GetNextDate(esppSchedule, referenceDate, offset);
}
private DateTimeOffset GetNextDate(EsppScheduleDto esppSchedule, DateTimeOffset referenceDate, TimeSpan? offset)
{
//offset - желательно передавать всегда, хотябы МСК, чтобы рассчитывались правильно даты, так как в ЕСПП все в МСК
logger.LogDebug("Начинаю рассчет nextRun по расписанию ЕСПП. Входные данные: date: {referenceDate}, offset: {offset}, esppSchedule: {esppSchedule}", referenceDate, offset, JsonSerializer.Serialize(esppSchedule));
var nextRun = referenceDate;
//var finalOffset = new TimeSpan(3, 0, 0) + (offset ?? TimeSpan.Zero);
var finalOffset = offset ?? TimeSpan.Zero;
var offsetReferenceDate = referenceDate.ToOffset(finalOffset);
logger.LogDebug("Дата после добавления offset - {offsetReferenceDate}", offsetReferenceDate);
////---------
//// Для рассчета по МСК времени, потому что в ЮТС может быть еще ВСК, а по МСК это уже ПНД
//var offsetReferenceDate = referenceDate.ToOffset(new TimeSpan(3, 0, 0));
////---------
//// может быть offset ,это работает когда смещение для УЗ ЕСПП и применение ЗО РГ
//if (offset.HasValue)
// offsetReferenceDate = offsetReferenceDate.Off(offsetReferenceDate.Offset + offset);
// //offsetReferenceDate = offsetReferenceDate.Add(offset.Value);
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;
}
logger.LogDebug("Рассчитанная дата, с учетом offset: {nextRun}", nextRun);
// Возвращаем обратно в ЮТС
nextRun = nextRun.ToOffset(TimeSpan.Zero);
logger.LogDebug("Рассчитанная дата с нулевым offset: {nextRun}", nextRun);
return nextRun;
}
/// <summary>
/// Получить расписание из БД
/// </summary>
/// <param name="jobGroupId"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
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)
{
var nowInTargetTz = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
calcDay = new DateTimeOffset(
nowInTargetTz.Year,
//DateTimeOffset.UtcNow.Year,
nowInTargetTz.Month,
//DateTimeOffset.UtcNow.Month,
nowInTargetTz.Day,
//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)
{
var nowInTargetTz = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
calcDay = new DateTimeOffset(
nowInTargetTz.Year,
nowInTargetTz.Month,
dayNum,
referenceDate.Hour,
referenceDate.Minute,
referenceDate.Second,
referenceDate.Offset
);
//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 startDay = referenceDate < DateTimeOffset.UtcNow
? new DateTimeOffset(
DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset).Year,
DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset).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 nowInTargetTz = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
var year = (referenceDate.Year < nowInTargetTz.Year)
? nowInTargetTz.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;
// сравниваем года в одном часовом поясе
var nowInTargetTz = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
var year = (referenceDate.Year < nowInTargetTz.Year)
? nowInTargetTz.Year
: referenceDate.Year;
//if (year == DateTimeOffset.UtcNow.Year && referenceDate.Month < DateTimeOffset.UtcNow.Month)
if (year == nowInTargetTz.Year && referenceDate.Month < nowInTargetTz.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);
}
}
}