GetEmployeeByEmailAsync возвращает массив; новый HeadType.CompanyDirectors
Tests / frontend-test (pull_request) Successful in 46s
Tests / backend-test (pull_request) Successful in 1m28s

/Employee/ByEmail на портале обновился: сотрудник может числиться в
нескольких компаниях, теперь отдаётся массив записей (одна на компанию).
ITnsuPortalClient.GetEmployeeByEmailAsync возвращает IReadOnlyList вместо
одного объекта; вызывающие места используют первую запись (как и раньше
при одиночном ответе), кроме нового HeadType.CompanyDirectors — там нужны
все компании.

HeadType.CompanyDirectors резолвится в директора КАЖДОЙ компании
инициатора — по одной строке результата на компанию, все с одним и тем
же OrderNo шага (они и так один шаг, то есть параллельны по построению).
Директор ищется через GetEmployeeByPositionAsync(position, company) с
захардкоженной должностью «Директор» по умолчанию и явными исключениями
для компаний, где директора называют иначе (ТОО KazPromService — «Главный
механик», ТОО СТ Global — «Генеральный директор»).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
gorbenko.pavel
2026-08-25 10:52:28 +05:00
parent ca8de91742
commit 572749b4d7
9 changed files with 220 additions and 63 deletions
@@ -294,18 +294,20 @@ public sealed class TemplateRouteStepService(
}
/// <summary>
/// Любой HeadType (Chief/DirectionHead/DirectorTansu/DepartmentHead) резолвится
/// в ОДНОГО конкретного человека — цепочка подчинённости инициатора или, для
/// DepartmentHead, единственный departmentId запроса (см.
/// ResolveTemplateRouteRequest.DepartmentId) не оставляют места для выбора.
/// Поэтому два шага одного маршрута с одинаковым HeadType всегда резолвились бы
/// в одного и того же (или неотличимо какого) руководителя — например, «два
/// разных директора одной и той же компании» просто не существуют. В отличие
/// от HasDuplicateAsync (точное совпадение ВСЕХ полей включая статус), здесь
/// достаточно совпадения HeadType — вне зависимости от статуса или остальных
/// полей шага. Исключение — условие появления шага: при разных условиях
/// в конкретный резолв попадёт только один из шагов, так что одного и того же
/// руководителя дважды в маршруте не окажется.
/// Любой HeadType (Chief/DirectionHead/DirectorTansu/DepartmentHead/CompanyDirectors)
/// резолвится в один и тот же ДЕТЕРМИНИРОВАННЫЙ результат — цепочка подчинённости
/// инициатора, единственный departmentId запроса (для DepartmentHead, см.
/// ResolveTemplateRouteRequest.DepartmentId) или компании самого инициатора (для
/// CompanyDirectors) не оставляют места для выбора. Для большинства значений это
/// один конкретный человек; для CompanyDirectors — один и тот же набор людей
/// (директор каждой компании инициатора), но тоже всегда один и тот же для
/// одного и того же инициатора. Поэтому два шага одного маршрута с одинаковым
/// HeadType всегда резолвились бы одинаково — например, «два разных директора
/// одной и той же компании» просто не существуют. В отличие от HasDuplicateAsync
/// (точное совпадение ВСЕХ полей включая статус), здесь достаточно совпадения
/// HeadType — вне зависимости от статуса или остальных полей шага. Исключение —
/// условие появления шага: при разных условиях в конкретный резолв попадёт только
/// один из шагов, так что дублирования исполнителей в маршруте не окажется.
/// </summary>
private async Task<bool> HasDuplicateHeadTypeAsync(
int routeId, HeadType? headType, IReadOnlyList<RouteStepConditionDto>? conditions, ConditionsLogic conditionsLogic,