Files
parr_api/PARR.TemplateDistributor/TemplateDistributor.cs

315 lines
14 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.Logging;
using PARR.BLL.Services.Interfaces;
using PARR.Constants;
using PARR.DAL.Models;
using PARR.DAL.Services.Interfaces;
using PARR.DAL.TransformServices;
namespace PARR.TemplateDistributor
{
internal class TemplateDistributor : ITemplateDistributor
{
private readonly ILogger<TemplateDistributor> logger;
private readonly ITemplateService templateService;
private readonly IApplicationsInWorkService applicationsInWorkService;
private readonly IEsppSchTypeScheduleService esppSchTypeScheduleService;
private readonly ICalendarService calendarService;
private readonly IEsppScheduleTransformService esppScheduleTransformService;
private readonly IWeekendDayService weekendDayService;
public TemplateDistributor(
ILogger<TemplateDistributor> logger,
ITemplateService templateService,
IApplicationsInWorkService applicationsInWorkService,
IEsppSchTypeScheduleService esppSchTypeScheduleService,
ICalendarService calendarService,
IEsppScheduleTransformService esppScheduleTransformService,
IWeekendDayService weekendDayService
)
{
this.logger = logger;
this.templateService = templateService;
this.applicationsInWorkService = applicationsInWorkService;
this.esppSchTypeScheduleService = esppSchTypeScheduleService;
this.calendarService = calendarService;
this.esppScheduleTransformService = esppScheduleTransformService;
this.weekendDayService = weekendDayService;
}
public async Task UpdateScheduleAsync(Guid applicationInWorkId)
{
// все шаблоны по applicationInWorkId
// группирует по РР
// -> DistributeTemplateForPeriodAsync
// сохранить в БД
//applicationInWorkId = Guid.Parse("bdfefd77-bce3-4f62-a484-5042c06f4467");
//var appInWork = await applicationsInWorkService.Get()
// .Include(aiw => aiw.EsppSchValues)
// .ThenInclude(esv => esv.EsppSchTypeValue)
// .ThenInclude(etv => etv!.DistributionPeriod)
// .FirstOrDefaultAsync(t => t.Id == applicationInWorkId);
var templates = await templateService.Get()
.Include(t => t.Host)
.ThenInclude(h => h.WorkGroup)
.Where(t => t.ApplicationInWorkId == applicationInWorkId)
.ToListAsync();
var workGroupsWithTemplates = templates.GroupBy(t => t.Host!.WorkGroupId);
foreach (var workGroupWithTemplates in workGroupsWithTemplates)
{
var wgId = workGroupWithTemplates.Key ?? Guid.NewGuid();//TODO сделать правильно
var values = workGroupWithTemplates.ToList();
if (!values.Any())
continue;
var distrTemplates = await DistributeTemplateAsync(values, applicationInWorkId, wgId);
}
// saveChanges
if (!await applicationsInWorkService.CommitAsync())
logger.LogError($"Ошибка записи изменений в БД при перераспределении NextRun шаблонов");
}
public async Task<List<Template>> DistributeTemplateAsync(List<Template> templates, Guid applicationInWorkId, Guid workGroupId)
{
var appInWork = await applicationsInWorkService.Get()
.Include(aiw => aiw.EsppSchValues)
.ThenInclude(esv => esv.EsppSchTypeValue)
.ThenInclude(etv => etv!.DistributionPeriod)
.FirstOrDefaultAsync(t => t.Id == applicationInWorkId);
var refDate = appInWork!.ReferenceDate;
//Проверяем наличие распределения РР на период
if (appInWork!.IsAutoDistributionEnabled)
{
var existingTemplates = await templateService.Get()
.Include(t => t.Host)
.Where(t => t.ApplicationInWorkId == applicationInWorkId && t.Host!.WorkGroupId == workGroupId)
.ToListAsync();
//Если распределенная РР получаем начало
var period = appInWork.EsppSchValues.FirstOrDefault()?.EsppSchTypeValue?.DistributionPeriod;
var periodType = ParseDistributionPeriodType(period!.Type);
var workDays = await GetWorkDaysAsync(appInWork);
//Готовим план распределения
var distrPlan = GetDistributionPlan(workDays, templates.Count + existingTemplates.Count);
var templateToDistrib = new List<Template>();
if (existingTemplates.Count > 0)
templateToDistrib = GetTemplatesToDistribute(ref distrPlan, existingTemplates);
templateToDistrib.AddRange(templates);
var distributedTemplates = 0;
foreach (var workDay in distrPlan)
{
var templatesCountForCurDay = workDay.Value;
templateToDistrib.Skip(distributedTemplates).Take(templatesCountForCurDay).ToList().ForEach(t =>
{
var nextRun = new DateTimeOffset(
workDay.Key.Year, workDay.Key.Month, workDay.Key.Day,
refDate.Hour, refDate.Minute, refDate.Second,
new TimeSpan(0, 0, 0));
if (nextRun < DateTimeOffset.UtcNow)
nextRun = esppScheduleTransformService.GetNextDateForDistributionRun(nextRun, periodType, period.Duration);
t.NextRun = nextRun;
});
distributedTemplates += templatesCountForCurDay;
}
return templateToDistrib;
//var d = new Dictionary<DateOnly, List<Template>>();
//foreach (var wd in workDays)
//{
// var wrokDateTimeOffset = new DateTimeOffset(
// wd.Year, wd.Month, wd.Day,
// refDate.Hour, refDate.Minute, refDate.Second,
// new TimeSpan(0, 0, 0));
// if (wrokDateTimeOffset < DateTimeOffset.UtcNow)
// wrokDateTimeOffset = esppScheduleTransformService.GetNextDateForDistributionRun(wrokDateTimeOffset, periodType, period.Duration);
// var assignedTemplates = existingTemplates.Where(t => t.NextRun == wrokDateTimeOffset).ToList();
// d.Add(wd, assignedTemplates);
//}
//var templatesPerStep = (double)templates.Count() / workDays.Count();//
//var currentTemplateStep = (int)Math.Ceiling(templatesPerStep);
//var delta = templatesPerStep - currentTemplateStep;
////templates.First().NextRun = currentDay;
//var templateDistributed = 0;
////Отталкиваясь от количества
//while (templateDistributed < templates.Count())
//{
// var wdCounts = d.Select(wd => wd.Value).OrderBy(count => count).ToArray();
// var templateCountDelta = d.Max(t => t.Value.Count()) - d.Min(t => t.Value.Count());
// var addingTemplatesCount = wdCounts.Length > 1 ?
// (templateCountDelta == 0) ? (int)Math.Ceiling(templatesPerStep) : templateCountDelta :
// (int)Math.Ceiling(templatesPerStep);
// var workDaysWithMinTemplates = d.Where(wd => wd.Value == wdCounts[0]).ToArray();
// if (workDaysWithMinTemplates.Length * addingTemplatesCount < templates.Count())
// {
// foreach (var wd in workDaysWithMinTemplates)
// {
// //Считаем DateTimeOffset, потому что у нас только дата пока
// var currentDay = new DateTimeOffset(
// wd.Key.Year, wd.Key.Month, wd.Key.Day,
// refDate.Hour, refDate.Minute, refDate.Second,
// new TimeSpan(0, 0, 0));
// templates.Skip(templateDistributed).Take(addingTemplatesCount).ToList().ForEach(t =>
// {
// t.NextRun = currentDay;
// d[wd.Key].Add(t);
// });
// templateDistributed += addingTemplatesCount;
// }
// }
// else
// {
// while (templateDistributed < templates.Count())
// {
// var wd = d.Where(wd => wd.Value == d.Min(t => t.Value)).First();
// //Считаем DateTimeOffset, потому что у нас только дата пока
// var currentDay = new DateTimeOffset(
// wd.Key.Year, wd.Key.Month, wd.Key.Day,
// refDate.Hour, refDate.Minute, refDate.Second,
// new TimeSpan(0, 0, 0));
// templates.Skip(templateDistributed).Take(addingTemplatesCount).ToList().ForEach(t =>
// {
// t.NextRun = currentDay;
// d[wd.Key].Add(t);
// });
// templateDistributed += addingTemplatesCount;
// }
// }
//}
}
else
templates.ForEach(t => t.NextRun = appInWork!.ReferenceDate);
return templates;
}
private async Task<List<DateOnly>> GetWorkDaysAsync(ApplicationsInWork appInWork)
{
var period = appInWork.EsppSchValues.FirstOrDefault()?.EsppSchTypeValue?.DistributionPeriod;
var periodType = ParseDistributionPeriodType(period!.Type);
var startPeriod = await esppScheduleTransformService.GetStartPeriodForDateAsync(appInWork.Id, appInWork.ReferenceDate, appInWork.ReferenceDate, periodType, period.Duration);
var result = calendarService.GetWorkDatesForPeriod(startPeriod, periodType, period.Duration, weekendDayService.GetWeekends);
result.ForEach(wd =>
{
var wrokDateTimeOffset = new DateTimeOffset(
wd.Year, wd.Month, wd.Day,
0, 0, 0,
new TimeSpan(0, 0, 0));
if (wrokDateTimeOffset < DateTimeOffset.UtcNow)
{
wrokDateTimeOffset = esppScheduleTransformService.GetNextDateForDistributionRun(wrokDateTimeOffset, periodType, period.Duration);
wd = DateOnly.FromDateTime(wrokDateTimeOffset.DateTime);
}
});
return result;
}
private List<Template> GetTemplatesToDistribute(ref Dictionary<DateOnly, int> distributionPlan, List<Template> templates)
{
var result = new List<Template>();
foreach (var wd in distributionPlan)
{
var templatesInWd = templates.Where(t => DateOnly.FromDateTime(t.NextRun.DateTime) == wd.Key).ToList();
var templatesToDistribute = templatesInWd.Skip(wd.Value).Take(templatesInWd.Count - wd.Value).ToList();
distributionPlan[wd.Key] -= templatesInWd.Count;
result.AddRange(templatesToDistribute);
}
return result;
}
private DistributionPeriodTypeEnum ParseDistributionPeriodType(string value)
{
var result = (DistributionPeriodTypeEnum)Enum.Parse(typeof(DistributionPeriodTypeEnum), value);
return result;
}
private Dictionary<DateOnly, int> GetDistributionPlan(List<DateOnly> workDays, int templateCount)
{
var d = new Dictionary<DateOnly, int>();
foreach (var wd in workDays)
{
var workDateTimeOffset = new DateOnly(wd.Year, wd.Month, wd.Day);
d.Add(workDateTimeOffset, 0);
}
var templateDistributed = 0;
if (templateCount > workDays.Count)
{
var mainPartTemplateCountToDistribute = templateCount / workDays.Count;
foreach (var item in d)
{
d[item.Key] = mainPartTemplateCountToDistribute;
templateDistributed += mainPartTemplateCountToDistribute;
}
}
var templatesPerWorkDay = (double)workDays.Count / (templateCount % workDays.Count);
var currentStep = (double)Math.Floor(templatesPerWorkDay);//??????? точно double?
var balance = (double)templatesPerWorkDay - Math.Floor(currentStep);
var currentIndex = 0;
while (templateDistributed < templateCount)
{
if (currentStep > 0)
{
d[d.ElementAt(currentIndex).Key] += 1;
templateDistributed++;
currentIndex += (int)Math.Floor(currentStep);
currentStep = templatesPerWorkDay + balance;
balance = currentStep - templatesPerWorkDay;
}
}
return d;
}
}
}