diff --git a/PARR.API/Contracts/V1/Requests/BaseRequests/TimeZoneOffsetClient.cs b/PARR.API/Contracts/V1/Requests/BaseRequests/TimeZoneOffsetClient.cs index 386ed565..cead2206 100644 --- a/PARR.API/Contracts/V1/Requests/BaseRequests/TimeZoneOffsetClient.cs +++ b/PARR.API/Contracts/V1/Requests/BaseRequests/TimeZoneOffsetClient.cs @@ -1,10 +1,23 @@ -namespace PARR.API.Contracts.V1.Requests.BaseRequests +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace PARR.API.Contracts.V1.Requests.BaseRequests { /// /// Смещение таймзоны относительно клиента /// public class TimeZoneOffsetClient { + /// + /// Удалить TimeZoneOffsetHours и не использовать!!!!!! Использовать только в минутах TimeZoneOffsetMinutes + /// public int TimeZoneOffsetHours { get; set; } = 0; + + /// + /// Часовая зона клиента, в минутах + /// + public int TimeZoneOffsetMinutes { get; set; } = 0; + + [BindNever] + public TimeSpan TimeZoneOffset => TimeSpan.FromMinutes(TimeZoneOffsetMinutes); } } diff --git a/PARR.API/Contracts/V1/Responses/Statistics/StatTemplateDistributorResponse.cs b/PARR.API/Contracts/V1/Responses/Statistics/StatTemplateDistributorResponse.cs index 7a090e07..61298176 100644 --- a/PARR.API/Contracts/V1/Responses/Statistics/StatTemplateDistributorResponse.cs +++ b/PARR.API/Contracts/V1/Responses/Statistics/StatTemplateDistributorResponse.cs @@ -29,7 +29,10 @@ public class StatDistributorItemResponse { - public string? WorkGroupName { get; set; } + /// + /// Наименование поля по которому группируется + /// + public string? GroupFieldName { get; set; } public int AllCount => Statistics?.Sum(t => t.AllCount) ?? 0; public int IsActivatedCount => Statistics?.Sum(t => t.IsActivatedCount) ?? 0; @@ -47,15 +50,19 @@ public int IsActivatedCount { get; set; } public int IsDeactivatedCount { get; set; } public int TemplatesInWeekendCount => !IsWorkDay && Templates != null ? Templates.Count : 0; - public List? Templates { get; set; } + public List? Templates { get; set; } } - public class StatTempleteDistribItem + public class StatTemplateDistribItem { + /// + /// TemplateId + /// + public Guid Id { get; set; } public required string Name { get; set; } public DateTimeOffset NextRun { get; set; } public bool IsActiveTemplate { get; set; } public bool IsActiveSchedular { get; set; } - public required ScheduleResponseAreaTimeOffsetResponse ResponseAreaOffset { get; set; } + //public required ScheduleResponseAreaTimeOffsetResponse ResponseAreaOffset { get; set; } } } diff --git a/PARR.API/Contracts/V1/Responses/TemplateResponse.cs b/PARR.API/Contracts/V1/Responses/TemplateResponse.cs index f7810309..3fd702f8 100644 --- a/PARR.API/Contracts/V1/Responses/TemplateResponse.cs +++ b/PARR.API/Contracts/V1/Responses/TemplateResponse.cs @@ -45,7 +45,7 @@ public bool IsAutoDistributionEnabled { get; set; } - public ScheduleResponseAreaTimeOffsetResponse? ResponseAreaOffset { get; set; } + //public ScheduleResponseAreaTimeOffsetResponse? ResponseAreaOffset { get; set; } } diff --git a/PARR.API/Controllers/V1/Statistics/StatTemplateDistributionController.cs b/PARR.API/Controllers/V1/Statistics/StatTemplateDistributionController.cs index 862a196f..3cf90b87 100644 --- a/PARR.API/Controllers/V1/Statistics/StatTemplateDistributionController.cs +++ b/PARR.API/Controllers/V1/Statistics/StatTemplateDistributionController.cs @@ -29,7 +29,7 @@ namespace PARR.API.Controllers.V1.Statistics private readonly IJobGroupService jobGroupService; private readonly IMapper mapper; private readonly IShortcodesService shortcodesService; - private readonly INextRunService nextRunService; + private readonly INextRunServiceV2 nextRunService; private readonly ILogger logger; private readonly IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService; @@ -38,7 +38,7 @@ namespace PARR.API.Controllers.V1.Statistics IJobGroupService jobGroupService, IMapper mapper, IShortcodesService shortcodesService, - INextRunService nextRunService, + INextRunServiceV2 nextRunService, ILogger logger, IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService ) @@ -63,8 +63,6 @@ namespace PARR.API.Controllers.V1.Statistics { // eadc5498-dba6-4f10-9b4b-a1653e3c3e61 - //TODO: похоже что timeZoneQuery лишняя - var jobGroup = await jobGroupService.Get() .Include(t => t.DistributionConfig) .ThenInclude(t => t.DistributionPeriod) @@ -95,65 +93,86 @@ namespace PARR.API.Controllers.V1.Statistics ItemsList = null }, true)); - var (dateStart, dateEnd) = GetStartEndPeriod(jobGroup, items.Max(t => t.Template.NextRun), timeZoneQuery.TimeZoneOffsetHours); + + var (dateStart, dateEnd) = GetStartEndPeriod(jobGroup, items.Min(t => t.Template.NextRun), items.Max(t => t.Template.NextRun), timeZoneQuery.TimeZoneOffset); // получаем список рабочих дней // получет все дни, даже с выходными, чтоб видеть реальную картину - var workDays = await nextRunService.GetWorkDaysAsync(dateStart, dateEnd, false, jobGroup.ReferenceDate); + var allDays = await nextRunService.GetWorkDaysAsync(dateStart, dateEnd, false); // получить список выходных дней (показываем только если IsExcludeWeekends = true) var weekends = jobGroup.DistributionConfig.IsExcludeWeekends - ? await nextRunService.GetWeekendsAsync(dateStart, dateEnd, jobGroup.ReferenceDate) + ? await nextRunService.GetWeekendsAsync(dateStart, dateEnd) : new HashSet(); + var responseItemsList = new List(); if (jobGroup.DistributionConfig.IsGroupingByWorkGroup) { // тут группируем по рабочим группам + logger.LogDebug("Группируем по рабочим группам"); // Получаем список пар (WorkGroupName, Template) - var templatesWithWorkGroupNames = new List<(string WorkGroupName, (Template Template, string ResponseArea))>(); + var templatesWithWorkGroupNames = new List<(string WorkGroupName, Template Template)>(); - // получаем названия рабочих групп и ЗО + // получаем названия рабочих групп foreach (var item in items) { var workGroupName = await shortcodesService.ApplyShortcodesAsync(item.WorkGroupMask, item.Template); - var responseArea = await shortcodesService.ApplyShortcodesAsync(item.ResponseAreaMask, item.Template); - templatesWithWorkGroupNames.Add((workGroupName, (item.Template, responseArea))); + templatesWithWorkGroupNames.Add((workGroupName, item.Template)); } // Группируем по WorkGroupName var grouped = templatesWithWorkGroupNames .GroupBy(x => x.WorkGroupName) - .ToDictionary(g => g.Key, g => g.Select(x => x.Item2).ToList()); + .ToDictionary(g => g.Key, g => g.Select(x => x.Template).ToList()); foreach (var groupedItem in grouped) { - var statResult = GetResponseByWorkGroup(groupedItem.Key, groupedItem.Value.ToList(), workDays, weekends, timeZoneQuery.TimeZoneOffsetHours, jobGroup.ReferenceDate); + var statResult = GetResponseByWorkGroup(groupedItem.Key, groupedItem.Value.ToList(), allDays, weekends, timeZoneQuery.TimeZoneOffset); + responseItemsList.Add(statResult); + } + } + else if (jobGroup.IsResponseAreaTimezone && !jobGroup.DistributionConfig.IsGroupingByWorkGroup) + { + // грппируем только по ЗО + logger.LogDebug("Группируем по ЗО"); + + // Будет группироваться по EsppValue, по MSK, MSK+1... + // получаем названия ЗО, из ЗО часовой пояс, формируем список шаблонов с часовым поясом ЗО + var templatesWithTimeZone = new List<(Template Template, string MskTimeZone)>(); + foreach (var item in items) + { + var responseArea = await shortcodesService.ApplyShortcodesAsync(item.ResponseAreaMask, item.Template); + var mskTimeZone = scheduleResponseAreaTimeOffsetService.GetByResponseAreaOrDefault(responseArea); + templatesWithTimeZone.Add((item.Template, mskTimeZone.EsppValue)); + } + + // Группируем по часовому поясу + var grouped = templatesWithTimeZone + .GroupBy(x => x.MskTimeZone) + .ToDictionary(g => g.Key, g => g.Select(x => x.Template).ToList()); + + foreach (var groupedItem in grouped) + { + var statResult = GetResponseByWorkGroup(groupedItem.Key, groupedItem.Value.ToList(), allDays, weekends, timeZoneQuery.TimeZoneOffset); responseItemsList.Add(statResult); } } else { - // не нужно группировать по рабочим группам - - // получаем названия ЗО - var templatesWithResponseArea = new List<(Template Template, string ResponseArea)>(); - foreach (var item in items) - { - var responseArea = await shortcodesService.ApplyShortcodesAsync(item.ResponseAreaMask, item.Template); - templatesWithResponseArea.Add((item.Template, responseArea)); - } + // не нужно группировать + logger.LogDebug("Без группировки"); // сразу формируем response - responseItemsList.Add(GetResponseByWorkGroup(null, templatesWithResponseArea, workDays, weekends, timeZoneQuery.TimeZoneOffsetHours, jobGroup.ReferenceDate)); + responseItemsList.Add(GetResponseByWorkGroup(null, items.Select(t => t.Template).ToList(), allDays, weekends, timeZoneQuery.TimeZoneOffset)); } var response = new StatTemplateDistributorResponse { JobGroup = mapper.Map(jobGroup), - ItemsList = responseItemsList.OrderBy(t => t.WorkGroupName).ToList() + ItemsList = responseItemsList.OrderBy(t => t.GroupFieldName).ToList() }; return Ok(new Response(response, true)); @@ -163,29 +182,33 @@ namespace PARR.API.Controllers.V1.Statistics /// Получить дату начала и конца периода /// /// + /// /// - /// + /// /// - private (DateOnly dateStart, DateOnly dateEnd) GetStartEndPeriod(JobGroup jobGroup, DateTimeOffset maxNextRunTemplate, int timeZoneOffsetHours) + private (DateOnly dateStart, DateOnly dateEnd) GetStartEndPeriod(JobGroup jobGroup, DateTimeOffset minNextRunTemplate, DateTimeOffset maxNextRunTemplate, TimeSpan clientOffset) { var durationDays = nextRunService.GetDurationDays(jobGroup.DistributionConfig!); - var dateStart = DateOnly.FromDateTime(DateTime.UtcNow.AddHours(timeZoneOffsetHours)); + var dateStart = DateOnly.FromDateTime(DateTime.UtcNow.Add(clientOffset)); // последний день, не может быть меньше чем durationDays var dateEnd = dateStart.AddDays(durationDays); // последний день, не может быть меньше чем refDate+durationDays - var sumRefDuration = DateOnly.FromDateTime(jobGroup.ReferenceDate.AddHours(timeZoneOffsetHours).AddDays(durationDays).Date); + var sumRefDuration = DateOnly.FromDateTime(jobGroup.ReferenceDate.Add(clientOffset).AddDays(durationDays).Date); if (dateEnd < sumRefDuration) dateEnd = sumRefDuration; - // с DateEnd вообще какая-то шурпатня :( - // как наглядно понять ок не ок распределяется, если на каком-то графике dateEnd может ухеать за период распределения // последний день, не может быть меньше чем дата последнего шаблона maxNextRunTemplate - var lastTempalteDate = DateOnly.FromDateTime(maxNextRunTemplate.AddHours(timeZoneOffsetHours).Date); - if (dateEnd < lastTempalteDate) - dateEnd = lastTempalteDate; + var lastTemplateDate = DateOnly.FromDateTime(maxNextRunTemplate.Add(clientOffset).Date); + if (dateEnd < lastTemplateDate) + dateEnd = lastTemplateDate; + + // Дата начала, не может быть меньше чем minNextRunTemplate + var minNextRunDate = DateOnly.FromDateTime(minNextRunTemplate.Add(clientOffset).Date); + if (dateStart < minNextRunDate) + dateStart = minNextRunDate; return (dateStart, dateEnd); } @@ -193,49 +216,49 @@ namespace PARR.API.Controllers.V1.Statistics /// /// Заполнить респонс /// - /// + /// /// - /// - /// + /// + /// + /// /// - private StatDistributorItemResponse GetResponseByWorkGroup(string? workGroupName, List<(Template Template, string ResponseArea)> templateList, List workDays, HashSet weekends, int timeZoneOffsetHours, DateTimeOffset referenceDate) + private StatDistributorItemResponse GetResponseByWorkGroup(string? groupFieldName, List