feat(all): исправления после добавления таблицы WorkGroup. Доработана логика генерации шаблонов с учетом рабочих групп

This commit is contained in:
Mikhail Trubnikov
2024-06-04 12:03:15 +10:00
parent 90948108c1
commit 7deef9a264
15 changed files with 227 additions and 20 deletions

View File

@@ -107,6 +107,14 @@
public const string getParam = "{id}";
}
public static class WorkGroup
{
public const string GetAll = Base + "/work-groups/";
public const string Get = Base + "/work-groups/" + getParam;
public const string getParam = "{id}";
}
public static class RobotHistoryLevel
{
public const string GetAll = Base + "/robot-history-levels/";

View File

@@ -28,6 +28,8 @@
//public required ScheduleRequest Schedule { get; set; }
public List<EsppSchValueRequest> Schedule { get; set; } = new List<EsppSchValueRequest>();
public List<Guid> WorkGroups { get; set; } = new List<Guid>();
}

View File

@@ -0,0 +1,10 @@
namespace PARR.API.Contracts.V1.Requests.Queries
{
public class WorkGroupQuery
{
/// <summary>
/// Поиск по имени, например "АСУ"
/// </summary>
public string? Name { get; set; }
}
}

View File

@@ -31,6 +31,8 @@
public int TemplatesCount { get; set; }
public ApplicationInWorkScheduleResponse? Schedule { get; set; }
public List<WorkGroupResponse>? WorkGroups { get; set; }
}

View File

@@ -10,7 +10,9 @@
public string? Status { get; set; }
public string? WorkGroup { get; set; }
public string? WorkGroupName { get; set; }
public WorkGroupResponse? WorkGroup { get; set; }
public string? ResponseArea { get; set; }

View File

@@ -0,0 +1,9 @@
namespace PARR.API.Contracts.V1.Responses
{
public class WorkGroupResponse
{
public Guid Id { get; set; }
public required string Name { get; set; }
}
}

View File

@@ -58,7 +58,8 @@ namespace PARR.API.Controllers.V1
{
query = query.Include(t => t.Application).ThenInclude(t => t!.ApplicationType)
.Include(t => t.Work)
.Include(t => t.Templates);
.Include(t => t.Templates)
.Include(t => t.WorkGroups).ThenInclude(t => t.WorkGroup);
}
query = query.OrderBy(t => t.ShortDescription);
@@ -93,6 +94,7 @@ namespace PARR.API.Controllers.V1
.Include(t => t.Application).ThenInclude(t => t!.ApplicationType)
.Include(t => t.Work)
.Include(t => t.Templates)
.Include(t => t.WorkGroups).ThenInclude(t => t.WorkGroup)
.FirstOrDefaultAsync(t => t.Id == id);
if (applicationInWork == null)
@@ -149,6 +151,16 @@ namespace PARR.API.Controllers.V1
});
});
//Добавляем рабочие группы
request.WorkGroups.ForEach(workGroupId =>
{
applicationInWork.WorkGroups.Add(new AppInWorkInWorkGroup
{
ApplicationsInWorkId = applicationInWork.Id,
WorkGroupId = workGroupId
});
});
if (!await applicationsInWorkService.CreateAsync(applicationInWork) || !await applicationsInWorkService.CommitAsync())
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Ошибка при созании задания на выполнение работ" } }));
@@ -159,6 +171,7 @@ namespace PARR.API.Controllers.V1
.Include(t => t.Application).ThenInclude(t => t!.ApplicationType)
.Include(t => t.Work)
.Include(t => t.Templates)
.Include(t => t.WorkGroups).ThenInclude(t => t.WorkGroup)
.FirstAsync(t => t.Id == applicationInWork.Id);
var locationUri = uriService.GetUri(ApiRoutes.Job.Get, ApiRoutes.Job.getParam, createdApplicationInWork.Id);
@@ -215,6 +228,18 @@ namespace PARR.API.Controllers.V1
});
});
//Обновляем рабочие группы
orig.WorkGroups.Clear();
request.WorkGroups.ForEach(workGroupId =>
{
orig.WorkGroups.Add(new AppInWorkInWorkGroup
{
ApplicationsInWorkId = orig.Id,
WorkGroupId = workGroupId
});
});
if (!await applicationsInWorkService.CommitAsync())
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Ошибка при изменении задания на выполнение работ." } }));
@@ -227,6 +252,7 @@ namespace PARR.API.Controllers.V1
.Include(t => t.Application).ThenInclude(t => t!.ApplicationType)
.Include(t => t.Work)
.Include(t => t.Templates)
.Include(t => t.WorkGroups).ThenInclude(t => t.WorkGroup)
.FirstAsync(t => t.Id == orig.Id);
return Ok(new Response<ApplicationInWorkResponse>(mapper.Map<ApplicationInWorkResponse>(updatedApplicationInWork), true));

