Выгрузка маршрутов: колонка «Организация» — компания сотрудника, а не маршрута

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
gorbenko.pavel
2026-10-13 10:26:09 +05:00
parent 6433299c62
commit 7c64f3ccf4
@@ -24,7 +24,6 @@ public sealed class RouteExportService(
{
private const string Empty = "—";
private const string Always = "Всегда";
private const string AnyOrganization = "Любая";
private const string AnyCompany = "любая компания";
private const string AnyDepartment = "любой отдел";
private const string ByRequest = "Определяется по заявке";
@@ -112,12 +111,9 @@ public sealed class RouteExportService(
var sections = routes
.Select(route =>
{
var organization = route.CompanyId is { } companyId
? directory.CompanyName(companyId)
: AnyOrganization;
return new RouteExportSection(
routes.Count < 2 ? null : RouteTitle(route, directory),
BuildGroups(route.Steps, organization, directory));
BuildGroups(route.Steps, directory));
})
.ToList();
@@ -131,7 +127,7 @@ public sealed class RouteExportService(
return $"Маршрут: {company} · {department}{(route.ByProject ? " · по проекту" : string.Empty)}";
}
private List<RouteExportGroup> BuildGroups(IEnumerable<TemplateRouteStep> steps, string organization, PortalDirectory directory)
private List<RouteExportGroup> BuildGroups(IEnumerable<TemplateRouteStep> steps, PortalDirectory directory)
{
var ordered = steps.OrderBy(s => s.OrderNo).ToList();
var approvers = ordered.Where(s => s.Status != RouteStepStatus.Execution).ToList();
@@ -139,22 +135,22 @@ public sealed class RouteExportService(
return new[]
{
new RouteExportGroup("Согласующие", BuildRows(approvers, organization, directory)),
new RouteExportGroup("Исполнители", BuildRows(executors, organization, directory))
new RouteExportGroup("Согласующие", BuildRows(approvers, directory)),
new RouteExportGroup("Исполнители", BuildRows(executors, directory))
}
.Where(g => g.Rows.Count > 0)
.ToList();
}
// Параллельные шаги (равный OrderNo) получают один и тот же номер.
private List<RouteExportRow> BuildRows(IReadOnlyList<TemplateRouteStep> steps, string organization, PortalDirectory directory)
private List<RouteExportRow> BuildRows(IReadOnlyList<TemplateRouteStep> steps, PortalDirectory directory)
{
var orderNos = steps.Select(s => s.OrderNo).Distinct().OrderBy(n => n).ToList();
return steps
.Select(step =>
{
var (role, executor) = DescribeRoleAndExecutor(step, directory);
var (role, executor, organization) = DescribeRoleAndExecutor(step, directory);
return new RouteExportRow(
orderNos.IndexOf(step.OrderNo) + 1,
role,
@@ -169,19 +165,22 @@ public sealed class RouteExportService(
// «Роль» — должность или тип руководителя, «исполнитель» — конкретный человек,
// если он известен уже при настройке маршрута. Директор Tansu и Управляющий
// директор Tansu — тоже конкретные люди (их email лежит в настройках).
private (string Role, string Executor) DescribeRoleAndExecutor(TemplateRouteStep step, PortalDirectory directory)
private (string Role, string Executor, string Organization) DescribeRoleAndExecutor(TemplateRouteStep step, PortalDirectory directory)
{
if (!string.IsNullOrWhiteSpace(step.ExecutorEmail))
{
var employee = directory.FindByEmail(step.ExecutorEmail);
return (EmptyIfBlank(employee?.Position), employee?.Fio ?? step.ExecutorEmail);
return (EmptyIfBlank(employee?.Position), employee?.Fio ?? step.ExecutorEmail, directory.CompaniesOf(step.ExecutorEmail));
}
if (!string.IsNullOrWhiteSpace(step.Position))
return (step.Position, directory.FindByPosition(step.Position)?.Fio ?? Empty);
{
var holder = directory.FindByPosition(step.Position);
return (step.Position, holder?.Fio ?? Empty, directory.CompaniesOf(holder?.Email));
}
if (step.ProjectRole is { } projectRole && projectRole != ProjectRole.None)
return (ProjectRoleLabels[projectRole], ByRequest);
return (ProjectRoleLabels[projectRole], ByRequest, Empty);
if (step.HeadType is { } headType && headType != HeadType.None)
{
@@ -193,12 +192,12 @@ public sealed class RouteExportService(
};
if (fixedEmail is not null)
// Нет в справочнике — лучше показать email, чем пустую ячейку.
return (HeadTypeLabels[headType], directory.FindByEmail(fixedEmail)?.Fio ?? fixedEmail);
return (HeadTypeLabels[headType], directory.FindByEmail(fixedEmail)?.Fio ?? fixedEmail, directory.CompaniesOf(fixedEmail));
return (HeadTypeLabels[headType], ByRequest);
return (HeadTypeLabels[headType], ByRequest, Empty);
}
return (Empty, Empty);
return (Empty, Empty, Empty);
}
private static string DescribeCondition(TemplateRouteStep step)
@@ -239,6 +238,22 @@ public sealed class RouteExportService(
public EmployeeContactInfo? FindByPosition(string position) =>
employees.FirstOrDefault(e => string.Equals(e.Position?.Trim(), position.Trim(), StringComparison.OrdinalIgnoreCase));
// Организация — компания СОТРУДНИКА, а не маршрута. Справочник даёт строку на
// каждую компанию, где он числится (совместители), поэтому выводим все.
public string CompaniesOf(string? email)
{
if (string.IsNullOrWhiteSpace(email))
return Empty;
var names = employees
.Where(e => string.Equals(e.Email, email, StringComparison.OrdinalIgnoreCase)
&& !string.IsNullOrWhiteSpace(e.Company))
.Select(e => e.Company)
.Distinct()
.ToList();
return names.Count == 0 ? Empty : string.Join(", ", names);
}
// Пока справочник не отдал имя (или запись удалена) — усечённый Guid, как в админке.
public string CompanyName(Guid id) => companies.GetValueOrDefault(id) ?? ShortGuid(id);