Резолв: Директор Tansu исключает совпавшего Директора компании
Директор Tansu (HeadType.DirectorTansu) нередко совпадает с директором ОДНОЙ из компаний инициатора (HeadType.CompanyDirectors) — обычно ТОО TANSU Construction. Раньше такое совпадение попадало в обычное схлопывание по email (CollapseByExecutorEmail) и утреннее исправление трактовало его как конфликт PositionCode (Chief/DirectionHead и т.п.) — с предупреждением и произвольным выбором одной из строк. Для этой конкретной пары это не конфликт, а один и тот же человек, увиденный с двух сторон: теперь строка CompanyDirectors с email, совпавшим с одним из DirectorTansu, убирается из результата ДО схлопывания — без предупреждения. Шаг DirectorTansu в маршруте обычно идёт последним, поэтому итоговый список не теряет исполнителя, просто перестаёт дублировать его через CompanyDirectors. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+60
@@ -849,6 +849,66 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
|
||||
resolved.Value.Steps.ShouldContain(s => s.ExecutorEmail == "mechanic.kps@tnsu.kz");
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Резолв: если Директор Tansu совпадает с одним из Директоров компаний, эта строка убирается из CompanyDirectors, а не конфликтует")]
|
||||
public async Task ResolveAsync_DirectorTansuMatchesOneOfCompanyDirectors_RemovesDuplicateFromCompanyDirectors()
|
||||
{
|
||||
// Arrange — директор ТОО TANSU Construction (из шага CompanyDirectors, OrderNo 5)
|
||||
// и Директор Tansu (из шага DirectorTansu, OrderNo 10, идёт в маршруте последним) —
|
||||
// один и тот же человек с одним и тем же email.
|
||||
var portalClient = new Mock<ITnsuPortalClient>();
|
||||
portalClient
|
||||
.Setup(x => x.GetEmployeeByEmailAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new List<EmployeeDirectoryInfo>
|
||||
{
|
||||
new(CompanyId, DepartmentId, "ТОО TANSU Construction", "Начальник отдела службы безопасности"),
|
||||
new(Guid.NewGuid(), DepartmentId, "ТОО KazPromService", "Начальник отдела службы безопасности")
|
||||
});
|
||||
portalClient
|
||||
.Setup(x => x.GetEmployeeByPositionAsync("Директор", "ТОО TANSU Construction", It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new EmployeeLookupResult(true, "director.tansu@tnsu.kz", "Директор TANSU"));
|
||||
portalClient
|
||||
.Setup(x => x.GetEmployeeByPositionAsync("Главный механик", "ТОО KazPromService", It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new EmployeeLookupResult(true, "mechanic.kps@tnsu.kz", "Главный механик KPS"));
|
||||
|
||||
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("CompanyDirectors overlaps DirectorTansu request", "/company-directors-overlaps-director-tansu", null, null, "General"),
|
||||
CancellationToken.None)).Value.Id;
|
||||
var route = await routeService.CreateAsync(
|
||||
new CreateTemplateRouteRequest(requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false),
|
||||
CancellationToken.None);
|
||||
await stepService.CreateAsync(
|
||||
route.Value.Id,
|
||||
new CreateTemplateRouteStepRequest(5, null, null, null, HeadType.CompanyDirectors, RouteStepStatus.Approval, false, null),
|
||||
CancellationToken.None);
|
||||
await stepService.CreateAsync(
|
||||
route.Value.Id,
|
||||
new CreateTemplateRouteStepRequest(10, null, null, null, HeadType.DirectorTansu, RouteStepStatus.Approval, false, null),
|
||||
CancellationToken.None);
|
||||
|
||||
// Action — тестовая конфигурация не переопределяет TnsuPortal:DirectorTansuEmail,
|
||||
// поэтому HeadType.DirectorTansu резолвится в дефолт из TnsuPortalOptions
|
||||
// ("director.tansu@tnsu.kz") — тот же email, что вернул GetEmployeeByPositionAsync
|
||||
// для ТОО TANSU Construction.
|
||||
var resolved = await routeService.ResolveAsync(
|
||||
new ResolveTemplateRouteRequest(requestTypeId, "someone@tnsu.kz", null, RouteStepStatus.Approval),
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert — вместо трёх строк (2 CompanyDirectors + 1 DirectorTansu) остаётся
|
||||
// две: KazPromService-директор и Директор Tansu, без предупреждения о конфликте.
|
||||
resolved.IsSuccess.ShouldBeTrue();
|
||||
resolved.Value.Steps.Count.ShouldBe(2);
|
||||
var tansuStep = resolved.Value.Steps.Single(s => s.ExecutorEmail == "director.tansu@tnsu.kz");
|
||||
tansuStep.PositionCode.ShouldBe(HeadType.DirectorTansu);
|
||||
tansuStep.OrderNo.ShouldBe(10);
|
||||
tansuStep.Warning.ShouldBeNull();
|
||||
resolved.Value.Steps.ShouldContain(s => s.ExecutorEmail == "mechanic.kps@tnsu.kz" && s.PositionCode == HeadType.CompanyDirectors);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Резолв шага с типом руководителя Chief возвращает warning, если руководитель не найден")]
|
||||
public async Task ResolveAsync_ByHeadTypeChief_NotFound_ReturnsWarning()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user