View File

@@ -63,19 +63,21 @@ namespace PARR.API.Controllers.V1
// шаблоны
query = query.Include(t => t.Template)
.ThenInclude(t => t!.Host).ThenInclude(t => t!.ResponseArea)
.ThenInclude(t => t!.Hosts).ThenInclude(t => t.WorkGroupItem)
.Include(t => t.Template)
.ThenInclude(a => a!.ApplicationsInWork)
.ThenInclude(w => w!.Work)
.ThenInclude(t => t!.Tnk)
.ThenInclude(s => s!.Subprocess)
.ThenInclude(p => p!.Process)
.AsSplitQuery();
.ThenInclude(p => p!.Process);
//.AsSplitQuery();
break;
case RobotsEnum.ScheduleOrder:
//расписание
// тут не делаем AsSplitQuery, не может подтянуть все таблицы
query = query.Include(t => t.Template)
.ThenInclude(t => t!.Host).ThenInclude(t => t!.ResponseArea)
.ThenInclude(t => t!.Hosts).ThenInclude(t => t.WorkGroupItem)
.Include(t => t.Template)
.ThenInclude(t => t!.ApplicationsInWork)
.ThenInclude(t => t!.EsppSchValues)

View File

@@ -0,0 +1,83 @@
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using PARR.API.Contracts.V1;
using PARR.API.Contracts.V1.Requests.Queries;
using PARR.API.Contracts.V1.Responses;
using PARR.API.Contracts.V1.Responses.Base;
using PARR.API.Controllers.V1.Base;
using PARR.API.Extensions;
using PARR.Constants;
using PARR.DAL.DomainModels;
using PARR.DAL.Models;
using PARR.DAL.Services.Interfaces;
namespace PARR.API.Controllers.V1
{
/// <summary>
/// Рабочие группы
/// </summary>
[Authorize(Roles = ParrRoles.Administrator.Role)]
public class WorkGroupController : BaseApiController
{
private readonly IMapper mapper;
private readonly IWorkGroupService workGroupService;
public WorkGroupController(
IMapper mapper,
IWorkGroupService workGroupService
)
{
this.mapper = mapper;
this.workGroupService = workGroupService;
}
/// <summary>
/// Список рабочих групп постранично
/// </summary>
/// <returns></returns>
[HttpGet(ApiRoutes.WorkGroup.GetAll)]
public async Task<IActionResult> GetAll([FromQuery] PaginationQuery paginationQuery, [FromQuery] WorkGroupQuery filter)
{
var paginationFilter = mapper.Map<PaginationFilter>(paginationQuery);
IQueryable<WorkGroup> query = workGroupService.Get()
.OrderBy(t => t.Name);
if (!string.IsNullOrEmpty(filter.Name))
query = query.Where(t => t.Name.ToLower().Contains(filter.Name.ToLower()));
var workGroups = await workGroupService.GetPage(query, paginationFilter).ToListAsync();
if (!workGroups.Any())
return NoContent();
var workGroupResponse = mapper.Map<List<WorkGroupResponse>>(workGroups);
var paginationResponse = new PagedResponse<WorkGroupResponse>(workGroupResponse, true).GetPaginatedProps(paginationFilter, query);
return Ok(paginationResponse);
}
/// <summary>
/// Получить рабочую группу по id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet(ApiRoutes.WorkGroup.Get)]
public async Task<IActionResult> Get([FromRoute] Guid id)
{
var workGroup = await workGroupService.GetAsync(id);
if (workGroup == null)
return NotFound();
var response = mapper.Map<WorkGroupResponse>(workGroup);
return Ok(new Response<WorkGroupResponse>(response, true));
}
}
}

View File

