апи для работы с маршрутами согласования #2
@@ -177,7 +177,9 @@ public sealed class TemplateRouteService(
|
|||||||
foreach (var step in matchingSteps)
|
foreach (var step in matchingSteps)
|
||||||
steps.Add(await ResolveStepAsync(step, email, employee.Company, projectExecutors, cancellationToken));
|
steps.Add(await ResolveStepAsync(step, email, employee.Company, projectExecutors, cancellationToken));
|
||||||
|
|
||||||
return Result.Ok(new ResolveTemplateRouteResultDto(route.Id, route.RouteType, route.RequiresControl, steps));
|
var collapsedSteps = CollapseByExecutorEmail(steps);
|
||||||
|
|
||||||
|
return Result.Ok(new ResolveTemplateRouteResultDto(route.Id, route.RouteType, route.RequiresControl, collapsedSteps));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<bool> HasDuplicateAsync(
|
private Task<bool> HasDuplicateAsync(
|
||||||
@@ -223,6 +225,22 @@ public sealed class TemplateRouteService(
|
|||||||
return new ResolvedRouteStepDto(step.Id, step.OrderNo, resolvedEmail, resolvedPosition, step.AllowReassignToOtherApprovers);
|
return new ResolvedRouteStepDto(step.Id, step.OrderNo, resolvedEmail, resolvedPosition, step.AllowReassignToOtherApprovers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<ResolvedRouteStepDto> CollapseByExecutorEmail(IReadOnlyList<ResolvedRouteStepDto> steps)
|
||||||
|
{
|
||||||
|
var seenEmails = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var result = new List<ResolvedRouteStepDto>();
|
||||||
|
|
||||||
|
foreach (var step in steps)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(step.ExecutorEmail) && !seenEmails.Add(step.ExecutorEmail))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
result.Add(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private static TemplateRouteDto ToDto(TemplateRoute e) => new(
|
private static TemplateRouteDto ToDto(TemplateRoute e) => new(
|
||||||
e.Id, e.RouteType, e.RequestTypeId, e.DepartmentId, e.CompanyId, e.ByProject, e.RequiresControl);
|
e.Id, e.RouteType, e.RequestTypeId, e.DepartmentId, e.CompanyId, e.ByProject, e.RequiresControl);
|
||||||
}
|
}
|
||||||
|
|||||||
+50
@@ -178,6 +178,56 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
|
|||||||
resolved.Value.Steps[0].ExecutorEmail.ShouldBe("approver@tnsu.kz");
|
resolved.Value.Steps[0].ExecutorEmail.ShouldBe("approver@tnsu.kz");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact(DisplayName = "Резолв маршрута схлопывает шаги, резолвящиеся в одинаковый email, оставляя шаг с меньшим OrderNo")]
|
||||||
|
public async Task ResolveAsync_StepsResolveToSameEmail_CollapsesToLowestOrderNo()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var portalClient = new Mock<ITnsuPortalClient>();
|
||||||
|
portalClient
|
||||||
|
.Setup(x => x.GetEmployeeByEmailAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(new EmployeeDirectoryInfo(CompanyId, DepartmentId, "ТОО TANSU Construction", "Тестовая должность"));
|
||||||
|
portalClient
|
||||||
|
.Setup(x => x.GetManagerAsync(It.IsAny<string>(), "chief", It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync("dup@tnsu.kz");
|
||||||
|
|
||||||
|
var services = TestServiceProviderFactory.Build(_connectionString, portalClient: portalClient.Object);
|
||||||
|
var routeService = services.GetRequiredService<TemplateRouteService>();
|
||||||
|
var requestTypeService = services.GetRequiredService<RequestTypeService>();
|
||||||
|
var stepService = services.GetRequiredService<TemplateRouteStepService>();
|
||||||
|
|
||||||
|
var requestTypeId = (await requestTypeService.CreateAsync(
|
||||||
|
new CreateRequestTypeRequest("Collapse duplicates request", "/collapse-duplicates"), CancellationToken.None)).Value.Id;
|
||||||
|
var route = await routeService.CreateAsync(
|
||||||
|
new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false),
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
await stepService.CreateAsync(
|
||||||
|
route.Value.Id,
|
||||||
|
new CreateTemplateRouteStepRequest(1, "dup@tnsu.kz", null, null, null, RouteStepStatus.Approval, false),
|
||||||
|
CancellationToken.None);
|
||||||
|
await stepService.CreateAsync(
|
||||||
|
route.Value.Id,
|
||||||
|
new CreateTemplateRouteStepRequest(2, "other@tnsu.kz", null, null, null, RouteStepStatus.Approval, false),
|
||||||
|
CancellationToken.None);
|
||||||
|
await stepService.CreateAsync(
|
||||||
|
route.Value.Id,
|
||||||
|
new CreateTemplateRouteStepRequest(3, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false),
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
// Action
|
||||||
|
var resolved = await routeService.ResolveAsync(
|
||||||
|
new ResolveTemplateRouteRequest(requestTypeId, "someone@tnsu.kz", null, RouteStepStatus.Approval),
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
resolved.IsSuccess.ShouldBeTrue();
|
||||||
|
resolved.Value.Steps.Count.ShouldBe(2);
|
||||||
|
resolved.Value.Steps[0].ExecutorEmail.ShouldBe("dup@tnsu.kz");
|
||||||
|
resolved.Value.Steps[0].OrderNo.ShouldBe(1);
|
||||||
|
resolved.Value.Steps[1].ExecutorEmail.ShouldBe("other@tnsu.kz");
|
||||||
|
resolved.Value.Steps[1].OrderNo.ShouldBe(2);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact(DisplayName = "Резолв маршрута с ролью в проекте подставляет исполнителя из проекта")]
|
[Fact(DisplayName = "Резолв маршрута с ролью в проекте подставляет исполнителя из проекта")]
|
||||||
public async Task ResolveAsync_ByProjectRole_UsesProjectExecutorEmail()
|
public async Task ResolveAsync_ByProjectRole_UsesProjectExecutorEmail()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user