+2
-1
@@ -7,7 +7,8 @@ public sealed record ResolvedRouteStepDto(
|
||||
int OrderNo,
|
||||
string? ExecutorEmail,
|
||||
string? Position,
|
||||
bool AllowReassignToOtherApprovers);
|
||||
bool AllowReassignToOtherApprovers,
|
||||
string? Warning = null);
|
||||
|
||||
public sealed record ResolveTemplateRouteResultDto(
|
||||
int RouteId,
|
||||
|
||||
@@ -197,14 +197,26 @@ public sealed class TemplateRouteService(
|
||||
IReadOnlyList<ProjectExecutorInfo> projectExecutors, CancellationToken cancellationToken)
|
||||
{
|
||||
string? resolvedEmail;
|
||||
string? warning = null;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(step.ExecutorEmail))
|
||||
{
|
||||
resolvedEmail = step.ExecutorEmail;
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(step.Position))
|
||||
{
|
||||
resolvedEmail = await portalClient.GetEmployeeByPositionAsync(step.Position, initiatorCompany, cancellationToken);
|
||||
if (string.IsNullOrWhiteSpace(resolvedEmail))
|
||||
warning = $"Не найден сотрудник на должности «{step.Position}» в компании «{initiatorCompany}».";
|
||||
}
|
||||
else if (step.ProjectRole is not (null or ProjectRole.None))
|
||||
{
|
||||
resolvedEmail = projectExecutors.FirstOrDefault(e => e.ProjectRole == step.ProjectRole)?.Email;
|
||||
if (string.IsNullOrWhiteSpace(resolvedEmail))
|
||||
warning = $"Не найден исполнитель с ролью в проекте «{step.ProjectRole}».";
|
||||
}
|
||||
else if (step.HeadType is not (null or HeadType.None))
|
||||
{
|
||||
resolvedEmail = step.HeadType switch
|
||||
{
|
||||
HeadType.Chief => await portalClient.GetManagerAsync(initiatorEmail, "chief", cancellationToken),
|
||||
@@ -212,8 +224,14 @@ public sealed class TemplateRouteService(
|
||||
HeadType.DirectorTansu => portalOptions.Value.DirectorTansuEmail,
|
||||
_ => null
|
||||
};
|
||||
if (string.IsNullOrWhiteSpace(resolvedEmail))
|
||||
warning = $"Не найден руководитель типа «{step.HeadType}».";
|
||||
}
|
||||
else
|
||||
{
|
||||
resolvedEmail = null;
|
||||
warning = "Не удалось определить исполнителя шага: не заполнено ни одно из полей.";
|
||||
}
|
||||
|
||||
string? resolvedPosition = null;
|
||||
if (!string.IsNullOrWhiteSpace(resolvedEmail))
|
||||
@@ -222,7 +240,7 @@ public sealed class TemplateRouteService(
|
||||
resolvedPosition = resolvedEmployee?.Position;
|
||||
}
|
||||
|
||||
return new ResolvedRouteStepDto(step.Id, step.OrderNo, resolvedEmail, resolvedPosition, step.AllowReassignToOtherApprovers);
|
||||
return new ResolvedRouteStepDto(step.Id, step.OrderNo, resolvedEmail, resolvedPosition, step.AllowReassignToOtherApprovers, warning);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<ResolvedRouteStepDto> CollapseByExecutorEmail(IReadOnlyList<ResolvedRouteStepDto> steps)
|
||||
|
||||
+84
@@ -367,6 +367,90 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
|
||||
portalClient.Verify(x => x.GetManagerAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Резолв шага с типом руководителя Chief возвращает warning, если руководитель не найден")]
|
||||
public async Task ResolveAsync_ByHeadTypeChief_NotFound_ReturnsWarning()
|
||||
{
|
||||
// Arrange
|
||||
var requestTypeId = await CreateRequestTypeAsync("Chief not found request");
|
||||
var route = await _service.CreateAsync(
|
||||
new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false),
|
||||
CancellationToken.None);
|
||||
|
||||
var stepService = _services.GetRequiredService<TemplateRouteStepService>();
|
||||
await stepService.CreateAsync(
|
||||
route.Value.Id,
|
||||
new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false),
|
||||
CancellationToken.None);
|
||||
|
||||
// Action
|
||||
var resolved = await _service.ResolveAsync(
|
||||
new ResolveTemplateRouteRequest(requestTypeId, "someone@tnsu.kz", null, RouteStepStatus.Approval),
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
resolved.IsSuccess.ShouldBeTrue();
|
||||
resolved.Value.Steps.ShouldHaveSingleItem();
|
||||
resolved.Value.Steps[0].ExecutorEmail.ShouldBeNull();
|
||||
resolved.Value.Steps[0].Warning.ShouldNotBeNull();
|
||||
resolved.Value.Steps[0].Warning.ShouldContain("руководитель");
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Резолв шага с должностью возвращает warning, если сотрудник по должности не найден")]
|
||||
public async Task ResolveAsync_ByPosition_NotFound_ReturnsWarning()
|
||||
{
|
||||
// Arrange
|
||||
var requestTypeId = await CreateRequestTypeAsync("Position not found request");
|
||||
var route = await _service.CreateAsync(
|
||||
new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false),
|
||||
CancellationToken.None);
|
||||
|
||||
var stepService = _services.GetRequiredService<TemplateRouteStepService>();
|
||||
await stepService.CreateAsync(
|
||||
route.Value.Id,
|
||||
new CreateTemplateRouteStepRequest(1, null, "Несуществующая должность", null, null, RouteStepStatus.Approval, false),
|
||||
CancellationToken.None);
|
||||
|
||||
// Action
|
||||
var resolved = await _service.ResolveAsync(
|
||||
new ResolveTemplateRouteRequest(requestTypeId, "someone@tnsu.kz", null, RouteStepStatus.Approval),
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
resolved.IsSuccess.ShouldBeTrue();
|
||||
resolved.Value.Steps.ShouldHaveSingleItem();
|
||||
resolved.Value.Steps[0].ExecutorEmail.ShouldBeNull();
|
||||
resolved.Value.Steps[0].Warning.ShouldNotBeNull();
|
||||
resolved.Value.Steps[0].Warning.ShouldContain("должности");
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Резолв шага с ролью в проекте возвращает warning, если исполнитель с такой ролью не найден")]
|
||||
public async Task ResolveAsync_ByProjectRole_NotFound_ReturnsWarning()
|
||||
{
|
||||
// Arrange
|
||||
var requestTypeId = await CreateRequestTypeAsync("Project role not found request");
|
||||
var route = await _service.CreateAsync(
|
||||
new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: true, RequiresControl: false),
|
||||
CancellationToken.None);
|
||||
|
||||
var stepService = _services.GetRequiredService<TemplateRouteStepService>();
|
||||
await stepService.CreateAsync(
|
||||
route.Value.Id,
|
||||
new CreateTemplateRouteStepRequest(1, null, null, ProjectRole.Security, null, RouteStepStatus.Approval, false),
|
||||
CancellationToken.None);
|
||||
|
||||
// Action
|
||||
var resolved = await _service.ResolveAsync(
|
||||
new ResolveTemplateRouteRequest(requestTypeId, "someone@tnsu.kz", Guid.NewGuid(), RouteStepStatus.Approval),
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
resolved.IsSuccess.ShouldBeTrue();
|
||||
resolved.Value.Steps.ShouldHaveSingleItem();
|
||||
resolved.Value.Steps[0].ExecutorEmail.ShouldBeNull();
|
||||
resolved.Value.Steps[0].Warning.ShouldNotBeNull();
|
||||
resolved.Value.Steps[0].Warning.ShouldContain("роль");
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Резолв маршрута откатывается на маршрут без отдела, если точного совпадения по отделу нет")]
|
||||
public async Task ResolveAsync_NoDepartmentRoute_FallsBackToCompanyWideRoute()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user