diff --git a/PARR.API.sln b/PARR.API.sln index ce7ca48a..b5227cf3 100644 --- a/PARR.API.sln +++ b/PARR.API.sln @@ -15,6 +15,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PARR.AIHIT", "PARR.AIHIT\AI EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C170A32C-5A9D-4F1C-B6FF-FAE12736ED17}" ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore .gitlab-ci.yml = .gitlab-ci.yml NuGet.config = NuGet.config README.md = README.md @@ -30,9 +31,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PARR.EsppTemplateSync", "PA EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PARR.MockData", "PARR.MockData\PARR.MockData.csproj", "{B02E5B16-59BD-4F02-9CC4-6873409DBD84}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PARR.GeneratorTemplates", "PARR.GeneratorTemplates\PARR.GeneratorTemplates.csproj", "{79EC7AD8-9A16-4806-881A-E5632C48C461}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PARR.GeneratorTemplates", "PARR.GeneratorTemplates\PARR.GeneratorTemplates.csproj", "{79EC7AD8-9A16-4806-881A-E5632C48C461}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PARR.GeneratorTemplatesWorker", "PARR.GeneratorTemplatesWorker\PARR.GeneratorTemplatesWorker.csproj", "{BE11D005-D3FE-4F29-93A2-7379E729C78D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PARR.GeneratorTemplatesWorker", "PARR.GeneratorTemplatesWorker\PARR.GeneratorTemplatesWorker.csproj", "{BE11D005-D3FE-4F29-93A2-7379E729C78D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/PARR.API/Contracts/V1/ApiRoutes.cs b/PARR.API/Contracts/V1/ApiRoutes.cs index 9b1591f7..78f5dd02 100644 --- a/PARR.API/Contracts/V1/ApiRoutes.cs +++ b/PARR.API/Contracts/V1/ApiRoutes.cs @@ -63,6 +63,14 @@ public const string getParam = "{id}"; } + public static class TemplateRobotStatus + { + public const string Get = Base + "/templates/" + templateId + "/robot-statuses"; + public const string Update = Base + "/templates/" + templateId + "/robot-statuses"; + + public const string templateId = "{templateId}"; + } + public static class StatusTemplate { public const string GetAll = Base + "/template-statuses/"; diff --git a/PARR.API/Contracts/V1/Requests/TemplateRobotStatusRequest.cs b/PARR.API/Contracts/V1/Requests/TemplateRobotStatusRequest.cs new file mode 100644 index 00000000..53924870 --- /dev/null +++ b/PARR.API/Contracts/V1/Requests/TemplateRobotStatusRequest.cs @@ -0,0 +1,7 @@ +namespace PARR.API.Contracts.V1.Requests +{ + public class TemplateRobotStatusRequest + { + public int Code { get; set; } + } +} diff --git a/PARR.API/Contracts/V1/Responses/TemplateRobotStatusResponse.cs b/PARR.API/Contracts/V1/Responses/TemplateRobotStatusResponse.cs new file mode 100644 index 00000000..84536b44 --- /dev/null +++ b/PARR.API/Contracts/V1/Responses/TemplateRobotStatusResponse.cs @@ -0,0 +1,8 @@ +namespace PARR.API.Contracts.V1.Responses +{ + public class TemplateRobotStatusResponse : RobotStatusResponse + { + public int RobotAttemptsNumber { get; set; } + public DateTimeOffset? RobotLastStatusUpdated { get; set; } + } +} diff --git a/PARR.API/Controllers/V1/TemplateController.cs b/PARR.API/Controllers/V1/TemplateController.cs index bd20a7b0..f6fc2569 100644 --- a/PARR.API/Controllers/V1/TemplateController.cs +++ b/PARR.API/Controllers/V1/TemplateController.cs @@ -19,14 +19,17 @@ namespace PARR.API.Controllers.V1 { private readonly IMapper mapper; private readonly ITemplateService templateService; + private readonly SettingsFromDb settingsFromDb; public TemplateController( IMapper mapper, - ITemplateService templateService + ITemplateService templateService, + SettingsFromDb settingsFromDb ) { this.mapper = mapper; this.templateService = templateService; + this.settingsFromDb = settingsFromDb; } @@ -106,33 +109,37 @@ namespace PARR.API.Controllers.V1 { Template? template = null; - //1.Поиск шаблонов со `StatusCode` 10 или 20 и `RobotStatusCode` = 11.Находим, **выбрали эту запись, конец**. - //2.Поиск шаблонов со `StatusCode` 10 или 20 и `RobotStatusCode` = 22 - //3.Далее проверяется `RobotLastStatusUpdated`, что время последнего смены статуса не превышает допустимого(берется из настроек, поле `RobotWaitTime`) и что текущая попытка не больше разрешенной(берется из настроек, поле `RobotAttemptsNumber`) - если это так, берется эта запись. - //4.Если `RobotStatusCode` = 22 и `RobotLastStatusUpdated` истекло и `RobotAttemptsNumber` >= допустимому, то ставим `RobotStatusCode`= 33 + //1.Ищем `RobotStatusCode` = 22 и `RobotLastStatusUpdated` истекло и `RobotAttemptsNumber` >= допустимого значения из настроек, + //ставим всем этим записям `RobotStatusCode`= 33 + //2.Поиск шаблонов со `StatusCode` 10 или 20 и `RobotStatusCode` = 11.Находим, **выбрали эту запись, конец**. + //3.Поиск шаблонов со `StatusCode` 10 или 20 и `RobotStatusCode` = 22. + //Далее проверяется `RobotLastStatusUpdated`, что время последнего смены статуса не превышает допустимого(берется из настроек, поле `RobotWaitTime`) + //и что текущая попытка не больше разрешенной(берется из настроек, поле `RobotAttemptsNumber`) - если это так, берется эта запись. //1. + await templateService.CheckAndSetErrorRobotStatusAsync(settingsFromDb.RobotAttemptsNumber, settingsFromDb.RobotWaitTime); + + + //2. template = await templateService.GetWithIncludes() .AsSplitQuery() - .FirstOrDefaultAsync(t => t.StatusCode == statusCode && t.RobotStatusCode ==(int)RobotStatusEnum.Wait); + .FirstOrDefaultAsync(t => t.StatusCode == statusCode && t.RobotStatusCode == (int)RobotStatusEnum.Wait); - //if (template == null) - //{ - // // переходим к шагам 2,3 - // template = await templateService.GetWithIncludes() - // .AsSplitQuery() - // //todo - // .FirstOrDefaultAsync(t => t.StatusCode == statusCode && t.RobotStatusCode == (int)RobotStatusEnum.InProgress); - //} + //3. + if (template == null) + { + var endDate = DateTimeOffset.UtcNow.Add(-settingsFromDb.RobotWaitTime); + template = await templateService.GetWithIncludes() + .AsSplitQuery() + .FirstOrDefaultAsync(t => + t.StatusCode == statusCode + && t.RobotStatusCode == (int)RobotStatusEnum.InProgress + && t.RobotAttemptsNumber < settingsFromDb.RobotAttemptsNumber + && t.RobotLastStatusUpdated < endDate + ); + } - - ////Тут узкое место, так как возвращается первый, если будет много роботов, то будет возвращаться им один и тот же элемент. - ////нужно вводить статус, что эл уже занят - // template = await templateService.GetWithIncludes() - // .AsSplitQuery() - // .FirstOrDefaultAsync(t => t.StatusCode == statusCode); - if (template == null) return NotFound(); diff --git a/PARR.API/Controllers/V1/TemplateRobotStatusController.cs b/PARR.API/Controllers/V1/TemplateRobotStatusController.cs new file mode 100644 index 00000000..abeaa0bd --- /dev/null +++ b/PARR.API/Controllers/V1/TemplateRobotStatusController.cs @@ -0,0 +1,109 @@ +using AutoMapper; +using FluentValidation; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using PARR.API.Contracts.V1; +using PARR.API.Contracts.V1.Requests; +using PARR.API.Contracts.V1.Responses; +using PARR.API.Contracts.V1.Responses.Base; +using PARR.API.Controllers.V1.Base; +using PARR.DAL.Contracts; +using PARR.DAL.Services.Interfaces; + +namespace PARR.API.Controllers.V1 +{ + public class TemplateRobotStatusController : BaseApiController + { + private readonly IMapper mapper; + private readonly ITemplateService templateService; + private readonly IValidator validator; + + public TemplateRobotStatusController( + IMapper mapper, + ITemplateService templateService, + IValidator validator + ) + { + this.mapper = mapper; + this.templateService = templateService; + this.validator = validator; + } + + + /// + /// Получить статус робота по Id шаблона + /// + /// + /// + [HttpGet(ApiRoutes.TemplateRobotStatus.Get)] + public async Task Get([FromRoute] Guid templateId) + { + var template = await templateService.Get() + .Include(t => t.RobotStatus) + .FirstOrDefaultAsync(t => t.Id == templateId); + + if (template == null) + return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Не найден шаблон с id: {templateId}" } })); + + var response = mapper.Map(template); + + return Ok(new Response(response, true)); + } + + + /// + /// Обновить статус робота по Id шаблона + /// + /// + /// + [HttpPut(ApiRoutes.TemplateRobotStatus.Update)] + public async Task UpdateStatus([FromRoute] Guid templateId, [FromBody] TemplateRobotStatusRequest request) + { + var resultValidate = await validator.ValidateAsync(request); + if (!resultValidate.IsValid) + return BadRequest(new Response(resultValidate.Errors)); + + var template = await templateService.Get() + .FirstOrDefaultAsync(t => t.Id == templateId); + + if (template == null) + return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Не найден шаблон с id: {templateId}" } })); + + switch (request.Code) + { + case (int)RobotStatusEnum.InProgress: + template.RobotAttemptsNumber++; + template.RobotStatusCode = request.Code; + template.RobotLastStatusUpdated = DateTimeOffset.UtcNow; + break; + case (int)RobotStatusEnum.Error: + template.RobotStatusCode = request.Code; + break; + case (int)RobotStatusEnum.Complete: + template.RobotStatusCode = request.Code; + template.RobotLastStatusUpdated = DateTimeOffset.UtcNow; + break; + case (int)RobotStatusEnum.Wait: + template.RobotStatusCode = request.Code; + template.RobotLastStatusUpdated = null; + template.RobotAttemptsNumber = 0; + break; + default: + break; + } + + if (!await templateService.CommitAsync()) + return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Ошибка при изменении статуса шаблону {templateId}" } })); + + var templateToResponse = await templateService.Get() + .Include(t => t.RobotStatus) + .FirstOrDefaultAsync(t => t.Id == templateId); + + var response = mapper.Map(templateToResponse); + + return Ok(new Response(response, true)); + } + + + } +} diff --git a/PARR.API/MappingProfiles/DomainToResponseProfile.cs b/PARR.API/MappingProfiles/DomainToResponseProfile.cs index a94dd9ef..5a8fc9a9 100644 --- a/PARR.API/MappingProfiles/DomainToResponseProfile.cs +++ b/PARR.API/MappingProfiles/DomainToResponseProfile.cs @@ -65,6 +65,11 @@ namespace PARR.API.MappingProfiles CreateMap(); + CreateMap() + .ForMember(d => d.Code, o => o.MapFrom(s => s.RobotStatus!.Code)) + .ForMember(d => d.Name, o => o.MapFrom(s => s.RobotStatus!.Name)) + .ForMember(d => d.Description, o => o.MapFrom(s => s.RobotStatus!.Description)); + CreateMap(); CreateMap(); diff --git a/PARR.API/Validators/TemplateRobotStatusRequestValidator.cs b/PARR.API/Validators/TemplateRobotStatusRequestValidator.cs new file mode 100644 index 00000000..bbf2839e --- /dev/null +++ b/PARR.API/Validators/TemplateRobotStatusRequestValidator.cs @@ -0,0 +1,15 @@ +using FluentValidation; +using PARR.API.Contracts.V1.Requests; +using PARR.DAL.Contracts; + +namespace PARR.API.Validators +{ + public class TemplateRobotStatusRequestValidator : AbstractValidator + { + public TemplateRobotStatusRequestValidator() + { + var statuses = new List { (int)RobotStatusEnum.Wait, (int)RobotStatusEnum.InProgress, (int)RobotStatusEnum.Error, (int)RobotStatusEnum.Complete }; + RuleFor(t => t.Code).Must(t => statuses.Contains(t)).WithMessage($"Допустимые значения: {string.Join(", ", statuses)}"); + } + } +} diff --git a/PARR.DAL/Services/Implementations/TemplateService.cs b/PARR.DAL/Services/Implementations/TemplateService.cs index 95a2e392..e1ace5be 100644 --- a/PARR.DAL/Services/Implementations/TemplateService.cs +++ b/PARR.DAL/Services/Implementations/TemplateService.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using PARR.DAL.Context; +using PARR.DAL.Contracts; using PARR.DAL.Models; using PARR.DAL.Services.Abstracts; using PARR.DAL.Services.Interfaces; @@ -34,12 +35,44 @@ namespace PARR.DAL.Services.Implementations .ThenInclude(t => t!.ResponseArea) .Include(h => h.Host) .ThenInclude(t => t!.EkStatus) - .Include(r=>r.RobotStatus) + .Include(r => r.RobotStatus) .Include(a => a.ApplicationsInWork) .ThenInclude(w => w!.Work) .ThenInclude(t => t!.Tnk) .ThenInclude(s => s!.Subprocess) .ThenInclude(p => p!.Process); } + + public async Task CheckAndSetErrorRobotStatusAsync(int robotAttemptsNumber, TimeSpan robotWaitTime) + { + //Ищем `RobotStatusCode` = 22 и `RobotLastStatusUpdated` истекло и `RobotAttemptsNumber` >= допустимого значения из настроек, + //ставим всем этим записям `RobotStatusCode`= 33 + + var endDate = DateTimeOffset.UtcNow.Add(-robotWaitTime); + + var templates = await EntitySet.Where(t => + t.RobotStatusCode == (int)RobotStatusEnum.InProgress + && t.RobotAttemptsNumber >= robotAttemptsNumber + && t.RobotLastStatusUpdated <= endDate + ) + .ToListAsync(); + + if (!templates.Any()) + return; + + foreach (var template in templates) + { + template.RobotStatusCode = (int)RobotStatusEnum.Error; + template.DateModified = DateTimeOffset.UtcNow; + logger.LogInformation($"Устанавливаю RobotStatus: {RobotStatusEnum.Error} для шаблона {template.Name}"); + } + + + var result = await CommitAsync(); + + if (!result) + logger.LogError($"Ошибка при сохранении изменений RobotStatus у шаблонов на {RobotStatusEnum.Error}"); + + } } } diff --git a/PARR.DAL/Services/Interfaces/ITemplateService.cs b/PARR.DAL/Services/Interfaces/ITemplateService.cs index 77777eca..aee39104 100644 --- a/PARR.DAL/Services/Interfaces/ITemplateService.cs +++ b/PARR.DAL/Services/Interfaces/ITemplateService.cs @@ -5,6 +5,14 @@ namespace PARR.DAL.Services.Interfaces { public interface ITemplateService : IBaseService