Несколько условий шага, объединяемых через И/ИЛИ #16

Merged
gorbenko.pavel merged 5 commits from several-ifs into main 2026-08-25 07:04:44 +00:00
Owner
<html> GetEmployeeByEmailAsync возвращает массив; новый HeadType.CompanyDirectors -- </html>[GetEmployeeByEmailAsync возвращает массив; новый HeadType.CompanyDirectors](http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/commit/572749b4d7cc11bc8e1ef4184a93f18a14b710ab)
<html> <body> <!--StartFragment--> GetEmployeeByEmailAsync возвращает массив; новый HeadType.CompanyDirectors -- <!--EndFragment--> </body> </html>[GetEmployeeByEmailAsync возвращает массив; новый HeadType.CompanyDirectors](http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/commit/572749b4d7cc11bc8e1ef4184a93f18a14b710ab)
gorbenko.pavel added 3 commits 2026-08-25 05:55:12 +00:00
Раньше шаг маршрута мог нести ровно одно условие (field_name/operation/
value). Теперь условий может быть сколько угодно, а их объединение задаётся
явно — ConditionsLogic.And (все условия) или .Or (любое из условий).

Бэкенд:
- Новая сущность TemplateRouteStepCondition (дочерняя таблица
  template_route_step_conditions, FK на шаг, cascade delete) вместо трёх
  колонок на TemplateRouteStep. Миграция AddMultipleStepConditions переносит
  существующие условия шагов в новую таблицу перед удалением старых колонок,
  чтобы не потерять уже настроенные в деве/проде условия.
- TemplateRouteService.MatchesConditions: несколько условий шага объединяются
  по ConditionsLogic (Any/All); HasUnevaluatableManagerCondition проверяет
  условие на IsManager в списке условий, а не единственное поле.
- TemplateRouteStepService: дубликаты шагов теперь сравниваются по всему
  набору условий (без учёта порядка) и, если условий два и более, по логике
  их объединения — сравнение в памяти, а не в SQL, кандидатов на дубликат
  в пределах маршрута всегда мало.
- Валидация условия: раньше «все три поля или ни одного», теперь — в каждом
  элементе списка все три поля обязательны.

Фронтенд:
- RouteStepFormModal: условие — динамический список строк (добавить/удалить),
  у каждой строки свой набор допустимых операторов по типу колонки; селектор
  И/ИЛИ появляется, когда условий два и больше.
- describeStepCondition в таблице шагов склеивает все условия через « И »/« ИЛИ ».

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Раньше по ответу резолва нельзя было отличить «руководителя направления» от
«директора Tansu» иначе как парсингом Position (реальной должности из
справочника api.tnsu.kz, свободный текст). PositionCode — это HeadType
самого шага (Chief/DirectionHead/DirectorTansu/ManagingDirectorTansu/
DepartmentHead), null для шагов с исполнителем по email/должности/роли.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GetEmployeeByEmailAsync возвращает массив; новый HeadType.CompanyDirectors
Tests / frontend-test (pull_request) Successful in 46s
Tests / backend-test (pull_request) Successful in 1m28s
572749b4d7
/Employee/ByEmail на портале обновился: сотрудник может числиться в
нескольких компаниях, теперь отдаётся массив записей (одна на компанию).
ITnsuPortalClient.GetEmployeeByEmailAsync возвращает IReadOnlyList вместо
одного объекта; вызывающие места используют первую запись (как и раньше
при одиночном ответе), кроме нового HeadType.CompanyDirectors — там нужны
все компании.

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

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
gorbenko.pavel added 2 commits 2026-08-25 06:51:29 +00:00
CollapseByExecutorEmail просто выкидывал дубликаты по email, оставляя
первую строку как есть — если у неё PositionCode был пуст (исполнитель
задан email/должностью), а у отброшенного дубликата задан (пришёл через
HeadType), тот терялся молча.

Теперь: PositionCode пустой у оставленной строки — переносится с дубликата.
Задан у обеих и они РАЗНЫЕ (один и тот же человек получен через два
несовместимых HeadType, например Chief и DirectionHead одновременно) —
оставляем первый попавшийся и добавляем предупреждение с обоими значениями,
а не выбираем молча.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Резолв: Директор Tansu исключает совпавшего Директора компании
Tests / frontend-test (pull_request) Successful in 46s
Tests / backend-test (pull_request) Successful in 1m47s
84b7003ac2
Директор 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>
gorbenko.pavel merged commit ba48ed1a0e into main 2026-08-25 07:04:44 +00:00
gorbenko.pavel deleted branch several-ifs 2026-08-25 07:04:44 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: gorbenko.pavel/tnsu-approval-route#16