@@ -163,6 +163,11 @@ public sealed class TemplateRouteService(
. GroupBy ( f = > f . FieldName , StringComparer . OrdinalIgnoreCase )
. ToDictionary ( g = > g . Key , g = > g . First ( ) . Value , StringComparer . OrdinalIgnoreCase ) ;
// Синтетическое поле условия (не пришедшее из request.FieldValues, а вычисленное
// здесь): должность в справочнике — свободный текст, поэтому «руководитель или
// нет» определяем по ключевым словам в её названии, а не по отдельному флагу.
fieldValues [ "IsManager" ] = IsManagerPosition ( employee . Position ) ? "true" : "false" ;
var matchingSteps = route . Steps
. Where ( s = > s . Status = = request . Status )
. Where ( s = > MatchesCondition ( s , fieldValues ) )
@@ -264,6 +269,7 @@ public sealed class TemplateRouteService(
HeadType . Chief = > await portalClient . GetManagerAsync ( initiatorEmail , "chief" , cancellationToken ) ,
HeadType . DirectionHead = > await portalClient . GetManagerAsync ( initiatorEmail , "directionHead" , cancellationToken ) ,
HeadType . DirectorTansu = > portalOptions . Value . DirectorTansuEmail ,
HeadType . ManagingDirectorTansu = > portalOptions . Value . ManagingDirectorTansuEmail ,
_ = > null
} ;
if ( string . IsNullOrWhiteSpace ( resolvedEmail ) )
@@ -328,6 +334,21 @@ public sealed class TemplateRouteService(
private static bool TryParseNumber ( string? value , out double number ) = >
double . TryParse ( value , NumberStyles . Float , CultureInfo . InvariantCulture , out number ) ;
/// <summary>
/// Должность в справочнике api.tnsu.kz — свободный текст без структуры (нет
/// отдельного флага «это руководитель»), поэтому определяем это по ключевым
/// словам в названии должности. Список подобран по фактическим должностям
/// в справочнике — Начальник/Руководитель/Директор/Заместитель/Главный/
/// Заведующий складом. «Ведущий», «Старший» и «Менеджер» намеренно не входят:
/// это старший специалист, а не должность с подчинёнными.
/// </summary>
private static readonly string [ ] ManagerPositionKeywords =
["начальник", "руководитель", "директор", "заместитель", "главный", "заведующий склад"] ;
private static bool IsManagerPosition ( string? position ) = >
! string . IsNullOrWhiteSpace ( position ) & &
ManagerPositionKeywords . Any ( keyword = > position . Contains ( keyword , StringComparison . OrdinalIgnoreCase ) ) ;
private static IReadOnlyList < ResolvedRouteStepDto > CollapseByExecutorEmail ( IReadOnlyList < ResolvedRouteStepDto > steps )
{
var seenEmails = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ;