diff --git a/backend/Tnsu.ApprovalRoute.Application/Common/StepLabels.cs b/backend/Tnsu.ApprovalRoute.Application/Common/StepLabels.cs
index 5e92a8f..8eb2b8b 100644
--- a/backend/Tnsu.ApprovalRoute.Application/Common/StepLabels.cs
+++ b/backend/Tnsu.ApprovalRoute.Application/Common/StepLabels.cs
@@ -20,6 +20,18 @@ public static class StepLabels
_ => headType.ToString()
};
+ /// Название с маленькой буквы — для середины фразы («Не найден руководитель направления»).
+ public static string InSentence(HeadType headType) => Lower(Of(headType));
+
+ ///
+ /// Все названия мужского рода («найден руководитель проекта»), кроме «Безопасность» —
+ /// для неё нужна отдельная формулировка, иначе выйдет «найден безопасность».
+ ///
+ public static string InSentence(ProjectRole projectRole) =>
+ projectRole == ProjectRole.Security ? "сотрудник службы безопасности" : Lower(Of(projectRole));
+
+ private static string Lower(string label) => char.ToLowerInvariant(label[0]) + label[1..];
+
public static string Of(ProjectRole projectRole) => projectRole switch
{
ProjectRole.ProjectManager => "Руководитель проекта",
diff --git a/backend/Tnsu.ApprovalRoute.Application/TemplateRoutes/TemplateRouteService.cs b/backend/Tnsu.ApprovalRoute.Application/TemplateRoutes/TemplateRouteService.cs
index 16c686b..3b3895f 100644
--- a/backend/Tnsu.ApprovalRoute.Application/TemplateRoutes/TemplateRouteService.cs
+++ b/backend/Tnsu.ApprovalRoute.Application/TemplateRoutes/TemplateRouteService.cs
@@ -438,7 +438,7 @@ public sealed class TemplateRouteService(
{
resolvedEmail = projectExecutors.FirstOrDefault(e => e.ProjectRole == step.ProjectRole)?.Email;
if (string.IsNullOrWhiteSpace(resolvedEmail))
- warning = $"Не найден исполнитель с ролью в проекте «{StepLabels.Of(step.ProjectRole.Value)}».";
+ warning = $"В проекте не найден {StepLabels.InSentence(step.ProjectRole.Value)}.";
}
// DepartmentHead — единственный HeadType, который резолвится НЕ по цепочке
// подчинённости инициатора, а по отделу, переданному явно в запросе (см.
@@ -471,12 +471,12 @@ public sealed class TemplateRouteService(
};
if (string.IsNullOrWhiteSpace(resolvedEmail))
{
- var headLabel = StepLabels.Of(step.HeadType.Value);
+ var headLabel = StepLabels.InSentence(step.HeadType.Value);
// Директор Tansu и управляющий директор берутся из настроек сервиса, а не из
// справочника сотрудников — там email не «не найден», а «не проставлен».
warning = step.HeadType is HeadType.DirectorTansu or HeadType.ManagingDirectorTansu
- ? $"Не проставлен «{headLabel}»: email не задан в настройках сервиса."
- : $"Не найден руководитель «{headLabel}»: не указан у инициатора в справочнике сотрудников.";
+ ? $"Не проставлен {headLabel}."
+ : $"Не найден {headLabel}.";
}
}
else
diff --git a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/TemplateRouteServiceTests.cs b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/TemplateRouteServiceTests.cs
index 8b7c2af..4ef4b38 100644
--- a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/TemplateRouteServiceTests.cs
+++ b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/TemplateRouteServiceTests.cs
@@ -1051,7 +1051,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture