diff --git a/PARR.API/Contracts/V1/ApiRoutes.cs b/PARR.API/Contracts/V1/ApiRoutes.cs index 0763c6ce..8d28f299 100644 --- a/PARR.API/Contracts/V1/ApiRoutes.cs +++ b/PARR.API/Contracts/V1/ApiRoutes.cs @@ -281,7 +281,7 @@ public static class StatTemplateDistribution { - public const string Get = BaseStat + "/template-distribution/{applicationInWorkId}"; + public const string Get = BaseStat + "/template-distribution/{jobGroupId}"; } public static class StatResponseAreaWorkLoad diff --git a/PARR.API/Contracts/V1/Responses/JobGroupResponse.cs b/PARR.API/Contracts/V1/Responses/JobGroupResponse.cs index 6cba2dae..8636a272 100644 --- a/PARR.API/Contracts/V1/Responses/JobGroupResponse.cs +++ b/PARR.API/Contracts/V1/Responses/JobGroupResponse.cs @@ -39,7 +39,7 @@ // public string? AgentScript { get; set; } } - public class JobGroupResponse : JobGroupBaseResponse + public class JobGroupResponse : JobGroupWithDistributionConfigResponse //: JobGroupBaseResponse { //public List? Jobs { get; set; } public int JobsCount { get; set; } @@ -58,11 +58,10 @@ /// public ScheduleExcludeTypeCalendarResponse? ScheduleExcludeTypeCalendar { get; set; } - /// - /// Настройки автораспределения - /// - public JobGroupDistributionConfigResponse? DistributionConfig { get; set; } - + ///// + ///// Настройки автораспределения + ///// + //public JobGroupDistributionConfigResponse? DistributionConfig { get; set; } } public class JobGroupScheduleResponse @@ -74,5 +73,13 @@ public List? Values { get; set; } } + public class JobGroupWithDistributionConfigResponse : JobGroupBaseResponse + { + /// + /// Настройки автораспределения + /// + public JobGroupDistributionConfigResponse? DistributionConfig { get; set; } + } + } diff --git a/PARR.API/Contracts/V1/Responses/Statistics/StatTemplateDistributorResponse.cs b/PARR.API/Contracts/V1/Responses/Statistics/StatTemplateDistributorResponse.cs index a3046a91..f627b902 100644 --- a/PARR.API/Contracts/V1/Responses/Statistics/StatTemplateDistributorResponse.cs +++ b/PARR.API/Contracts/V1/Responses/Statistics/StatTemplateDistributorResponse.cs @@ -2,23 +2,50 @@ { public class StatTemplateDistributorResponse { - public WorkGroupResponse? WorkGroup { get; set; } + public required JobGroupWithDistributionConfigResponse JobGroup { get; set; } - public int AllCount { get; set; } + /// + /// Всего шаблонов + /// + public int AllCount => ItemsList?.Sum(t => t.AllCount) ?? 0; - public int IsActivatedAllCount { get; set; } + /// + /// Всего шаблонов с активированными шаблонами и расписаниями + /// + public int IsActivatedAllCount => ItemsList?.Sum(t => t.IsActivatedCount) ?? 0; - public int IsDeactivatedAllCount { get; set; } + /// + /// Всего шаблонов с деактивированными шаблонами и/или расписаниями + /// + public int IsDeactivatedAllCount => ItemsList?.Sum(t => t.IsDeactivatedCount) ?? 0; + public List? ItemsList { get; set; } + } - public List? Stat { get; set; } + public class StatDistributorItemResponse + { + public string? WorkGroupName { get; set; } + + public int AllCount => Statistics?.Sum(t => t.AllCount) ?? 0; + public int IsActivatedCount => Statistics?.Sum(t => t.IsActivatedCount) ?? 0; + public int IsDeactivatedCount => Statistics?.Sum(t => t.IsDeactivatedCount) ?? 0; + + public List? Statistics { get; set; } } public class StatTemplateDistributorDateStat { - public DateTimeOffset Date { get; set; } + public DateOnly Date { get; set; } public int AllCount { get; set; } public int IsActivatedCount { get; set; } - public int IsDeactivatedCount { get;set; } + public int IsDeactivatedCount { get; set; } + public List? Templates { get; set; } + } + + public class StatTempleteDistribItem + { + public required string Name { get; set; } + public bool IsActiveTemplate { get; set; } + public bool IsActiveSchedular { get; set; } } } diff --git a/PARR.API/Controllers/V1/Statistics/StatTemplateDistributionController.cs b/PARR.API/Controllers/V1/Statistics/StatTemplateDistributionController.cs index f439f6e2..5c14802d 100644 --- a/PARR.API/Controllers/V1/Statistics/StatTemplateDistributionController.cs +++ b/PARR.API/Controllers/V1/Statistics/StatTemplateDistributionController.cs @@ -8,7 +8,12 @@ using PARR.API.Contracts.V1.Responses.Base; using PARR.API.Contracts.V1.Responses.Statistics; using PARR.API.Controllers.V1.Base; using PARR.Constants; +using PARR.DAL.DomainServices.Shortcodes; +using PARR.DAL.Models; +using PARR.DAL.Models.Job; +using PARR.DAL.NextRunServices; using PARR.DAL.Services.Interfaces; +using PARR.DAL.Services.Interfaces.Job; namespace PARR.API.Controllers.V1.Statistics { @@ -19,83 +24,183 @@ namespace PARR.API.Controllers.V1.Statistics public class StatTemplateDistributionController : BaseApiController { private readonly ITemplateService templateService; - private readonly IWorkGroupService workGroupService; + private readonly IJobGroupService jobGroupService; private readonly IMapper mapper; + private readonly IShortcodesService shortcodesService; + private readonly INextRunService nextRunService; + private readonly ILogger logger; public StatTemplateDistributionController( ITemplateService templateService, - IWorkGroupService workGroupService, - IMapper mapper + IJobGroupService jobGroupService, + IMapper mapper, + IShortcodesService shortcodesService, + INextRunService nextRunService, + ILogger logger ) { this.templateService = templateService; - this.workGroupService = workGroupService; + this.jobGroupService = jobGroupService; this.mapper = mapper; + this.shortcodesService = shortcodesService; + this.nextRunService = nextRunService; + this.logger = logger; } /// - /// Статистика распределения шаблонов по JobId (ApplicationInWorkId) + /// Статистика распределения шаблонов по jobGroupId, только для Used /// - /// + /// /// [HttpGet(ApiRoutes.StatTemplateDistribution.Get)] - public async Task Get([FromRoute] Guid jobId) + public async Task Get([FromRoute] Guid jobGroupId) { - // f87dbe9f-cabf-4108-9c93-12068d4b48a5 + // eadc5498-dba6-4f10-9b4b-a1653e3c3e61 - var query = templateService.Get() - .Include(t => t.Unit) - .Where(t => t.JobId == jobId) - .GroupBy(t => t.Unit!.BaseFields!.WorkGroup) - .Select(x => new - { - WorkGroup = x.Key, - CountTemplates = x.Count(), - IsActivatedAllCount = x.Count(t => t.IsActiveTemplate && t.IsActiveSchedule), - IsDeactivatedAllCount = x.Count(t => !t.IsActiveTemplate || !t.IsActiveSchedule || (!t.IsActiveTemplate && !t.IsActiveSchedule)), - GroupingByDate = - x.GroupBy(d => d.NextRun).OrderBy(d => d.Key).Select(d => - new - { - Date = d.Key, - //Всего - Count = d.Count(), - //Только активированных - IsActivatedCount = d.Count(t => t.IsActiveTemplate && t.IsActiveSchedule), - //Только деактивированных - IsDeactivatedCount = d.Count(t => !t.IsActiveTemplate || !t.IsActiveSchedule || (!t.IsActiveTemplate && !t.IsActiveSchedule)) - } - ) - }); + var jobGroup = await jobGroupService.Get() + .Include(t => t.DistributionConfig) + .ThenInclude(t => t.DistributionPeriod) + .AsNoTracking() + .FirstOrDefaultAsync(t => t.Id == jobGroupId); - var statResult = await query.ToListAsync(); + if (jobGroup == null) + return BadRequest(new Response(false, new List { new ErrorModel { Message = "Не найдена группа работ" } })); - var workGroups = await workGroupService.Get() - .Include(t => t.ResponseArea) - .Where(t => statResult.Select(x => x.WorkGroup).Any(w => w == t.Name)) + if (!jobGroup.IsAutoDistributionEnabled || jobGroup.DistributionConfig == null) + return BadRequest(new Response(false, new List { new ErrorModel { Message = "Для группы работ не включено автораспределение" } })); + + var items = await templateService.Get() + .Where(t => t.Job!.GroupId == jobGroupId && t.StatusTypeId == TemplateStatusTypeEnum.Used) + .Select(t => new + { + WorkGroup = t.Job!.WorkGroupMask, + Template = t + }) + .AsNoTracking() .ToListAsync(); - - var response = statResult.Select(stat => new StatTemplateDistributorResponse - { - WorkGroup = mapper.Map(workGroups.FirstOrDefault(x => x.Name == stat.WorkGroup)),//TODO Migration to job - AllCount = stat.CountTemplates, - IsActivatedAllCount = stat.IsActivatedAllCount, - IsDeactivatedAllCount = stat.IsDeactivatedAllCount, - Stat = stat.GroupingByDate.Select(x => new StatTemplateDistributorDateStat + if (!items.Any()) + return Ok(new Response(new StatTemplateDistributorResponse { - AllCount = x.Count, - Date = x.Date, - IsActivatedCount = x.IsActivatedCount, - IsDeactivatedCount = x.IsDeactivatedCount - }).ToList() - }) - .OrderBy(t => t.WorkGroup?.Name) - .ToList(); + JobGroup = mapper.Map(jobGroup), + ItemsList = null + }, true)); + // получаем список рабочих дней + var workDays = await GetWorkDaysAsync(jobGroup, items.Max(t => t.Template.NextRun)); - return Ok(new Response>(response, true)); + var responseItemsList = new List(); + + if (jobGroup.DistributionConfig.IsGroupingByWorkGroup) + { + // тут группируем по рабочим группам + + // Получаем список пар (WorkGroupName, Template) + var templatesWithWorkGroupNames = new List<(string WorkGroupName, Template Template)>(); + + // получаем названия рабочих групп + foreach (var item in items) + { + var workGroupName = await shortcodesService.ApplyShortcodesAsync(item.WorkGroup, item.Template); + templatesWithWorkGroupNames.Add((workGroupName, item.Template)); + } + + // Группируем по WorkGroupName + var grouped = templatesWithWorkGroupNames + .GroupBy(x => x.WorkGroupName) + .ToDictionary(g => g.Key, g => g.Select(x => x.Template).ToList()); + + foreach (var groupedItem in grouped) + { + var statResult = GetResponseByWorkGroup(groupedItem.Key, groupedItem.Value, workDays); + responseItemsList.Add(statResult); + } + } + else + { + // не нужно группировать по рабочим группам + + // сразу формируем response + responseItemsList.Add(GetResponseByWorkGroup(null, items.Select(t => t.Template).ToList(), workDays)); + } + + var response = new StatTemplateDistributorResponse + { + JobGroup = mapper.Map(jobGroup), + ItemsList = responseItemsList.OrderBy(t => t.WorkGroupName).ToList() + }; + + return Ok(new Response(response, true)); } + + + private async Task> GetWorkDaysAsync(JobGroup jobGroup, DateTimeOffset maxNextRunTemplate) + { + var durationDays = nextRunService.GetDurationDays(jobGroup.DistributionConfig!); + + var dateStart = DateOnly.FromDateTime(DateTime.UtcNow); + + // последний день, не может быть меньше чем durationDays + var dateEnd = dateStart.AddDays(durationDays); + + // последний день, не может быть меньше чем refDate+durationDays + var sumRefDuration = DateOnly.FromDateTime(jobGroup.ReferenceDate.AddDays(durationDays).Date); + if (dateEnd < sumRefDuration) + dateEnd = sumRefDuration; + + // с DateEnd вообще какая-то шурпатня :( + // как наглядно понять ок не ок распределяется, если на каком-то графике dateEnd может ухеать за период распределения + // последний день, не может быть меньше чем дата последнего шаблона maxNextRunTemplate + var lastTempalteDate = DateOnly.FromDateTime(maxNextRunTemplate.Date); + if (dateEnd < lastTempalteDate) + dateEnd = lastTempalteDate; + + return await nextRunService.GetWorkDaysAsync(dateStart, dateEnd, jobGroup.DistributionConfig!.IsExcludeWeekends); + } + + private StatDistributorItemResponse GetResponseByWorkGroup(string? workGroupName, List