Files
parr_api/PARR.EsppScheduleSync/ScheduleSyncher.cs

566 lines
28 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.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PARR.BLL.Helpers;
using PARR.BLL.Services.Interfaces;
using PARR.DAL.Contracts;
using PARR.DAL.Models;
using PARR.DAL.Services.Interfaces;
using PARR.DAL.TransformServices;
using PARR.EsppScheduleSync.Domain;
using PARR.EsppScheduleSync.Settings;
using PARR.EsppSync;
namespace PARR.EsppScheduleSync
{
internal class ScheduleSyncher : IScheduleSyncher
{
private readonly ILogger<ScheduleSyncher> logger;
private readonly GlobalSettings globalSettings;
private readonly IMqService mqService;
private readonly ISyncService<EsppObjectSchedule> syncService;
private readonly SettingsFromDb settingsFromDb;
private readonly IServiceProvider serviceProvider;
//private readonly INextRunModifierService nextRunModifierService;
public ScheduleSyncher(
ILogger<ScheduleSyncher> logger,
GlobalSettings globalSettings,
IMqService mqService,
ISyncService<EsppObjectSchedule> syncService,
SettingsFromDb settingsFromDb,
IServiceProvider serviceProvider
//INextRunModifierService nextRunModifierService
)
{
this.logger = logger;
this.globalSettings = globalSettings;
this.mqService = mqService;
this.syncService = syncService;
this.settingsFromDb = settingsFromDb;
this.serviceProvider = serviceProvider;
//this.nextRunModifierService = nextRunModifierService;
if (globalSettings.MqSettings == null)
{
logger.LogError("Нет секции настроек хранилища. MqSettings, EsppTemplates");
throw new Exception("Нет секции настроек хранилища. MqSettings, EsppTemplates");
}
}
public async Task StartAsync()
{
var isConnected = await mqService.InitConsumerAsync(globalSettings!.MqSettings!, SyncScheduleAsync);
if (!isConnected)
throw new Exception("Ошибка при подключении к RabbitMq");
logger.LogInformation($"Запущена проверка очереди {globalSettings.MqSettings!.QueueName}.");
}
public async Task StopAsync()
{
await mqService.DisposeAsync();
logger.LogInformation($"=== === === Соединение с очередью {globalSettings.MqSettings!.QueueName} закрыто === === ===");
}
private async Task SyncScheduleAsync(string str)
{
const int expectedParts = 24;
var separator = globalSettings.ParsingSeparator;
var parts = str.Split(separator);
if (parts.Length != expectedParts)
{
logger.LogWarning("Некорректное количество полей в строке расписания: {Actual} (ожидается {Expected})", parts.Length, expectedParts);
await syncService.SyncEsppObjectAsync(str, ParseStrToEsppObject, ConvertDbObjToEsppObj);
return;
}
var esppScheduleId = parts[0]; // ScheduleEsppId из ЕСПП
var templateNameRaw = parts[6];
var templateName = templateNameRaw.ToUpper();
// Проверка префикса (как в ParseStrToEsppObject)
if (string.IsNullOrEmpty(settingsFromDb.TemplatePrefixWithoutVariable) ||
!templateName.Contains(settingsFromDb.TemplatePrefixWithoutVariable))
{
logger.LogWarning("Имя шаблона '{TemplateName}' не соответствует префиксу '{Prefix}'. Пропущено.", templateName, settingsFromDb.TemplatePrefixWithoutVariable);
await syncService.SyncEsppObjectAsync(str, ParseStrToEsppObject, ConvertDbObjToEsppObj);
return;
}
// === ЕДИНЫЙ ЗАПРОС: ищем по имени ИЛИ по ScheduleEsppId ===
using var scope = serviceProvider.CreateScope();
var templateService = scope.ServiceProvider.GetRequiredService<ITemplateService>();
var candidates = await templateService.Get()
.Where(t => t.Name == templateName || t.ScheduleEsppId == esppScheduleId)
.ToListAsync();
var templateByName = candidates.FirstOrDefault(t => t.Name == templateName);
var templateByScheduleId = candidates.FirstOrDefault(t => t.ScheduleEsppId == esppScheduleId);
if (templateByName == null && templateByScheduleId == null)
{
// Нет ни по имени, ни по ID
logger.LogWarning(
"Расписание из ЕСПП не привязано ни к одному шаблону: TemplateName='{TemplateName}', ScheduleEsppId='{EsppId}'",
templateName,
esppScheduleId
);
}
else if (templateByName == null && templateByScheduleId != null)
{
// Есть только по ID → имя не совпадает
logger.LogWarning(
"Расхождение привязки: расписание из ЕСПП с ScheduleEsppId='{EsppId}' и TemplateName='{TemplateName}' " +
"соответствует шаблону в БД с именем '{DbTemplateName}', Id='{TemplateId}'.",
esppScheduleId,
templateName,
templateByScheduleId.Name,
templateByScheduleId.Id
);
}
else if (templateByName != null)
{
var dbScheduleId = templateByName.ScheduleEsppId ?? string.Empty;
if (string.IsNullOrEmpty(dbScheduleId))
{
// Утерян ID в БД
logger.LogWarning(
"У шаблона '{TemplateName}' (Id='{TemplateId}') отсутствует ScheduleEsppId в БД, но в ЕСПП он равен '{EsppId}'",
templateName,
templateByName.Id,
esppScheduleId
);
}
else if (dbScheduleId != esppScheduleId)
{
// ID не совпадают — проверяем, не занят ли esppScheduleId другим шаблоном
var conflictingTemplate = candidates.FirstOrDefault(t =>
t.Id != templateByName.Id && t.ScheduleEsppId == esppScheduleId);
if (conflictingTemplate != null)
{
logger.LogWarning(
"Конфликт ScheduleEsppId: расписание '{EsppId}' из ЕСПП с именем '{TemplateName}' " +
"уже привязано к другому шаблону '{OtherTemplateName}' (Id='{OtherTemplateId}') в БД.",
esppScheduleId,
templateName,
conflictingTemplate.Name,
conflictingTemplate.Id
);
}
else
{
logger.LogWarning(
"Несовпадение ScheduleEsppId для шаблона '{TemplateName}' (Id='{TemplateId}'): в БД='{DbId}', в ЕСПП='{EsppId}'",
templateName,
templateByName.Id,
dbScheduleId,
esppScheduleId
);
}
}
}
// Передаём оригинальную строку в стандартный синхронизатор
await syncService.SyncEsppObjectAsync(str, ParseStrToEsppObject, ConvertDbObjToEsppObj);
}
/// <summary>
/// Преобразование модели БД в модель для сравнения
/// </summary>
/// <param name="template"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
private EsppObjectSchedule ConvertDbObjToEsppObj(Template template)
{
//дефолтное значение, изменится в сервисе nextRunModifierService
var nextRunByAccountRobotTimeZone = DateTimeOffset.MinValue;
using (var scope = serviceProvider.CreateScope())
{
var nextRunModifierService = scope.ServiceProvider.GetService<INextRunModifierService>();
if (nextRunModifierService == null)
throw new Exception($"Не найден сервис: {nameof(INextRunModifierService)}");
nextRunByAccountRobotTimeZone = nextRunModifierService.GetNextRunByAccountRobotTimeZone(template.NextRun);
}
var esppObjectFromDb = new EsppObjectSchedule
{
TemplateName = template.Name.ToUpper(),
Code = template.ScheduleEsppId ?? "",
ScheduleName = template.Name,
IsActive = template.IsActiveSchedule,
//ResponseArea = template.Host!.ResponseArea!.Name,//TODO Migration to job
//WorkGroup = template.Host!.WorkGroup!,
//WorkGroup = template.Host!.WorkGroup!.Name,//TODO Migration to job
//Мы решили, что для всех расписаний "Нет исключений", если что-то поменяется, тут нужно переделать
//TypeV60calendar = settingsFromDb.ScheduleExcludeType == "Нет исключений" ? "NONE" : "",
TypeV60calendar = GetTypeV60calendar(template),
V60calendar = GetV60calendar(template),
//Scheduled = EsppScheduleHelpers.GetNextRun(template.NextRun),
//Scheduled = EsppScheduleHelpers.GetNextRun(nextRunModifierService.GetNextRunByAccountRobotTimeZone(template.NextRun)),
//BasisTime = EsppScheduleHelpers.GetGenerationTime(nextRunModifierService.GetNextRunByAccountRobotTimeZone(template.NextRun)),
Scheduled = EsppScheduleHelpers.GetNextRun(nextRunByAccountRobotTimeZone),
BasisTime = EsppScheduleHelpers.GetGenerationTime(nextRunByAccountRobotTimeZone),
Timezone = settingsFromDb.ScheduleTimezone,
//Мы решили, что для всех расписаний "Отсутствует дата завершения", если что-то поменяется, тут нужно переделать
TerminationType = settingsFromDb.ScheduleRepeatRange == "Отсутствует дата завершения" ? "forever" : "",
CompleteAfter = ""
};
FillScheduleFromDb(template, ref esppObjectFromDb);
return ClearOptionalFields(esppObjectFromDb);
}
/// <summary>
/// Получить исключение - Календарь
/// </summary>
/// <param name="template"></param>
/// <returns></returns>
private string GetV60calendar(Template template)
{
// мы знаем, что у нас точно в шаблоне есть инклуды до ScheduleExcludeType и ScheduleExcludeTypeCalendar
if (template.Job?.Group?.ScheduleExcludeTypeCalendar == null)
{
logger.LogDebug("Для шаблона шаблона {templte}, {templateId} нет исключений календаря", template.Name, template.Id);
//TODO:!!!!!!!!!!! Вот тут null или string.Empty??? Спросить у Андрея что он нам вернет!
return string.Empty;
}
logger.LogDebug("Для шаблона шаблона {templte}, {templateId} установлено исключений календаря \"{name}\", EsppValue: {esppValue}", template.Name, template.Id, template.Job.Group.ScheduleExcludeTypeCalendar.Title, template.Job.Group.ScheduleExcludeTypeCalendar.EsppValue);
return template.Job.Group.ScheduleExcludeTypeCalendar.EsppValue;
}
/// <summary>
/// Получить тип календаря
/// </summary>
/// <returns></returns>
private string GetTypeV60calendar(Template template)
{
// мы знаем, что у нас точно в шаблоне есть инклуды до ScheduleExcludeType и ScheduleExcludeTypeCalendar
// на всякий конечно же проверим
if (template.Job?.Group?.ScheduleExcludeType == null)
{
logger.LogError("Для шаблона {templte}, {templateId} не смог получить тип исключения, установил значение по умолчанию \"Без исключения\"", template.Name, template.Id);
return "NONE";
}
logger.LogDebug("Для шаблона шаблона {templte}, {templateId} тип исключения \"{name}\", EsppValue: {esppValue}", template.Name, template.Id, template.Job.Group.ScheduleExcludeType.Title, template.Job.Group.ScheduleExcludeType.EsppValue);
return template.Job.Group.ScheduleExcludeType.EsppValue;
#region old logic
//// Это костыль, нужно придумать как это хранить в БД.
//switch (settingsFromDb.ScheduleExcludeType.ToLower())
//{
// case ("нет исключений"):
// return "NONE";
// case ("выполнить только в указанном календаре"):
// return "ONLY";
// default:
// return "";
//}
#endregion
}
/// <summary>
/// Заполнить расписание из БД
/// </summary>
/// <returns></returns>
private void FillScheduleFromDb(Template template, ref EsppObjectSchedule esppObject)
{
using (var scope = serviceProvider.CreateScope())
{
var esppSchTypeConfigService = scope.ServiceProvider.GetService<IEsppSchTypeConfigService>();
if (esppSchTypeConfigService == null)
throw new Exception($"Не найден сервис: {nameof(IEsppSchTypeConfigService)}");
var esppSchedule = esppSchTypeConfigService.GetEsppScheduleDto(template.Job!.GroupId);
if (esppSchedule == null)
{
logger.LogError($"Не смог получить расписание из БД для шаблона templateId: {template.Id}, {template.Name}");
return;
}
var typeSchedule = GetTypeScheduleByString(esppSchedule.TypeSchedule.Name);
if (!typeSchedule.HasValue)
return;
//тип повторения
esppObject.TypeSchedule = typeSchedule.Value;
//в зависимости от типа, присваиваем значения
switch (esppObject.TypeSchedule)
{
case EsppSchTypeScheduleEnum.Regularly:
esppObject.Interval = esppSchedule.Values.First(t => t.Order == 0).Value.EsppExportValue;
break;
case EsppSchTypeScheduleEnum.Weekly:
esppObject.Dayofweek = esppSchedule.Values.First(t => t.Order == 0).Value.EsppExportValue;
break;
case EsppSchTypeScheduleEnum.Monthly:
//если включено автораспределение, подставляем дату месяца из NextRun
//if (template.ApplicationsInWork?.IsAutoDistributionEnabled == true)//TODO Migration to job
//{
// var nextRunByRobotTimeZone = nextRunModifierService.GetNextRunByAccountRobotTimeZone(template.NextRun);
// esppObject.Dayofmonth = nextRunByRobotTimeZone.Day.ToString();
// // esppObject.Dayofmonth = template.NextRun.Day.ToString();
//}
//else
//{
esppObject.Dayofmonth = esppSchedule.Values.First(t => t.Order == 0).Value.EsppExportValue;
//}
break;
case EsppSchTypeScheduleEnum.Monthly2:
esppObject.Md1 = esppSchedule.Values.First(t => t.Order == 0).Value.EsppExportValue;
esppObject.Md2 = esppSchedule.Values.First(t => t.Order == 1).Value.EsppExportValue;
break;
case EsppSchTypeScheduleEnum.Annually:
esppObject.Annualm = esppSchedule.Values.First(t => t.Order == 0).Value.EsppExportValue;
esppObject.Annualday = esppSchedule.Values.First(t => t.Order == 1).Value.EsppExportValue;
break;
case EsppSchTypeScheduleEnum.Annually2:
esppObject.An1 = esppSchedule.Values.First(t => t.Order == 0).Value.EsppExportValue;
esppObject.An2 = esppSchedule.Values.First(t => t.Order == 1).Value.EsppExportValue;
esppObject.An3 = esppSchedule.Values.First(t => t.Order == 2).Value.EsppExportValue;
break;
}
}
}
/// <summary>
/// Парсинг из строки в модель для сравнения
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
private EsppObjectSchedule? ParseStrToEsppObject(string str)
{
var splittedContent = str.Split(globalSettings.ParsingSeparator);
if (splittedContent.Length != 24)
{
logger.LogError($"Входная строка после сплита не содержит 24 объекта (факт: {splittedContent.Length}).");
return null;
}
var templateName = splittedContent[6].ToUpper();
var scheduleName = splittedContent[2];
if (!IsValidName(templateName) || !IsValidName(scheduleName))
return null;
bool.TryParse(splittedContent[1].Trim(), out var isActive);
var typeSchedule = GetTypeScheduleByString(splittedContent[7]);
if (!typeSchedule.HasValue)
return null;
var esppObject = new EsppObjectSchedule
{
TemplateName = templateName,
Code = splittedContent[0],
ScheduleName = scheduleName,
IsActive = isActive,
ResponseArea = splittedContent[5],
WorkGroup = splittedContent[3],
TypeSchedule = typeSchedule.Value,
Interval = splittedContent[8],
Dayofweek = splittedContent[15],
Dayofmonth = splittedContent[14],
Md1 = splittedContent[16],
Md2 = splittedContent[17],
Annualm = splittedContent[13],
Annualday = splittedContent[12],
An1 = splittedContent[9],
An2 = splittedContent[10],
An3 = splittedContent[11],
TypeV60calendar = splittedContent[21],
Scheduled = splittedContent[4],
Timezone = splittedContent[18],
TerminationType = splittedContent[19],
CompleteAfter = splittedContent[20],
V60calendar = splittedContent[22],
BasisTime = splittedContent[23]
};
return ClearOptionalFields(esppObject);
}
/// <summary>
/// Преобразовать из строки в Тип Повторения
/// </summary>
/// <param name="typeSchedule"></param>
/// <returns></returns>
private EsppSchTypeScheduleEnum? GetTypeScheduleByString(string typeSchedule)
{
typeSchedule = typeSchedule.ToLower();
//Regularly, Регулярно (значение в ЕСПП и в БД не совпадают, в бд Regularly, в ЕСПП simple)
if (typeSchedule == "simple" || typeSchedule == EsppSchTypeScheduleEnum.Regularly.ToString().ToLower())
return EsppSchTypeScheduleEnum.Regularly;
if (typeSchedule == EsppSchTypeScheduleEnum.Weekly.ToString().ToLower())
return EsppSchTypeScheduleEnum.Weekly;
if (typeSchedule == EsppSchTypeScheduleEnum.Monthly.ToString().ToLower())
return EsppSchTypeScheduleEnum.Monthly;
if (typeSchedule == EsppSchTypeScheduleEnum.Monthly2.ToString().ToLower())
return EsppSchTypeScheduleEnum.Monthly2;
if (typeSchedule == EsppSchTypeScheduleEnum.Annually.ToString().ToLower())
return EsppSchTypeScheduleEnum.Annually;
if (typeSchedule == EsppSchTypeScheduleEnum.Annually2.ToString().ToLower())
return EsppSchTypeScheduleEnum.Annually2;
logger.LogError($"Не смог преобразовать Тип повторения из ЕСПП в EsppSchTypeScheduleEnum. Получено значение {typeSchedule}");
return null;
}
/// <summary>
/// Проверка имени шаблона и расписание на соответствие префиксу из настроек
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
private bool IsValidName(string name)
{
var currentCulture = Thread.CurrentThread.CurrentCulture;
//if (!string.IsNullOrEmpty(settingsFromDb.TemplatePrefixName) && name.StartsWith(settingsFromDb.TemplatePrefixName, true, currentCulture))
// return true;
if (!string.IsNullOrEmpty(settingsFromDb.TemplatePrefixWithoutVariable) && name.Contains(settingsFromDb.TemplatePrefixWithoutVariable)) // , StringComparison.CurrentCultureIgnoreCase
return true;
logger.LogWarning($"Имя шаблона или расписания не соответствует обязательному префиксу({settingsFromDb.TemplatePrefixWithoutVariable}). {name} игнорирован");
return false;
}
/// <summary>
/// Очистка полей которые не нуждаются в синхронизации
/// </summary>
/// <param name="esppObject"></param>
/// <returns></returns>
private EsppObjectSchedule ClearOptionalFields(EsppObjectSchedule esppObject)
{
esppObject.Code = string.Empty;
esppObject.ScheduleName = string.Empty;
esppObject.ResponseArea = string.Empty;
esppObject.WorkGroup = string.Empty;
esppObject.CompleteAfter = string.Empty;
//esppObject.V60calendar = string.Empty;
// В ЕСПП, при изменении "Повторять задачу", остаются предыдущие значения, их не нужно синхронизировать (касается только данных полученных из ЕСПП, в БД все ок)
// т.е. если стояло Ежедненвно:понедельник, а изменили например на Еженедельно..., то в ежедневно значения останутся, но будут отрабатывать значения из Еженедельно.
// Т е значения из Ежедненвно проверять не нужно, вот их и будем очищать
switch (esppObject.TypeSchedule)
{
case EsppSchTypeScheduleEnum.Regularly:
//esppObject.Interval = string.Empty;
esppObject.Dayofweek = string.Empty;
esppObject.Dayofmonth = string.Empty;
esppObject.Md1 = string.Empty;
esppObject.Md2 = string.Empty;
esppObject.Annualm = string.Empty;
esppObject.Annualday = string.Empty;
esppObject.An1 = string.Empty;
esppObject.An2 = string.Empty;
esppObject.An3 = string.Empty;
break;
case EsppSchTypeScheduleEnum.Weekly:
esppObject.Interval = string.Empty;
//esppObject.Dayofweek = string.Empty;
esppObject.Dayofmonth = string.Empty;
esppObject.Md1 = string.Empty;
esppObject.Md2 = string.Empty;
esppObject.Annualm = string.Empty;
esppObject.Annualday = string.Empty;
esppObject.An1 = string.Empty;
esppObject.An2 = string.Empty;
esppObject.An3 = string.Empty;
break;
case EsppSchTypeScheduleEnum.Monthly:
esppObject.Interval = string.Empty;
esppObject.Dayofweek = string.Empty;
//esppObject.Dayofmonth = string.Empty;
esppObject.Md1 = string.Empty;
esppObject.Md2 = string.Empty;
esppObject.Annualm = string.Empty;
esppObject.Annualday = string.Empty;
esppObject.An1 = string.Empty;
esppObject.An2 = string.Empty;
esppObject.An3 = string.Empty;
break;
case EsppSchTypeScheduleEnum.Monthly2:
esppObject.Interval = string.Empty;
esppObject.Dayofweek = string.Empty;
esppObject.Dayofmonth = string.Empty;
//esppObject.Md1 = string.Empty;
//esppObject.Md2 = string.Empty;
esppObject.Annualm = string.Empty;
esppObject.Annualday = string.Empty;
esppObject.An1 = string.Empty;
esppObject.An2 = string.Empty;
esppObject.An3 = string.Empty;
break;
case EsppSchTypeScheduleEnum.Annually:
esppObject.Interval = string.Empty;
esppObject.Dayofweek = string.Empty;
esppObject.Dayofmonth = string.Empty;
esppObject.Md1 = string.Empty;
esppObject.Md2 = string.Empty;
//esppObject.Annualm = string.Empty;
//esppObject.Annualday = string.Empty;
esppObject.An1 = string.Empty;
esppObject.An2 = string.Empty;
esppObject.An3 = string.Empty;
break;
case EsppSchTypeScheduleEnum.Annually2:
esppObject.Interval = string.Empty;
esppObject.Dayofweek = string.Empty;
esppObject.Dayofmonth = string.Empty;
esppObject.Md1 = string.Empty;
esppObject.Md2 = string.Empty;
esppObject.Annualm = string.Empty;
esppObject.Annualday = string.Empty;
//esppObject.An1 = string.Empty;
//esppObject.An2 = string.Empty;
//esppObject.An3 = string.Empty;
break;
}
return esppObject;
}
}
}