@@ -81,20 +81,26 @@ namespace PARR.API.MappingProfiles
CreateMap<ApplicationType, ApplicationTypeResponse>();
#region Host
#region Host
// --- Host ---
CreateMap<DAL.Models.Host, HostBaseResponse>()
.Include<DAL.Models.Host, HostTemplateResponse>()
.Include<DAL.Models.Host, HostWithApplicationsResponse>()
.ForMember(d => d.Status, o => o.MapFrom(s => s.EkStatus!.Name))
.ForMember(d => d.ResponseArea, o => o.MapFrom(s => s.ResponseArea!.Name));
.ForMember(d => d.ResponseArea, o => o.MapFrom(s => s.ResponseArea!.Name))
.ForMember(d => d.WorkGroupName, o => o.MapFrom(s => s.WorkGroupItem.Name))
.ForMember(d => d.WorkGroup, o => o.MapFrom(s => s.WorkGroupItem));
CreateMap<DAL.Models.Host, HostTemplateResponse>();
CreateMap<DAL.Models.Host, HostWithApplicationsResponse>()
.ForMember(d => d.Applications, o => o.MapFrom(s => s.ApplicationsInHosts.Select(t => t.Application).OrderBy(t => t.Name)));
// === Host ===
#endregion
#region WorkGroup
CreateMap<WorkGroup, WorkGroupResponse>();
#endregion
@@ -133,7 +139,7 @@ namespace PARR.API.MappingProfiles
.ForMember(d => d.TemplateDuration, o => o.MapFrom(s => s.Template!.ApplicationsInWork!.TemplateDuration))
.ForMember(d => d.Initiator, o => o.MapFrom<RobotTaskTemplateInitiatorResolver>())
.ForMember(d => d.Category, o => o.MapFrom<RobotTaskTemplateCategoryResolver>())
.ForMember(d => d.WorkGroup, o => o.MapFrom(s => s.Template!.Host!.WorkGroup))
.ForMember(d => d.WorkGroup, o => o.MapFrom(s => s.Template!.Host!.WorkGroupItem!.Name))
.ForMember(d => d.Ek, o => o.MapFrom(s => s.Template!.Host!.Ek))
.ForMember(d => d.Name, o => o.MapFrom(s => s.Template!.Name))
.ForMember(d => d.ProcessName, o => o.MapFrom(s => s.Template!.ApplicationsInWork!.Work!.Tnk!.Subprocess!.Process!.Name))
@@ -154,7 +160,7 @@ namespace PARR.API.MappingProfiles
.ForMember(d => d.ResponseArea, o => o.MapFrom(s => s.Template!.Host!.ResponseArea!.Name))
.ForMember(d => d.TemplateName, o => o.MapFrom(s => s.Template!.Name))
.ForMember(d => d.TemplateId, o => o.MapFrom(s => s.Template!.Id))
.ForMember(d => d.WorkGroup, o => o.MapFrom(s => s.Template!.Host!.WorkGroup))
.ForMember(d => d.WorkGroup, o => o.MapFrom(s => s.Template!.Host!.WorkGroupItem!.Name))
.ForMember(d => d.Exclude, o => o.MapFrom<RobotTaskScheduleExcludeResolver>())
.ForMember(d => d.Timezone, o => o.MapFrom<RobotTaskScheduleTimezoneResolver>())
.ForMember(d => d.RepeatRange, o => o.MapFrom<RobotTaskScheduleRepeatRangeResolver>())
@@ -251,7 +257,8 @@ namespace PARR.API.MappingProfiles
.ForMember(d => d.Work, o => o.MapFrom(s => s.Work))
.ForMember(d => d.Application, o => o.MapFrom(s => s.Application))
.ForMember(d => d.TemplatesCount, o => o.MapFrom(s => s.Templates.Count()))
.ForMember(d => d.Schedule, o => o.MapFrom<ApplicationInWorkScheduleResolver>());
.ForMember(d => d.Schedule, o => o.MapFrom<ApplicationInWorkScheduleResolver>())
.ForMember(d => d.WorkGroups, o => o.MapFrom(s => s.WorkGroups.OrderBy(t => t.WorkGroup.Name).Select(t => t.WorkGroup)));
#endregion

View File

@@ -1,6 +1,6 @@
using FluentValidation;
using Microsoft.EntityFrameworkCore;
using PARR.API.Contracts.V1.Requests;
using PARR.API.Contracts.V1.Responses.Base;
using PARR.DAL.Services.Interfaces;
namespace PARR.API.Validators
@@ -10,17 +10,19 @@ namespace PARR.API.Validators
private readonly IApplicationsInWorkService applicationsInWorkService;
private readonly IWorkService workService;
private readonly IEsppSchTypeConfigService esppSchTypeConfigService;
private readonly IWorkGroupService workGroupService;
public ApplicationInWorkValidator(
IApplicationsInWorkService applicationsInWorkService,
IWorkService workService,
IEsppSchTypeConfigService esppSchTypeConfigService
IEsppSchTypeConfigService esppSchTypeConfigService,
IWorkGroupService workGroupService
)
{
this.applicationsInWorkService = applicationsInWorkService;
this.workService = workService;
this.esppSchTypeConfigService = esppSchTypeConfigService;
this.workGroupService = workGroupService;
RuleFor(t => t.TemplateDuration)
.NotNull().NotEmpty()//.WithMessage("Длительность данного задания на выполнение работ не может быть пустым")
@@ -45,8 +47,8 @@ namespace PARR.API.Validators
RuleFor(t => t.ApplicationId)
.MustAsync(async (entity, value, c) => await IsApplicationExist(entity))
.WithMessage("Указан несуществующий Id программного обеспечения");
//.MustAsync(async (entity, value, c) => await AppInWorkIsUnique(entity))
// .WithMessage("Задание на выполнение работ с такими ПО и работой уже существует");
//.MustAsync(async (entity, value, c) => await AppInWorkIsUnique(entity))
// .WithMessage("Задание на выполнение работ с такими ПО и работой уже существует");
//.WithMessage("У данного задания на выполнение работ указан несуществующий Id программного обеспечения");
@@ -89,6 +91,18 @@ namespace PARR.API.Validators
// .When(t => t.Schedule != null).WithMessage("Не удалось найти подходящую конфигруцию планировщика задания на выполнение работ");
#endregion
RuleFor(t => t.WorkGroups)
.MustAsync(async (entity, value, c) => await IsWorkGroupsExistAsync(entity))
.WithMessage("Указаные несуществующие Id рабочих групп");
}
private async Task<bool> IsWorkGroupsExistAsync(ApplicationInWorkRequest entity)
{
var result = await workGroupService.Get().Where(t => entity.WorkGroups.Contains(t.Id)).ToListAsync();
return entity.WorkGroups.Count() == result.Count();
}
//private bool IsEsppSchValuesExist(ApplicationInWorkRequest request)

View File

@@ -27,7 +27,7 @@ namespace PARR.DAL.Services.Implementations
public async Task<Template?> GetTemplateByNameAsync(string name)
{
return await GetWithIncludes()
.Include(t=>t.RobotConfigurations)
.Include(t => t.RobotConfigurations)
.FirstOrDefaultAsync(t => t.Name == name);
}
@@ -36,6 +36,8 @@ namespace PARR.DAL.Services.Implementations
return Get()
.Include(h => h.Host)
.ThenInclude(t => t!.ResponseArea)
.Include(h => h.Host)
.ThenInclude(t => t!.WorkGroupItem)
.Include(h => h.Host)
.ThenInclude(t => t!.EkStatus)
.Include(a => a.ApplicationsInWork)

View File

@@ -84,7 +84,8 @@ namespace PARR.EsppScheduleSync
ScheduleName = template.Name,
IsActive = template.IsActiveSchedule,
ResponseArea = template.Host!.ResponseArea!.Name,
WorkGroup = template.Host!.WorkGroup!,
//WorkGroup = template.Host!.WorkGroup!,
WorkGroup = template.Host!.WorkGroupItem!.Name,
//Мы решили, что для всех расписаний "Нет исключений", если что-то поменяется, тут нужно переделать
TypeV60calendar = settingsFromDb.ScheduleExclude == "Нет исключений" ? "NONE" : "",
Scheduled = EsppScheduleHelpers.GetNextRun(template.ApplicationsInWork!.NextRun),

View File

