Резолв: Директор Tansu исключает совпавшего Директора компании
Tests / frontend-test (pull_request) Successful in 46s
Tests / backend-test (pull_request) Successful in 1m47s

Директор 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:
gorbenko.pavel
2026-08-25 11:46:34 +05:00
parent fb90d372c8
commit 84b7003ac2
2 changed files with 92 additions and 1 deletions
@@ -221,11 +221,42 @@ public sealed class TemplateRouteService(
HasUnevaluatableManagerCondition(step, positionMissing) ? PositionMissingWarning : null,
cancellationToken)));
var collapsedSteps = CollapseByExecutorEmail(stepResults.SelectMany(x => x).ToList());
var flattenedSteps = RemoveCompanyDirectorsDuplicatedByDirectorTansu(stepResults.SelectMany(x => x).ToList());
var collapsedSteps = CollapseByExecutorEmail(flattenedSteps);
return Result.Ok(new ResolveTemplateRouteResultDto(route.Id, route.RequiresControl, collapsedSteps));
}
/// <summary>
/// Директор Tansu (HeadType.DirectorTansu) сплошь и рядом совпадает с директором
/// ОДНОЙ из компаний инициатора (HeadType.CompanyDirectors) — обычно с ТОО TANSU
/// Construction. Оба шага в маршруте законны сами по себе, но если так, из
/// CompanyDirectors нужно убрать именно ЭТУ строку — не смешивать её с шагом
/// DirectorTansu через обычное схлопывание по email (см. CollapseByExecutorEmail):
/// тот считает разные PositionCode у одного email конфликтом и пишет
/// предупреждение, а здесь это не конфликт, а одно и то же лицо, просто
/// увиденное с двух сторон. Шаг DirectorTansu в маршруте обычно идёт последним
/// (после всех CompanyDirectors), поэтому удаление дубликата из CompanyDirectors
/// оставляет его на своём месте — в конце.
/// </summary>
private static IReadOnlyList<ResolvedRouteStepDto> RemoveCompanyDirectorsDuplicatedByDirectorTansu(
IReadOnlyList<ResolvedRouteStepDto> steps)
{
var directorTansuEmails = steps
.Where(s => s.PositionCode == HeadType.DirectorTansu && !string.IsNullOrWhiteSpace(s.ExecutorEmail))
.Select(s => s.ExecutorEmail!)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
if (directorTansuEmails.Count == 0)
return steps;
return steps
.Where(s => s.PositionCode != HeadType.CompanyDirectors
|| string.IsNullOrWhiteSpace(s.ExecutorEmail)
|| !directorTansuEmails.Contains(s.ExecutorEmail))
.ToList();
}
/// <summary>
/// Маршрут для компании/отдела инициатора с фолбэком по отделу: сначала точное
/// совпадение отдела, если такого маршрута нет — запасной маршрут без отдела