diff --git a/backend/Tnsu.ApprovalRoute.Application/TemplateRoutes/ResolveTemplateRouteDto.cs b/backend/Tnsu.ApprovalRoute.Application/TemplateRoutes/ResolveTemplateRouteDto.cs index 1aba714..2029871 100644 --- a/backend/Tnsu.ApprovalRoute.Application/TemplateRoutes/ResolveTemplateRouteDto.cs +++ b/backend/Tnsu.ApprovalRoute.Application/TemplateRoutes/ResolveTemplateRouteDto.cs @@ -23,10 +23,18 @@ public sealed record ResolvedRouteStepDto( int? SlaHours = null, bool? AskOverdueReason = null); +/// +/// Предупреждение уровня всего резолва (не привязано к конкретному шагу) — например, +/// в маршруте есть шаг с условием по полю IsManager, а в заявке не передано значение +/// поля Position, из которого IsManager вычисляется. Это не ошибка (резолв всё равно +/// отработал, IsManager просто посчитался как «false»), но может означать, что +/// вызывающая сторона забыла передать поле, и шаг из-за этого не появился, хотя должен был. +/// public sealed record ResolveTemplateRouteResultDto( int RouteId, bool RequiresControl, - IReadOnlyList Steps); + IReadOnlyList Steps, + string? Warning = null); /// Значение поля списка SharePoint (field_name шага) для проверки условий шагов при resolve. public sealed record ResolveFieldValueDto(string FieldName, string Value); diff --git a/backend/Tnsu.ApprovalRoute.Application/TemplateRoutes/TemplateRouteService.cs b/backend/Tnsu.ApprovalRoute.Application/TemplateRoutes/TemplateRouteService.cs index d29eea0..dd5dd0d 100644 --- a/backend/Tnsu.ApprovalRoute.Application/TemplateRoutes/TemplateRouteService.cs +++ b/backend/Tnsu.ApprovalRoute.Application/TemplateRoutes/TemplateRouteService.cs @@ -163,10 +163,29 @@ 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"; + // Синтетическое поле условия — переопределяет то, что мог прислать вызывающий + // код под тем же именем. Считаем НЕ по должности инициатора из справочника + // api.tnsu.kz (employee.Position — она у самого сотрудника чаще всего не + // «руководитель»), а по полю Position, заполненному В САМОЙ АНКЕТЕ (например, + // «Должность» в Обходном листе — там указывают должность увольняемого, а не + // того, кто отправил заявку). Это свободный текст, поэтому определяем + // «руководитель или нет» по ключевым словам в нём. Нет такого поля в заявке — + // считаем «нет» (а не подставляем должность инициатора как фолбэк). + fieldValues.TryGetValue("Position", out var formPosition); + fieldValues["IsManager"] = IsManagerPosition(formPosition) ? "true" : "false"; + + // Предупреждение уровня резолва, а не шага: условие по IsManager есть в маршруте + // (хотя бы на одном шаге, независимо от статуса — фильтр по s.Status ниже сузил бы + // проверку до конкретной фазы, а «забыли передать Position» это не про фазу), но + // Position в заявке не пришёл вовсе. Если поле пришло, но пустой строкой или не + // похоже на руководящую должность — это осознанный «false», предупреждать не о чем. + string? generalWarning = null; + if (string.IsNullOrWhiteSpace(formPosition) && + route.Steps.Any(s => string.Equals(s.FieldName, "IsManager", StringComparison.OrdinalIgnoreCase))) + { + generalWarning = "В маршруте есть шаг с условием по полю «IsManager», но в заявке не передано значение " + + "поля «Position» (должность) — условие считается невыполненным."; + } var matchingSteps = route.Steps .Where(s => s.Status == request.Status) @@ -181,7 +200,7 @@ public sealed class TemplateRouteService( var collapsedSteps = CollapseByExecutorEmail(steps); - return Result.Ok(new ResolveTemplateRouteResultDto(route.Id, route.RequiresControl, collapsedSteps)); + return Result.Ok(new ResolveTemplateRouteResultDto(route.Id, route.RequiresControl, collapsedSteps, generalWarning)); } /// diff --git a/frontend/src/webparts/approvalAdmin/components/screens/RouteStepFormModal.tsx b/frontend/src/webparts/approvalAdmin/components/screens/RouteStepFormModal.tsx index 80b73fb..c8787f2 100644 --- a/frontend/src/webparts/approvalAdmin/components/screens/RouteStepFormModal.tsx +++ b/frontend/src/webparts/approvalAdmin/components/screens/RouteStepFormModal.tsx @@ -78,8 +78,10 @@ const OPERATION_OPTIONS = Object.values(ComparisonOperator) /** * Служебное поле условия, не привязанное к списку SharePoint — вычисляется на бэкенде - * из должности инициатора (см. TemplateRouteService.ResolveAsync), а не из данных заявки. - * Поэтому оно доступно всегда, даже если у типа заявки не настроен list_name. + * (см. TemplateRouteService.ResolveAsync) по значению поля Position В САМОЙ ЗАЯВКЕ + * («Должность» — например, у увольняемого в обходном листе), а не по должности того, + * кто отправил заявку. Поэтому поле доступно всегда, даже если у типа заявки не + * настроен list_name — источник данных не сам список, а fieldValues resolve-запроса. */ const IS_MANAGER_FIELD = 'IsManager'; const SYNTHETIC_FIELD_OPTIONS = [{ value: IS_MANAGER_FIELD, label: texts.routeSteps.conditionFieldIsManagerLabel }]; diff --git a/frontend/src/webparts/approvalAdmin/texts.ts b/frontend/src/webparts/approvalAdmin/texts.ts index 7a010d2..0ff23df 100644 --- a/frontend/src/webparts/approvalAdmin/texts.ts +++ b/frontend/src/webparts/approvalAdmin/texts.ts @@ -236,7 +236,7 @@ export const texts = { conditionCheckbox: 'Показывать шаг только при выполнении условия', conditionDisabledHint: 'У типа заявки не указан список SharePoint (list_name) — доступно только служебное поле «Инициатор — руководитель».', - conditionFieldIsManagerLabel: 'Инициатор — руководитель (IsManager)', + conditionFieldIsManagerLabel: 'Должность в заявке — руководящая (IsManager)', conditionFieldNameLabel: 'Поле списка', conditionFieldNamePlaceholder: 'Выберите столбец списка', conditionFieldNameLoading: 'Загрузка столбцов списка...',