diff --git a/PARR.API/Contracts/V1/ApiRoutes.cs b/PARR.API/Contracts/V1/ApiRoutes.cs index dc91e5c7..4af58ae9 100644 --- a/PARR.API/Contracts/V1/ApiRoutes.cs +++ b/PARR.API/Contracts/V1/ApiRoutes.cs @@ -356,6 +356,16 @@ #endregion + + #region Distributor + + public static class Distributor + { + public const string Distribute = Base + "/distributor/"; + } + + #endregion + } } diff --git a/PARR.API/Contracts/V1/Requests/ApplicationInWorkRequest.cs b/PARR.API/Contracts/V1/Requests/ApplicationInWorkRequest.cs index 90c4eb80..eb6df60b 100644 --- a/PARR.API/Contracts/V1/Requests/ApplicationInWorkRequest.cs +++ b/PARR.API/Contracts/V1/Requests/ApplicationInWorkRequest.cs @@ -15,7 +15,17 @@ public required string Solution { get; set; } - public DateTimeOffset NextRun { get; set; } + //public DateTimeOffset NextRun { get; set; } + + /// + /// Дата начала работ + /// + public DateTimeOffset ReferenceDate { get; set; } + + /// + /// Включить автораспределение + /// + public bool IsAutoDistributionEnabled { get; set; } public bool IsAgent { get; set; } diff --git a/PARR.API/Contracts/V1/Requests/DistributeRequest.cs b/PARR.API/Contracts/V1/Requests/DistributeRequest.cs new file mode 100644 index 00000000..26b81422 --- /dev/null +++ b/PARR.API/Contracts/V1/Requests/DistributeRequest.cs @@ -0,0 +1,7 @@ +namespace PARR.API.Contracts.V1.Requests +{ + public class DistributeRequest + { + public Guid ApplicationInWorkId { get; set; } + } +} diff --git a/PARR.API/Contracts/V1/Responses/ApplicationInWorkResponse.cs b/PARR.API/Contracts/V1/Responses/ApplicationInWorkResponse.cs index 5d25ff4c..94ee6f13 100644 --- a/PARR.API/Contracts/V1/Responses/ApplicationInWorkResponse.cs +++ b/PARR.API/Contracts/V1/Responses/ApplicationInWorkResponse.cs @@ -12,9 +12,13 @@ public required string Solution { get; set; } - public DateTimeOffset? LastRun { get; set; } + //public DateTimeOffset? LastRun { get; set; } - public DateTimeOffset NextRun { get; set; } + //public DateTimeOffset NextRun { get; set; } + + public DateTimeOffset ReferenceDate { get; set; } + + public bool IsAutoDistributionEnabled { get; set; } public bool IsAgent { get; set; } @@ -32,7 +36,7 @@ public ApplicationInWorkScheduleResponse? Schedule { get; set; } - public List? WorkGroups { get; set; } + public List? WorkGroups { get; set; } } diff --git a/PARR.API/Contracts/V1/Responses/EsppScheduleResponse.cs b/PARR.API/Contracts/V1/Responses/EsppScheduleResponse.cs index 8b373efe..fab55922 100644 --- a/PARR.API/Contracts/V1/Responses/EsppScheduleResponse.cs +++ b/PARR.API/Contracts/V1/Responses/EsppScheduleResponse.cs @@ -57,6 +57,8 @@ public Guid Id { get; set; } public required string Value { get; set; } + + public Guid? DistributionPeriodId { get; set; } } public class EsppScheduleTypeConfigWithDataResponse diff --git a/PARR.API/Contracts/V1/Responses/TemplateResponse.cs b/PARR.API/Contracts/V1/Responses/TemplateResponse.cs index bdeb1b84..d3bc86ae 100644 --- a/PARR.API/Contracts/V1/Responses/TemplateResponse.cs +++ b/PARR.API/Contracts/V1/Responses/TemplateResponse.cs @@ -23,6 +23,7 @@ public DateTimeOffset? LastRun { get; set; } public DateTimeOffset NextRun { get; set; } + public bool IsAutoDistributionEnabled { get; set; } public HostTemplateResponse? Host { get; set; } diff --git a/PARR.API/Controllers/V1/AgentTaskController.cs b/PARR.API/Controllers/V1/AgentTaskController.cs index 361fe992..6d08caef 100644 --- a/PARR.API/Controllers/V1/AgentTaskController.cs +++ b/PARR.API/Controllers/V1/AgentTaskController.cs @@ -71,7 +71,8 @@ namespace PARR.API.Controllers.V1 && t.ApplicationsInWork!.IsAgent == true && t.IsActiveTemplate == true && t.IsActiveSchedule == true - && t.ApplicationsInWork!.NextRun.DateTime.Date == date.Date.Date + //&& t.ApplicationsInWork!.NextRun.DateTime.Date == date.Date.Date + && t.NextRun.DateTime.Date == date.Date.Date && t.RobotConfigurations.All(c => c.TaskStatusCode == (int)TaskStatusEnum.Ok) && t.ApplicationsInWork.EsppSchValues.Any() ).ToListAsync(); @@ -87,12 +88,12 @@ namespace PARR.API.Controllers.V1 foreach (var template in templates) { //var templateSchedule = await esppScheduleTransformService.GetNextScheduleAsync(template.ApplicationInWorkId, template.ApplicationsInWork!.LastRun ?? template.ApplicationsInWork!.NextRun); - var templateSchedule = await esppScheduleTransformService.GetNextScheduleAsync(template.ApplicationInWorkId, template.ApplicationsInWork!.NextRun); + var templateSchedule = await esppScheduleTransformService.GetNextScheduleAsync(template.ApplicationInWorkId, template.NextRun); if (!templateSchedule.Any()) { logger.LogWarning($"Запросили раписание для агента по NextRun и вернулся пустой список! Такого не должно быть! " + - $"ApplicationInWorkId: {template.ApplicationInWorkId}, latRun: {template.ApplicationsInWork!.LastRun}, NextRun: {template.ApplicationsInWork!.NextRun}, ip: {ip}, date: {date}"); + $"ApplicationInWorkId: {template.ApplicationInWorkId}, latRun: {template.LastRun}, NextRun: {template.NextRun}, ip: {ip}, date: {date}"); continue; } diff --git a/PARR.API/Controllers/V1/ApplicationInWorkController.cs b/PARR.API/Controllers/V1/ApplicationInWorkController.cs index 3c4d1dca..e1d5d4be 100644 --- a/PARR.API/Controllers/V1/ApplicationInWorkController.cs +++ b/PARR.API/Controllers/V1/ApplicationInWorkController.cs @@ -11,10 +11,14 @@ using PARR.API.Contracts.V1.Responses.Base; using PARR.API.Controllers.V1.Base; using PARR.API.Extensions; using PARR.API.Services.Interfaces; +using PARR.API.Settings; +using PARR.BLL.Domain.Mq; +using PARR.BLL.Services.Interfaces; using PARR.Constants; using PARR.DAL.DomainModels; using PARR.DAL.Models; using PARR.DAL.Services.Interfaces; +using System.Text.Json; namespace PARR.API.Controllers.V1 { @@ -27,6 +31,8 @@ namespace PARR.API.Controllers.V1 private readonly IValidator validator; private readonly IUriService uriService; private readonly ITemplateService templateService; + private readonly IMqService mqService; + private readonly MqSettings mqSettings; public ApplicationInWorkController( ILogger logger, @@ -34,7 +40,9 @@ namespace PARR.API.Controllers.V1 IApplicationsInWorkService applicationsInWorkService, IValidator validator, IUriService uriService, - ITemplateService templateService + ITemplateService templateService, + IMqService mqService, + MqSettings mqSettings ) { this.logger = logger; @@ -43,6 +51,8 @@ namespace PARR.API.Controllers.V1 this.validator = validator; this.uriService = uriService; this.templateService = templateService; + this.mqService = mqService; + this.mqSettings = mqSettings; } @@ -147,7 +157,9 @@ namespace PARR.API.Controllers.V1 ShortDescription = request.ShortDescription.Trim(), FullDescription = request.FullDescription.Trim(), Solution = request.Solution.Trim(), - NextRun = request.NextRun, + //NextRun = request.NextRun, + IsAutoDistributionEnabled = request.IsAutoDistributionEnabled, + ReferenceDate = request.ReferenceDate, IsAgent = request.IsAgent, AgentName = request.AgentName?.Trim(), AgentTimeOutSec = request.AgentTimeOutSec, @@ -178,7 +190,7 @@ namespace PARR.API.Controllers.V1 if (!await applicationsInWorkService.CreateAsync(applicationInWork) || !await applicationsInWorkService.CommitAsync()) return BadRequest(new Response(false, new List { new ErrorModel { Message = "Ошибка при созании задания на выполнение работ" } })); - logger.LogInformation($"Пользователь {User.Identity?.Name} добавил задание на выполнение работ: {applicationInWork.Id}, {applicationInWork.ShortDescription}, {applicationInWork.Solution}, {applicationInWork.NextRun}"); + logger.LogInformation($"Пользователь {User.Identity?.Name} добавил задание на выполнение работ: {applicationInWork.Id}, {applicationInWork.ShortDescription}, {applicationInWork.Solution}"); var createdApplicationInWork = await applicationsInWorkService.Get() @@ -225,13 +237,18 @@ namespace PARR.API.Controllers.V1 if (orig == null) return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Ошибка при изменении задания на выполнение работ. Не найдено задание с Id: {id}" } })); + //Расписание было изменено, ниже добавим задание в очередь на обновление расписаний у связанных шаблонов + var isScheduleChanged = IsScheduleChanged(orig, request); + orig.WorkId = request.WorkId; orig.ApplicationId = request.ApplicationId; orig.TemplateDuration = request.TemplateDuration; orig.ShortDescription = request.ShortDescription.Trim(); orig.FullDescription = request.FullDescription.Trim(); orig.Solution = request.Solution.Trim(); - orig.NextRun = request.NextRun; + //orig.NextRun = request.NextRun; + orig.ReferenceDate = request.ReferenceDate; + orig.IsAutoDistributionEnabled = request.IsAutoDistributionEnabled; orig.IsAgent = request.IsAgent; orig.AgentName = request.AgentName?.Trim(); orig.AgentTimeOutSec = request.AgentTimeOutSec; @@ -267,9 +284,29 @@ namespace PARR.API.Controllers.V1 logger.LogInformation($"Пользователь {User.Identity?.Name} обновил задание на выполнение работ: {orig.Id}," + $" {orig.WorkId}, {orig.ApplicationId}, {orig.TemplateDuration}, {orig.ShortDescription}," + - $" {orig.FullDescription}, {orig.Solution}, {orig.NextRun}, {orig.IsAgent}, {orig.AgentName}, {orig.AgentTimeOutSec}, {orig.AgentScript}"); + $" {orig.FullDescription}, {orig.Solution}, {nameof(orig.IsAutoDistributionEnabled)}: {orig.IsAutoDistributionEnabled}, {orig.IsAgent}, {orig.AgentName}, {orig.AgentTimeOutSec}, {orig.AgentScript}"); + if (isScheduleChanged) + { + //расписание было обновлено, отправим задание в очередь на перерасчет NextRun + var requestToMq = new TemplateDistributorMq + { + ApplicationInWorkId = id + }; + + var msg = JsonSerializer.Serialize(requestToMq); + + logger.LogDebug($"Расписание в РР applicationInWorkId: {id} было изменено. Отправляем задание в очередь на перерасчет NextRun"); + + var sendResult = mqService.Send(mqSettings.TemplateDistributor, new[] { msg }); + + if (sendResult.IsSuccess) + logger.LogInformation($"Задание на перерасчет NextRun успешно отправлено в очередь MQ {mqSettings.TemplateDistributor.QueueName}"); + else + logger.LogError($"Ошибка при отправке задания на перерасчет NextRun в очередь MQ {mqSettings.TemplateDistributor.QueueName}"); + } + var updatedApplicationInWork = await applicationsInWorkService.Get() .Include(t => t.Application).ThenInclude(t => t!.ApplicationType) .Include(t => t.Work) @@ -317,10 +354,40 @@ namespace PARR.API.Controllers.V1 logger.LogInformation($"Пользователь {User.Identity?.Name} удалил задание на выполнение работ: {applicationsInWork.Id},{applicationsInWork.WorkId}," + $" {applicationsInWork.ApplicationId}, {applicationsInWork.TemplateDuration}, {applicationsInWork.ShortDescription}," + - $" {applicationsInWork.FullDescription}, {applicationsInWork.Solution}, {applicationsInWork.NextRun}, {applicationsInWork.IsAgent}," + + $" {applicationsInWork.FullDescription}, {applicationsInWork.Solution}, {nameof(applicationsInWork.IsAutoDistributionEnabled)}: {applicationsInWork.IsAutoDistributionEnabled}, {applicationsInWork.IsAgent}," + $" {applicationsInWork.AgentName}, {applicationsInWork.AgentTimeOutSec}, {applicationsInWork.AgentScript}"); return NoContent(); } + + + /// + /// Проверка, были ли изменения в расписании + /// + /// + /// + /// + private bool IsScheduleChanged(ApplicationsInWork orig, ApplicationInWorkRequest request) + { + var isScheduleChanged = false; + + if (orig.ReferenceDate != request.ReferenceDate) + isScheduleChanged = true; + + if (request.Schedule.Count() != orig.EsppSchValues.Count()) + isScheduleChanged = true; + + request.Schedule.ForEach(requestSchedule => + { + var schExist = orig.EsppSchValues.FirstOrDefault(t => t.ApplicationsInWorkId == orig.Id + && t.TypeValueId == requestSchedule.TypeValueId + && t.TypeConfigId == requestSchedule.TypeConfigId); + + if (schExist == null) + isScheduleChanged = true; + }); + + return isScheduleChanged; + } } } diff --git a/PARR.API/Controllers/V1/DistributorController.cs b/PARR.API/Controllers/V1/DistributorController.cs new file mode 100644 index 00000000..0a529956 --- /dev/null +++ b/PARR.API/Controllers/V1/DistributorController.cs @@ -0,0 +1,65 @@ +using FluentValidation; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using PARR.API.Contracts.V1; +using PARR.API.Contracts.V1.Requests; +using PARR.API.Contracts.V1.Responses.Base; +using PARR.API.Controllers.V1.Base; +using PARR.API.Settings; +using PARR.BLL.Domain.Mq; +using PARR.BLL.Services.Interfaces; +using PARR.Constants; +using System.Text.Json; + +namespace PARR.API.Controllers.V1 +{ + /// + /// Распределитель РР + /// + [Authorize(Roles = ParrRoles.Administrator.Role)] + public class DistributorController : BaseApiController + { + private readonly IMqService mqService; + private readonly MqSettings mqSettings; + private readonly IValidator validator; + + public DistributorController( + IMqService mqService, + MqSettings mqSettings, + IValidator validator + ) + { + this.mqService = mqService; + this.mqSettings = mqSettings; + this.validator = validator; + } + + + /// + /// Перераспределить шаблоны для регламентной работы + /// + /// + [HttpPost(ApiRoutes.Distributor.Distribute)] + public async Task Distribute([FromBody] DistributeRequest request) + { + var resultValidate = await validator.ValidateAsync(request); + if (!resultValidate.IsValid) + return BadRequest(new Response(resultValidate.Errors)); + + var requestToMq = new TemplateDistributorMq + { + ApplicationInWorkId = request.ApplicationInWorkId + }; + + var msg = JsonSerializer.Serialize(requestToMq); + + var sendResult = mqService.Send(mqSettings.TemplateDistributor, new[] { msg }); + + if (sendResult.IsSuccess) + return Created("", new Response(null, true, new List(), "Отправлен запрос на перераспределение регламентных работ.")); + else + return BadRequest(new Response(false, new List { new ErrorModel { Message="Ошибка при отправке данных."} })); + + } + } +} diff --git a/PARR.API/Controllers/V1/RobotTaskController.cs b/PARR.API/Controllers/V1/RobotTaskController.cs index 2f1b9b36..147acdf9 100644 --- a/PARR.API/Controllers/V1/RobotTaskController.cs +++ b/PARR.API/Controllers/V1/RobotTaskController.cs @@ -143,35 +143,52 @@ namespace PARR.API.Controllers.V1 private async Task UpdateLastNextRunDate(RobotConfiguration task) { - var appInWorks = task.Template!.ApplicationsInWork!; + var template = task.Template!; // передаем LastRun, если его нет, то NextRun //var nextRun = await esppScheduleTransformService.GetNextDateAsync(task.Template!.ApplicationInWorkId, task.Template.ApplicationsInWork!.LastRun ?? task.Template.ApplicationsInWork.NextRun); // всегда считаем по nextRun - var nextRun = await esppScheduleTransformService.GetNextDateAsync(task.Template!.ApplicationInWorkId, task.Template.ApplicationsInWork!.NextRun); + var nextRun = await esppScheduleTransformService.GetNextDateAsync(template.ApplicationInWorkId, template.NextRun); - if (nextRun != appInWorks.NextRun) + if (nextRun != template.NextRun) { - logger.LogDebug($"Для appilcationInWork id {task.Template.ApplicationInWorkId}, шаблона id {task.TemplateId} обновляю nextRun, новое значение {nextRun}, старое значение {appInWorks.NextRun}"); + logger.LogDebug($"Для шаблона id {template.Id} обновляю nextRun, новое значение {nextRun}, старое значение {template.NextRun}"); - if (appInWorks.NextRun < DateTimeOffset.UtcNow) + if (template.NextRun < DateTimeOffset.UtcNow) { - logger.LogDebug($"Для appilcationInWork id {task.Template.ApplicationInWorkId}, шаблона id {task.TemplateId} обновляю lastRun, новое значение {appInWorks.NextRun}, старое значение {appInWorks.LastRun}"); - appInWorks.LastRun = appInWorks.NextRun; + logger.LogDebug($"Для шаблона id {template.Id} обновляю lastRun, новое значение {template.NextRun}, старое значение {template.LastRun}"); + template.LastRun = template.NextRun; } - appInWorks.NextRun = nextRun; + template.NextRun = nextRun; await robotConfigurationService.CommitAsync(); } - //if (appInWorks.LastRun != null) // if - ерунда какая-то. ошибка видимо. - //if (nextRun > DateTimeOffset.UtcNow) - // appInWorks.LastRun = appInWorks.NextRun; + #region Old, до выноса lastRun, nextRun в templates + //var appInWorks = task.Template!.ApplicationsInWork!; - //appInWorks.NextRun = nextRun; + //// передаем LastRun, если его нет, то NextRun + ////var nextRun = await esppScheduleTransformService.GetNextDateAsync(task.Template!.ApplicationInWorkId, task.Template.ApplicationsInWork!.LastRun ?? task.Template.ApplicationsInWork.NextRun); + //// всегда считаем по nextRun + //var nextRun = await esppScheduleTransformService.GetNextDateAsync(task.Template!.ApplicationInWorkId, task.Template.ApplicationsInWork!.NextRun); + + //if (nextRun != appInWorks.NextRun) + //{ + // logger.LogDebug($"Для appilcationInWork id {task.Template.ApplicationInWorkId}, шаблона id {task.TemplateId} обновляю nextRun, новое значение {nextRun}, старое значение {appInWorks.NextRun}"); + + // if (appInWorks.NextRun < DateTimeOffset.UtcNow) + // { + // logger.LogDebug($"Для appilcationInWork id {task.Template.ApplicationInWorkId}, шаблона id {task.TemplateId} обновляю lastRun, новое значение {appInWorks.NextRun}, старое значение {appInWorks.LastRun}"); + // appInWorks.LastRun = appInWorks.NextRun; + // } + + // appInWorks.NextRun = nextRun; + + // await robotConfigurationService.CommitAsync(); + //} + #endregion - //await robotConfigurationService.CommitAsync(); } } } diff --git a/PARR.API/MappingProfiles/DomainToResponseProfile.cs b/PARR.API/MappingProfiles/DomainToResponseProfile.cs index 29c5d108..1101863a 100644 --- a/PARR.API/MappingProfiles/DomainToResponseProfile.cs +++ b/PARR.API/MappingProfiles/DomainToResponseProfile.cs @@ -33,9 +33,12 @@ namespace PARR.API.MappingProfiles .ForMember(d => d.SyncStatus, o => o.MapFrom(s => s.RobotConfigurations.OrderBy(t => t.RobotCode))) .ForMember(d => d.Schedule, o => o.MapFrom()) .ForMember(d => d.Agent, o => o.MapFrom(s => s.ApplicationsInWork)) - .ForMember(d => d.NextRun, o => o.MapFrom(s => s.ApplicationsInWork!.NextRun)) - .ForMember(d => d.LastRun, o => o.MapFrom(s => s.ApplicationsInWork!.LastRun)) - .ForMember(d => d.OrderCount, o => o.MapFrom(s => s.Orders.Count())); + //.ForMember(d => d.NextRun, o => o.MapFrom(s => s.ApplicationsInWork!.NextRun)) + //.ForMember(d => d.LastRun, o => o.MapFrom(s => s.ApplicationsInWork!.LastRun)) + .ForMember(d => d.NextRun, o => o.MapFrom(s => s.NextRun)) + .ForMember(d => d.LastRun, o => o.MapFrom(s => s.LastRun)) + .ForMember(d => d.OrderCount, o => o.MapFrom(s => s.Orders.Count())) + .ForMember(d=>d.IsAutoDistributionEnabled, o=>o.MapFrom(s=>s.ApplicationsInWork!.IsAutoDistributionEnabled)); // === Template === @@ -166,8 +169,12 @@ namespace PARR.API.MappingProfiles .ForMember(d => d.Timezone, o => o.MapFrom()) .ForMember(d => d.RepeatRange, o => o.MapFrom()) //.ForMember(d => d.GenerationTime, o => o.MapFrom(s => s.Template!.ApplicationsInWork!.ScheduleGenerationTime)) - .ForMember(d => d.GenerationTime, o => o.MapFrom(s => EsppScheduleHelpers.GetGenerationTime(s.Template!.ApplicationsInWork!.NextRun))) - .ForMember(d => d.NextStart, o => o.MapFrom(s => EsppScheduleHelpers.GetNextRun(s.Template!.ApplicationsInWork!.NextRun))) + //~~~~~~~~~ + //.ForMember(d => d.GenerationTime, o => o.MapFrom(s => EsppScheduleHelpers.GetGenerationTime(s.Template!.ApplicationsInWork!.NextRun))) + //.ForMember(d => d.NextStart, o => o.MapFrom(s => EsppScheduleHelpers.GetNextRun(s.Template!.ApplicationsInWork!.NextRun))) + //~~~~~~~~~ + .ForMember(d => d.GenerationTime, o => o.MapFrom(s => EsppScheduleHelpers.GetGenerationTime(s.Template!.NextRun))) + .ForMember(d => d.NextStart, o => o.MapFrom(s => EsppScheduleHelpers.GetNextRun(s.Template!.NextRun))) //.ForMember(d => d.NextStart, o => o.MapFrom(s => EsppScheduleHelpers.GetFirstStart(s.Template!.ApplicationsInWork!.ScheduleStartDate, s.Template!.ApplicationsInWork!.ScheduleGenerationTime))) //.ForMember(d => d.NextStart, o => o.MapFrom(s => EsppScheduleHelpers.GetNextScheduleStart(s.Template!.NextScheduleStart))) .ForMember(d => d.ScheduleType, o => o.MapFrom(s => diff --git a/PARR.API/MappingProfiles/Resolvers/TemplateScheduleResolver.cs b/PARR.API/MappingProfiles/Resolvers/TemplateScheduleResolver.cs index 9b4f1458..4480014e 100644 --- a/PARR.API/MappingProfiles/Resolvers/TemplateScheduleResolver.cs +++ b/PARR.API/MappingProfiles/Resolvers/TemplateScheduleResolver.cs @@ -38,8 +38,10 @@ namespace PARR.API.MappingProfiles.Resolvers var response = new EsppScheduleResponse { - NextRun = source.ApplicationsInWork!.NextRun, - LastRun = source.ApplicationsInWork.LastRun, + //NextRun = source.ApplicationsInWork!.NextRun, + //LastRun = source.ApplicationsInWork.LastRun, + NextRun = source.NextRun, + LastRun = source.LastRun, Timezone = settingsFromDb.ScheduleTimezone, TypeSchedule = mapper.Map(schedule.TypeSchedule), Values = mapper.Map>(schedule.Values).OrderBy(t => t.Order).ToList() diff --git a/PARR.API/Settings/MqSettings.cs b/PARR.API/Settings/MqSettings.cs index 7cee6dae..7989cb5c 100644 --- a/PARR.API/Settings/MqSettings.cs +++ b/PARR.API/Settings/MqSettings.cs @@ -5,6 +5,7 @@ namespace PARR.API.Settings public class MqSettings { public MqGenerateTemplates GenerateTemplates { get; set; } = new MqGenerateTemplates(); + public MqTemplateDistributor TemplateDistributor { get; set; } = new MqTemplateDistributor(); public MqStatistics Statistics { get; set; } = new MqStatistics(); } @@ -16,6 +17,14 @@ namespace PARR.API.Settings public string Password { get; set; } = string.Empty; } + public class MqTemplateDistributor : IMqSettings + { + public string HostName { get; set; } = string.Empty; + public string QueueName { get; set; } = string.Empty; + public string User { get; set; } = string.Empty; + public string Password { get; set; } = string.Empty; + } + public class MqStatistics { public StatMqAuth StatMqAuth { get; set; } = new StatMqAuth(); diff --git a/PARR.API/Validators/ApplicationInWorkValidator.cs b/PARR.API/Validators/ApplicationInWorkValidator.cs index f973f46f..f953c81d 100644 --- a/PARR.API/Validators/ApplicationInWorkValidator.cs +++ b/PARR.API/Validators/ApplicationInWorkValidator.cs @@ -1,5 +1,6 @@ using FluentValidation; using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json.Linq; using PARR.API.Contracts.V1.Requests; using PARR.DAL.Services.Interfaces; @@ -11,18 +12,21 @@ namespace PARR.API.Validators private readonly IWorkService workService; private readonly IEsppSchTypeConfigService esppSchTypeConfigService; private readonly IWorkGroupService workGroupService; + private readonly IEsppSchTypeValueService esppSchTypeValueService; public ApplicationInWorkValidator( IApplicationsInWorkService applicationsInWorkService, IWorkService workService, IEsppSchTypeConfigService esppSchTypeConfigService, - IWorkGroupService workGroupService + IWorkGroupService workGroupService, + IEsppSchTypeValueService esppSchTypeValueService ) { this.applicationsInWorkService = applicationsInWorkService; this.workService = workService; this.esppSchTypeConfigService = esppSchTypeConfigService; this.workGroupService = workGroupService; + this.esppSchTypeValueService = esppSchTypeValueService; RuleFor(t => t.TemplateDuration) .NotNull().NotEmpty()//.WithMessage("Длительность данного задания на выполнение работ не может быть пустым") @@ -96,8 +100,36 @@ namespace PARR.API.Validators RuleFor(t => t.WorkGroups) .MustAsync(async (entity, value, c) => await IsWorkGroupsExistAsync(entity)) .WithMessage("Указаные несуществующие Id рабочих групп"); + + + //Автораспределение может быть включено, только если у EsppSchTypeValues не пустое поле DistributionPeriodId + RuleFor(t => t.IsAutoDistributionEnabled) + .MustAsync(async (entity, value, c) => await IsAllowAutoDistributionEnabledAsync(entity, value)) + .WithMessage("Нельзя включить автораспределение для этого типа расписания"); } + + private async Task IsAllowAutoDistributionEnabledAsync(ApplicationInWorkRequest entity, bool value) + { + //Автораспределение может быть включено, только если у EsppSchTypeValues не пустое поле DistributionPeriodId + + if (value == false) + return true; + + //Распределение включено, смотрим, разрешено ли оно в расписании + //по идее, это расписание только с одним значением в EsppSchValues, но мы проверим у всех, но такого быть не может по хорошему + foreach (var item in entity.Schedule) + { + var schVal = await esppSchTypeValueService.GetAsync(item.TypeValueId); + if (schVal != null) + if (schVal.DistributionPeriodId == null) + return false; + } + + return true; + } + + private async Task IsWorkGroupsExistAsync(ApplicationInWorkRequest entity) { var result = await workGroupService.Get().Where(t => entity.WorkGroups.Contains(t.Id)).ToListAsync(); diff --git a/PARR.API/Validators/DistributeRequestValidator.cs b/PARR.API/Validators/DistributeRequestValidator.cs new file mode 100644 index 00000000..2f051a46 --- /dev/null +++ b/PARR.API/Validators/DistributeRequestValidator.cs @@ -0,0 +1,24 @@ +using FluentValidation; +using PARR.API.Contracts.V1.Requests; +using PARR.DAL.Services.Interfaces; + +namespace PARR.API.Validators +{ + public class DistributeRequestValidator : AbstractValidator + { + private readonly IApplicationsInWorkService applicationsInWorkService; + + public DistributeRequestValidator(IApplicationsInWorkService applicationsInWorkService) + { + this.applicationsInWorkService = applicationsInWorkService; + + + RuleFor(t => t.ApplicationInWorkId).NotEmpty().MustAsync(async (entity, value, c) => + { + var appInWork = await applicationsInWorkService.GetAsync(value); + + return appInWork != null; + }).WithMessage("Недопустимое значение"); + } + } +} diff --git a/PARR.API/appsettings.Development.json b/PARR.API/appsettings.Development.json index 56f9d92b..b1f1d5d6 100644 --- a/PARR.API/appsettings.Development.json +++ b/PARR.API/appsettings.Development.json @@ -24,6 +24,9 @@ "GenerateTemplates": { "HostName": "10.99.253.216" }, + "TemplateDistributor": { + "HostName": "10.99.253.216" + }, "Statistics": { "StatMqAuth": { "HostName": "10.99.253.216" diff --git a/PARR.API/appsettings.json b/PARR.API/appsettings.json index 31ed1be8..22c4c29d 100644 --- a/PARR.API/appsettings.json +++ b/PARR.API/appsettings.json @@ -45,6 +45,12 @@ "User": "generate_templates_api", "Password": "DHhjgdsf*&%95" }, + "TemplateDistributor": { + "HostName": "parr-rabbitmq", + "QueueName": "parr-template-distributor", + "User": "espp_template_distributor_writer", + "Password": "KGgsdfu%%44qqwew" + }, "Statistics": { "StatMqAuth": { "HostName": "parr-rabbitmq", diff --git a/PARR.DAL/Models/ApplicationsInWork.cs b/PARR.DAL/Models/ApplicationsInWork.cs index 0e4bbde1..9de4cd00 100644 --- a/PARR.DAL/Models/ApplicationsInWork.cs +++ b/PARR.DAL/Models/ApplicationsInWork.cs @@ -69,15 +69,15 @@ namespace PARR.DAL.Models public required string Solution { get; set; } - /// - /// Время последнего запуска скрипта/генерации наряда - /// - public DateTimeOffset? LastRun { get; set; } + ///// + ///// Время последнего запуска скрипта/генерации наряда + ///// + //public DateTimeOffset? LastRun { get; set; } - /// - /// Время следующего запуска скрипта/генерации наряда - /// - public DateTimeOffset NextRun { get; set; } + ///// + ///// Время следующего запуска скрипта/генерации наряда + ///// + //public DateTimeOffset NextRun { get; set; } /// /// Включить автораспределение @@ -89,7 +89,6 @@ namespace PARR.DAL.Models /// public DateTimeOffset ReferenceDate { get; set; } - /// /// Выполняет агент /// diff --git a/PARR.DAL/ParrDalInstaller.cs b/PARR.DAL/ParrDalInstaller.cs index 86519ec0..79394dde 100644 --- a/PARR.DAL/ParrDalInstaller.cs +++ b/PARR.DAL/ParrDalInstaller.cs @@ -82,6 +82,7 @@ namespace PARR.DAL services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); // TransformServices services.AddTransient(); diff --git a/PARR.DAL/Services/Implementations/EsppSchTypeValueService.cs b/PARR.DAL/Services/Implementations/EsppSchTypeValueService.cs new file mode 100644 index 00000000..cd177f68 --- /dev/null +++ b/PARR.DAL/Services/Implementations/EsppSchTypeValueService.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using PARR.DAL.Context; +using PARR.DAL.Models; +using PARR.DAL.Services.Abstracts; +using PARR.DAL.Services.Interfaces; + +namespace PARR.DAL.Services.Implementations +{ + internal class EsppSchTypeValueService : BaseService, IEsppSchTypeValueService + { + private readonly DataContext dataContext; + + protected override DbSet EntitySet => dataContext.EsppSchTypeValues; + + protected override DataContext EntitiContext => dataContext; + + public EsppSchTypeValueService(DataContext dataContext, ILogger logger): base(logger) + { + this.dataContext = dataContext; + } + } +} diff --git a/PARR.DAL/Services/Interfaces/IEsppSchTypeValueService.cs b/PARR.DAL/Services/Interfaces/IEsppSchTypeValueService.cs new file mode 100644 index 00000000..12746ddd --- /dev/null +++ b/PARR.DAL/Services/Interfaces/IEsppSchTypeValueService.cs @@ -0,0 +1,9 @@ +using PARR.DAL.Models; +using PARR.DAL.Services.Interfaces.Base; + +namespace PARR.DAL.Services.Interfaces +{ + public interface IEsppSchTypeValueService : IBaseService + { + } +} diff --git a/PARR.EsppOrderLoader/Services/OrderItemService.cs b/PARR.EsppOrderLoader/Services/OrderItemService.cs index 1559081a..f31a9429 100644 --- a/PARR.EsppOrderLoader/Services/OrderItemService.cs +++ b/PARR.EsppOrderLoader/Services/OrderItemService.cs @@ -53,7 +53,8 @@ namespace PARR.EsppOrderLoader.Services StatusCode = (int)orderStatusService.GetOrderStatusByName(esppOrder.Status), GenerateDate = template == null ? null - : CalcGenerationDate(template.ApplicationsInWork!.NextRun, template.ApplicationsInWork!.LastRun), + //: CalcGenerationDate(template.ApplicationsInWork!.NextRun, template.ApplicationsInWork!.LastRun), + : CalcGenerationDate(template.NextRun, template.LastRun), //CalcGenerationDate(esppOrder.ExpirationDateUTC, template.ApplicationsInWork!.TemplateDurationTimeSpan), ExpirationDate = esppOrder.ExpirationDateUTC }; diff --git a/PARR.EsppScheduleSync/ScheduleSyncher.cs b/PARR.EsppScheduleSync/ScheduleSyncher.cs index 9c7390a1..9c3513c0 100644 --- a/PARR.EsppScheduleSync/ScheduleSyncher.cs +++ b/PARR.EsppScheduleSync/ScheduleSyncher.cs @@ -88,7 +88,8 @@ namespace PARR.EsppScheduleSync WorkGroup = template.Host!.WorkGroup!.Name, //Мы решили, что для всех расписаний "Нет исключений", если что-то поменяется, тут нужно переделать TypeV60calendar = settingsFromDb.ScheduleExclude == "Нет исключений" ? "NONE" : "", - Scheduled = EsppScheduleHelpers.GetNextRun(template.ApplicationsInWork!.NextRun), + //Scheduled = EsppScheduleHelpers.GetNextRun(template.ApplicationsInWork!.NextRun), + Scheduled = EsppScheduleHelpers.GetNextRun(template.NextRun), Timezone = settingsFromDb.ScheduleTimezone, //Мы решили, что для всех расписаний "Отсутствует дата завершения", если что-то поменяется, тут нужно переделать TerminationType = settingsFromDb.ScheduleRepeatRange == "Отсутствует дата завершения" ? "forever" : "", diff --git a/PARR.GeneratorTemplates/PARR.GeneratorTemplates.csproj b/PARR.GeneratorTemplates/PARR.GeneratorTemplates.csproj index 21190861..766e58d3 100644 --- a/PARR.GeneratorTemplates/PARR.GeneratorTemplates.csproj +++ b/PARR.GeneratorTemplates/PARR.GeneratorTemplates.csproj @@ -9,6 +9,7 @@ + diff --git a/PARR.GeneratorTemplates/Services/TemplateManager.cs b/PARR.GeneratorTemplates/Services/TemplateManager.cs index d2c1b59b..029c07ec 100644 --- a/PARR.GeneratorTemplates/Services/TemplateManager.cs +++ b/PARR.GeneratorTemplates/Services/TemplateManager.cs @@ -7,6 +7,7 @@ using PARR.DAL.Contracts; using PARR.DAL.Extensions; using PARR.DAL.Models; using PARR.DAL.Services.Interfaces; +using PARR.TemplateDistributor; using System.Text.Json; namespace PARR.GeneratorTemplates.Services @@ -19,6 +20,7 @@ namespace PARR.GeneratorTemplates.Services private readonly SettingsFromDb settingsFromDb; private readonly IRobotConfigurationService robotConfigurationService; private readonly IApplicationsInWorkService applicationsInWorkService; + private readonly ITemplateDistributor templateDistributor; public TemplateManager( ITemplateService templateService, @@ -26,7 +28,8 @@ namespace PARR.GeneratorTemplates.Services ILogger logger, SettingsFromDb settingsFromDb, IRobotConfigurationService robotConfigurationService, - IApplicationsInWorkService applicationsInWorkService + IApplicationsInWorkService applicationsInWorkService, + ITemplateDistributor templateDistributor ) { this.templateService = templateService; @@ -35,6 +38,7 @@ namespace PARR.GeneratorTemplates.Services this.settingsFromDb = settingsFromDb; this.robotConfigurationService = robotConfigurationService; this.applicationsInWorkService = applicationsInWorkService; + this.templateDistributor = templateDistributor; } public async Task CreateTemplates(GeneratorTemplateMq query) @@ -95,7 +99,85 @@ namespace PARR.GeneratorTemplates.Services } - //Создаем шаблоны + // хосты группируем по РГ, чтоб потом распределить/рассчитать NextRun + var groupingHostByWg = hosts.GroupBy(t => t.WorkGroupId); + + foreach (var group in groupingHostByWg) + { + var groupingHosts = group.ToList(); + + var templates = CreateQueryTemplates(groupingHosts, query); + + //заполняем NextRun + templates = await templateDistributor.DistributeTemplateAsync(templates, appInWorkWg.Id, (Guid)group.Key!); + + // сохраняем + foreach (var template in templates) + { + if (!await templateService.CreateAsync(template) || !await templateService.CommitAsync()) + { + logger.LogError($"Ошибка при создании шаблона: Name: {template.Name}, ApplicationInWorkId: {template.ApplicationInWorkId}, HostId: {template.HostId}"); + } + else + { + logger.LogInformation($"Создан шаблон: Name: {template.Name}, ApplicationInWorkId: {template.ApplicationInWorkId}, " + + $"HostId: {template.HostId}"); + } + } + } + + #region создавали шаблоны до того, как стали группировать по РГ + ////Создаем шаблоны + //foreach (var host in hosts) + //{ + // var appInWork = host.ApplicationsInHosts.FirstOrDefault(t => t.ApplicationId == query.ApplicationId)?.Application?.ApplicationsInWorks.FirstOrDefault(a => a.WorkId == query.WorkId); + + // if (appInWork == null) + // { + // // такого не может быть, так как выполнился запрос при получении списка хостов, но мало ли + // logger.LogError($"Ошибка при создании шаблона. В таблице ApplicationsInWorks не найден Id согласно запроса WorkId: {query.WorkId}, ApplicationId: {query.ApplicationId}"); + // return; + // } + + // var template = new Template + // { + // Id = Guid.NewGuid(), + // //Name = TemplateHelpers.GenerateTemplateName("ЗО" ,settingsFromDb.TemplatePrefixName, host.Ek, appInWork.Work!.Name), + // //Генерить имя с полным именем работы и подстановкой ЭК + // //Name = TemplateHelpers.GenerateTemplateNameWithResponseArea(host.ResponseArea!.Name, settingsFromDb.TemplatePrefixName, host.Ek, appInWork.Work!.Name.ApplyShortcode(Constants.Shortcode.Shortcodes.EK, host.Ek)), + // //Вместо полного имени работы, подставляется сокращенный суффикс или полное имя с ЭК если суффикса нет + // Name = TemplateHelpers.GenerateTemplateNameWithResponseArea( + // host.ResponseArea!.Name, + // settingsFromDb.TemplatePrefixName, + // host.Ek, + // appInWork.Work!.TemplateSuffix ?? appInWork.Work!.Name.ApplyShortcode(Constants.Shortcode.Shortcodes.EK, host.Ek) + // ), + // IsActiveTemplate = query.IsActiveTemplate ?? false, + // IsActiveSchedule = query.IsActiveSchedule ?? false, + // ApplicationInWorkId = appInWork.Id, + // HostId = host.Id, + // //TODO: + // //NextRun = ???, + // LastRun = null + // }; + + // if (!await templateService.CreateAsync(template) || !await templateService.CommitAsync()) + // { + // logger.LogError($"Ошибка при создании шаблона: Name: {template.Name}, ApplicationInWorkId: {template.ApplicationInWorkId}, HostId: {template.HostId}"); + // } + // else + // { + // logger.LogInformation($"Создан шаблон: Name: {template.Name}, ApplicationInWorkId: {template.ApplicationInWorkId}, " + + // $"HostId: {template.HostId}"); + // } + //} + #endregion + } + + private List