using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using PARR.Constants; using PARR.DAL.DomainServices.Shortcodes; using PARR.DAL.Models; using PARR.DAL.Models.Job; using PARR.DAL.NextRunServices.Models; using PARR.DAL.NextRunServices.Subservices; using PARR.DAL.Services.Interfaces; using PARR.DAL.Services.Interfaces.Job; namespace PARR.DAL.NextRunServices { internal class NextRunService : INextRunService { private readonly ILogger logger; private readonly ITemplateService templateService; private readonly IJobGroupService jobGroupService; private readonly IEsppScheduleTransformService esppScheduleTransformService; private readonly ITemplateDistributor templateDistributor; private readonly IShortcodesService shortcodesService; public NextRunService( ILogger logger, ITemplateService templateService, IJobGroupService jobGroupService, IEsppScheduleTransformService esppScheduleTransformService, ITemplateDistributor templateDistributor, IShortcodesService shortcodesService ) { this.logger = logger; this.templateService = templateService; this.jobGroupService = jobGroupService; this.esppScheduleTransformService = esppScheduleTransformService; this.templateDistributor = templateDistributor; this.shortcodesService = shortcodesService; } public async Task?> GetNextRunForJobGroupWithAutoDistributionAsync(Guid jobGroupId) { logger.LogInformation("Начинаю распределять шаблоны для группы работ {gobGroupId}", jobGroupId); var jobGroup = await jobGroupService.Get() .Include(t => t.DistributionConfig) .ThenInclude(t => t.DistributionPeriod) .AsNoTracking() .FirstOrDefaultAsync(t => t.Id == jobGroupId); if (jobGroup == null) { logger.LogError("Не найдена группа работа с id: {id}", jobGroupId); return null; } if (!jobGroup.IsAutoDistributionEnabled || jobGroup.DistributionConfig == null) { logger.LogError("Группа работ id: {id} не подходит для автораспределения, у нее или отсутствуют настройки или не включено автораспределение. IsAutoDistributionEnabled: {IsAutoDistributionEnabled}. Есть конфиг: {DistributionConfig}", jobGroupId, jobGroup.IsAutoDistributionEnabled, jobGroup.DistributionConfig != null); return null; } logger.LogInformation("Параметры распределения. groupId: {groupId}, name: {groupName}, referenceDate: {referenceDate}, " + "distributionPeriodName: {distributionPeriodName}, distributionPeriodDuration: {distributionPeriodDuration}, " + "distributionPeriodType: {distributionPeriodType}, IsExcludeWeekends: {IsExcludeWeekends}, IsGroupingByWorkGroup: {IsGroupingByWorkGroup}", jobGroupId, jobGroup.GroupName, jobGroup.ReferenceDate, jobGroup.DistributionConfig.DistributionPeriod.Name, jobGroup.DistributionConfig.DistributionPeriod.Duration, jobGroup.DistributionConfig.DistributionPeriod.Type, jobGroup.DistributionConfig.IsExcludeWeekends, jobGroup.DistributionConfig.IsGroupingByWorkGroup ); var duration = GetDurationDays(jobGroup.DistributionConfig); var dateStart = GetDateStart(jobGroup.ReferenceDate); //получаем шаблоны только в статусе Used var allTemplates = await templateService.Get() .Include(t => t.Job) .Where(t => t.Job!.GroupId == jobGroupId && t.StatusTypeId == TemplateStatusTypeEnum.Used) .AsNoTracking() .ToListAsync(); if (!allTemplates.Any()) { logger.LogInformation("В группе c ИД {jobGroupId} отсутствуют шаблоны в статусе Used", jobGroupId); return null; } logger.LogDebug("Всего шаблонов для распределения в статусе Used: {count} шт.", allTemplates.Count); var result = new List(); if (jobGroup.DistributionConfig.IsGroupingByWorkGroup) { // Нужно группировать по РГ // список шаблонов с полученной из шорткода РГ var templatesWithWorkGroup = new List(); foreach (var template in allTemplates) { var workGroupName = await shortcodesService.ApplyShortcodesAsync(template.Job!.WorkGroupMask, template); templatesWithWorkGroup.Add(new TemplateWithWorkGroupDto(template.Id, template.NextRun, workGroupName)); } var grouping = templatesWithWorkGroup.GroupBy(t => t.WorkGroup); //распределяем foreach (var workGroupTemplates in grouping) { logger.LogInformation("Начинаю распределять шаблоны для рабочей группы: {workGroup}. Всего шаблонов: {countTemplates} шт.", workGroupTemplates.Key, workGroupTemplates.Count()); var templatesForDistribute = workGroupTemplates.Select(t => new TemplateNextRunDto(t.Id, t.NextRun)).ToList(); var distributedResult = await templateDistributor.DistributeTemplatesAsync(dateStart, duration, jobGroup.ReferenceDate, templatesForDistribute, jobGroup.DistributionConfig.IsExcludeWeekends); result.AddRange(distributedResult); } } else { // Не нужна группировка по РГ // сразу распределяем все шаблоны var templatesForDistribute = allTemplates.Select(t => new TemplateNextRunDto(t.Id, t.NextRun)).ToList(); result = await templateDistributor.DistributeTemplatesAsync(dateStart, duration, jobGroup.ReferenceDate, templatesForDistribute, jobGroup.DistributionConfig.IsExcludeWeekends); } logger.LogInformation("Завершено распределение шаблонов для группы {jobGroupId}. Всего распределено шаблонов: {count} шт.", jobGroupId, result.Count); return result; } public async Task GetNextRunForJobGroupWithEsppSchedulleAsync(Guid jobGroupId) { var jobGroup = await jobGroupService.Get().AsNoTracking().FirstOrDefaultAsync(t => t.Id == jobGroupId); if (jobGroup == null) { logger.LogError("Не найдена группа работ с Id: {jobGroupId}.", jobGroupId); throw new ArgumentNullException(nameof(jobGroupId), $"Не найдена группа работ с Id: {jobGroupId}"); } // Берем из обычного расписания ЕСПП return await esppScheduleTransformService.GetNextDateAsync(jobGroupId, jobGroup.ReferenceDate); } public async Task GetNextRunForNewTemplateAsync(Guid jobGroupId) { // определяю это автораспределение или нет, вызываю соответствующий рассчет var jobGroup = await jobGroupService.Get() .Include(t => t.DistributionConfig) .ThenInclude(t => t.DistributionPeriod) .AsNoTracking() .FirstOrDefaultAsync(t => t.Id == jobGroupId); if (jobGroup == null) { logger.LogError("Не найдена группа работ с Id: {jobGroupId}.", jobGroupId); throw new ArgumentNullException(nameof(jobGroupId), $"Не найдена группа работ с Id: {jobGroupId}"); } if (jobGroup.IsAutoDistributionEnabled) { // тут автораспределение if (jobGroup.DistributionConfig == null) { logger.LogError("Для группы работ {jobGroupId}, указано автораспределение, но отсутствует конфиг в таблице {GroupDistributionConfigs}", jobGroupId, nameof(JobGroupDistributionConfig)); throw new ArgumentNullException(nameof(JobGroupDistributionConfig), $"Для jobGroupId: {jobGroupId} отсутствует конфигурация автораспределения в таблице {nameof(JobGroupDistributionConfig)}"); } #region Вынести в одтельный метод, почти все повторяется var dateStart = GetDateStart(jobGroup.ReferenceDate); var duration = GetDurationDays(jobGroup.DistributionConfig); var result = await templateDistributor.GetValidNextRunForTemplateAsync(dateStart, duration, jobGroup.ReferenceDate, new TemplateNextRunDto(Guid.Empty, null), //TODO: тут пока не получаем список шаблонов, но позже, когда будем строить каждый раз план, нужно будет сюда передавать список связанных шаблонов new List(), jobGroup.DistributionConfig.IsExcludeWeekends, isNew: true ); #endregion logger.LogDebug("Получил nextRun {nextRun} по jobGroupId {jobGroupId} для нового шаблона, тип расписания: автораспределение", result.NextRun, jobGroupId); return result.NextRun; } else { // тут расписание ЕСПП var nextRun = await esppScheduleTransformService.GetNextDateAsync(jobGroup.Id, jobGroup.ReferenceDate); logger.LogDebug("Получил nextRun {nextRun} по jobGroupId {jobGroupId}, тип расписания ЕСПП", nextRun, jobGroupId); return nextRun; } } public async Task GetNextRunForTemplateAsync(Guid templateId, bool isNew) { var template = await templateService.Get() .Include(t => t.Job) .ThenInclude(t => t.Group).ThenInclude(t => t.DistributionConfig).ThenInclude(t => t.DistributionPeriod) .AsNoTracking() .FirstOrDefaultAsync(t => t.Id == templateId); if (template == null) { logger.LogError("Не найден шаблон с Id: {templateId}.", templateId); throw new ArgumentNullException(nameof(templateId), $"Не найден шаблон с Id: {templateId}"); } if (template.Job!.Group!.IsAutoDistributionEnabled == true) { // включено автораспределение if (template.Job!.Group.DistributionConfig == null) { logger.LogError("Для шаблона {templateId}, jobGroupId {jobGroupId}, указано автораспределение, но отсутствует конфиг в таблице {GroupDistributionConfigs}", template.Id, template.Job.GroupId, nameof(JobGroupDistributionConfig)); throw new ArgumentNullException(nameof(JobGroupDistributionConfig), $"Для jobGroupId: {template.Job.GroupId} отсутствует конфигурация автораспределения в таблице {nameof(JobGroupDistributionConfig)}"); } #region Вынести в одтельный метод, почти все повторяется var dateStart = GetDateStart(template.Job!.Group.ReferenceDate); var duration = GetDurationDays(template.Job!.Group.DistributionConfig); var result = await templateDistributor.GetValidNextRunForTemplateAsync(dateStart, duration, template.Job.Group.ReferenceDate, new TemplateNextRunDto(template.Id, template.NextRun), //TODO: тут пока не получаем список шаблонов, но позже, когда будем строить каждый раз план, нужно будет сюда передавать список связанных шаблонов new List(), template.Job.Group.DistributionConfig.IsExcludeWeekends, isNew ); #endregion logger.LogDebug("Получил nextRun {nextRun} по templateId {templateId}, isNew: {isNew}, тип расписания: автораспределение", result.NextRun, templateId, isNew); return result.NextRun; } else { // считаем как ЕСПП var nextRun = await esppScheduleTransformService.GetNextDateAsync(template.Job.Group.Id, template.Job.Group.ReferenceDate); logger.LogDebug("Получил nextRun {nextRun} по templateId {templateId}, isNew: {isNew}, тип расписания: ЕСПП", nextRun, templateId, isNew); return nextRun; } } /// /// Получить продолжительность в днях /// /// /// public int GetDurationDays(JobGroupDistributionConfig config) { var period = config.DistributionPeriod; var periodType = period!.Type; int.TryParse(period.Duration, out var duration); var periodDays = duration; if (periodType == DistributionPeriodTypeEnum.Day.ToString()) periodDays = duration; if (periodType == DistributionPeriodTypeEnum.Month.ToString()) // в месяце 30 дней, duration*30 periodDays = duration * 30; if (periodType == DistributionPeriodTypeEnum.Year.ToString()) // в году 365 дней, duration*365 periodDays = duration * 365; logger.LogDebug("Период распределения: {name}, duration: {duration}, type: {type}. Итого в днях: {days}", period.Name, period.Duration, period.Type, periodDays); if (periodDays == 0) logger.LogWarning("Период распределения 0 дней. Неверный конфиг распределения в таблице {table}. {name}, duration: {duration}, type: {type}", nameof(DistributionPeriod), period.Name, period.Duration, period.Type); return periodDays; } /// /// Получить дату начала распределения /// /// /// private DateOnly GetDateStart(DateTimeOffset referenceDate) { var today = DateTime.UtcNow; if (today < referenceDate) return DateOnly.FromDateTime(referenceDate.Date); else return DateOnly.FromDateTime(today.Date); } public async Task> GetWorkDaysAsync(DateOnly start, DateOnly end, bool excludeWeekends, DateTimeOffset referenceDate) { //return await templateDistributor.GetWorkDaysAsync(start, end, excludeWeekends); return await templateDistributor.GetWorkDaysTESTAsync(start, end, excludeWeekends, referenceDate); } public async Task> GetWeekendsAsync(DateOnly startDate, DateOnly endDate, DateTimeOffset referenceDate) { return await templateDistributor.GetWeekendsTESTAsync(startDate, endDate, referenceDate); } } }