@@ -73,7 +73,8 @@ namespace PARR.EsppTemplateSync
{
TemplateName = template.Name,
IsActive = template.IsActiveTemplate,
WorkGroup = template.Host!.WorkGroup!,
//WorkGroup = template.Host!.WorkGroup!,
WorkGroup = template.Host!.WorkGroupItem!.Name,
ShortDescription = Normalize(template.ApplicationsInWork!.ShortDescription),
ResponseArea = template.Host!.ResponseArea!.Name,
Duration = template.ApplicationsInWork.TemplateDuration,

View File

@@ -18,13 +18,15 @@ namespace PARR.GeneratorTemplates.Services
private readonly ILogger<TemplateManager> logger;
private readonly SettingsFromDb settingsFromDb;
private readonly IRobotConfigurationService robotConfigurationService;
private readonly IApplicationsInWorkService applicationsInWorkService;
public TemplateManager(
ITemplateService templateService,
IHostService hostService,
ILogger<TemplateManager> logger,
SettingsFromDb settingsFromDb,
IRobotConfigurationService robotConfigurationService
IRobotConfigurationService robotConfigurationService,
IApplicationsInWorkService applicationsInWorkService
)
{
this.templateService = templateService;
@@ -32,10 +34,24 @@ namespace PARR.GeneratorTemplates.Services
this.logger = logger;
this.settingsFromDb = settingsFromDb;
this.robotConfigurationService = robotConfigurationService;
this.applicationsInWorkService = applicationsInWorkService;
}
public async Task CreateTemplates(GeneratorTemplateMq query)
{
//В таблице AppInWork находим РР, и смотрим ее рабочие группы. Затем по ним будем фильтровать хосты
var appInWorkWg = await applicationsInWorkService.Get()
.Include(t => t.WorkGroups)
.FirstAsync(t => t.ApplicationId == query.ApplicationId && t.WorkId == query.WorkId);
if (!appInWorkWg.WorkGroups.Any())
{
logger.LogWarning($"Не генерирую шаблоны. Нет рабочих групп у ApplicationInWork с id: {appInWorkWg.Id}");
return;
}
var workGroups = appInWorkWg.WorkGroups.ToList();
//Согласно параметрам, находит все ЭК в таблице Hosts.
//Далее проверяет есть ли такие шаблоны в таблице Templates, если нет, то создает, активирует и ставит статус Creating.
//Если такие шаблоны уже есть, ничего с ними не делает.
@@ -44,6 +60,7 @@ namespace PARR.GeneratorTemplates.Services
// список хостов для создания шаблонов
var queryHosts = hostService.Get()
//.Include(t => t.WorkGroupItem)
.Include(t => t.ApplicationsInHosts)
.ThenInclude(t => t.Application).ThenInclude(t => t!.ApplicationsInWorks).ThenInclude(w => w.Work)
.Include(t => t.Templates)
@@ -57,6 +74,8 @@ namespace PARR.GeneratorTemplates.Services
&& EF.Functions.Like(t.Ek.ToLower(), ekPattern)
// смотрим что нет такого шаблона
&& t.Templates.Any(templ => templ.ApplicationsInWork!.WorkId == query.WorkId && templ.ApplicationsInWork.ApplicationId == query.ApplicationId) == false
// смотрим что у этих хостов есть нужные группы
//&& workGroups.Any(x => x.WorkGroupId == t.WorkGroupId)
);
// статус ЭК
@@ -67,6 +86,8 @@ namespace PARR.GeneratorTemplates.Services
.AsSplitQuery()
.ToListAsync();
// Исключаем хосты у которых нет нужных групп
hosts = hosts.Where(t => workGroups.Any(x => x.WorkGroupId == t.WorkGroupId)).ToList();
if (!hosts.Any())
{
logger.LogInformation($"Не найдено ЭК попадающих под условие: ${JsonSerializer.Serialize(query)}");
@@ -112,6 +133,20 @@ namespace PARR.GeneratorTemplates.Services
public async Task DeactivateTemplates(GeneratorTemplateMq query)
{
//В таблице AppInWork находим РР, и смотрим ее рабочие группы. Затем по ним будем фильтровать хосты
var appInWorkWg = await applicationsInWorkService.Get()
.Include(t => t.WorkGroups)
.FirstAsync(t => t.ApplicationId == query.ApplicationId && t.WorkId == query.WorkId);
if (!appInWorkWg.WorkGroups.Any())
{
logger.LogWarning($"Не деактивируем шаблоны. Нет рабочих групп у ApplicationInWork с id: {appInWorkWg.Id}");
return;
}
var workGroups = appInWorkWg.WorkGroups.ToList();
//Ищет все активные шаблоны согласно параметрам в таблице Templates, и деактивирует их. Меняем на статус Updating
// делаем sql формат для like из запроса.
@@ -136,6 +171,9 @@ namespace PARR.GeneratorTemplates.Services
.AsSplitQuery()
.ToListAsync();
// Исключаем хосты у которых нет нужных групп
existTemplates = existTemplates.Where(t => workGroups.Any(x => x.WorkGroupId == t.Host.WorkGroupId)).ToList();
if (!existTemplates.Any())
{
logger.LogInformation("Нет шаблонов для деактивации");