Выгрузка маршрутов: сборка таблицы перенесена на бэкенд, добавлены организация, комментарий и деление на согласующих/исполнителей

Бэкенд сам читает маршруты, шаги и справочники портала (сотрудники с должностями,
компании, отделы), фронт шлёт только id типов заявок и скачивает файл.
Колонки: №, Роль, Исполнитель, Организация, Условие, Комментарий.
Согласующие и исполнители — отдельными таблицами. Условие по CompaniesJson
показывается как «организация». Директор Tansu и Управляющий директор Tansu
выводятся с ФИО. Комментарий «назначает согласующего/исполнителя» — при галочке
переназначения.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
gorbenko.pavel
2026-10-13 10:24:18 +05:00
parent 43b3c77ac1
commit 6433299c62
15 changed files with 357 additions and 167 deletions
@@ -15,7 +15,7 @@ namespace Tnsu.ApprovalRoute.Api.Controllers;
[ApiController]
[Authorize(Policy = AuthPolicies.Any)]
[Route("api/reports")]
public sealed class ReportsController(VacancyReportService vacancies) : ControllerBase
public sealed class ReportsController(VacancyReportService vacancies, RouteExportService routeExport) : ControllerBase
{
private const string XlsxContentType =
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
@@ -54,19 +54,19 @@ public sealed class ReportsController(VacancyReportService vacancies) : Controll
/// <summary>
/// Выгрузка маршрутов согласования выбранных типов заявок в xlsx (лист на тип).
/// POST, потому что строки собирает админка — у неё уже есть справочники
/// сотрудников и подписи шагов (см. RouteExportWorkbook).
/// POST — потому что в теле список id типов (см. RouteExportService).
/// </summary>
[HttpPost("routes")]
[Produces(XlsxContentType)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public IActionResult Routes([FromBody] RouteExportRequest request)
public async Task<IActionResult> Routes([FromBody] RouteExportRequest request, CancellationToken ct = default)
{
if (request.Sheets is not { Count: > 0 })
return BadRequest(new { errors = new[] { "Не выбрано ни одного типа заявки." } });
var result = await routeExport.BuildAsync(request.RequestTypeIds, ct);
if (result.IsFailed)
return BadRequest(new { errors = result.Errors.Select(e => e.Message) });
var file = RouteExportWorkbook.Build(request);
var file = result.Value;
var disposition = new ContentDispositionHeaderValue("attachment");
disposition.SetHttpFileName(RouteExportWorkbook.FileName);