Files
tnsu-approval-route/backend/Tnsu.ApprovalRoute.Api/Controllers/ReportsController.cs
T
gorbenko.pavel 6433299c62 Выгрузка маршрутов: сборка таблицы перенесена на бэкенд, добавлены организация, комментарий и деление на согласующих/исполнителей
Бэкенд сам читает маршруты, шаги и справочники портала (сотрудники с должностями,
компании, отделы), фронт шлёт только id типов заявок и скачивает файл.
Колонки: №, Роль, Исполнитель, Организация, Условие, Комментарий.
Согласующие и исполнители — отдельными таблицами. Условие по CompaniesJson
показывается как «организация». Директор Tansu и Управляющий директор Tansu
выводятся с ФИО. Комментарий «назначает согласующего/исполнителя» — при галочке
переназначения.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-13 10:24:18 +05:00

78 lines
3.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Globalization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
using Tnsu.ApprovalRoute.Api.Auth;
using Tnsu.ApprovalRoute.Application.Reports;
namespace Tnsu.ApprovalRoute.Api.Controllers;
/// <summary>
/// Выгрузки отчётов файлами. В отличие от остальных контроллеров отдаёт не
/// ApiResponse, а сам файл: получатель — кнопка «скачать» в портале и Power
/// Automate, обоим нужен готовый xlsx, а не JSON с телом в base64.
/// </summary>
[ApiController]
[Authorize(Policy = AuthPolicies.Any)]
[Route("api/reports")]
public sealed class ReportsController(VacancyReportService vacancies, RouteExportService routeExport) : ControllerBase
{
private const string XlsxContentType =
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
/// <summary>
/// Отчёт по вакансиям за период — лист «Список вакансий (все)» по данным
/// заявок на подбор и на приём.
///
/// Период отбирает заявки по «Дате подачи заявки» (колонка H отчёта). Заявки
/// на приём периодом НЕ ограничены: у вакансии, поданной в начале интервала,
/// приём мог оформляться уже после его конца.
/// </summary>
[HttpGet("vacancies")]
[Produces(XlsxContentType)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Vacancies(
[FromQuery] DateOnly from, [FromQuery] DateOnly to, CancellationToken ct = default)
{
var result = await vacancies.GetAsync(from, to, ct);
if (result.IsFailed)
return BadRequest(new { errors = result.Errors.Select(e => e.Message) });
var report = result.Value;
var file = VacancyReportWorkbook.Build(report);
// Имя файла с кириллицей и пробелами — через ContentDispositionHeaderValue:
// он сам добавит filename* в RFC 5987, иначе браузер сохранит файл
// с искажённым именем.
var disposition = new ContentDispositionHeaderValue("attachment");
disposition.SetHttpFileName(VacancyReportWorkbook.FileName(report));
Response.Headers.ContentDisposition = disposition.ToString();
return File(file, XlsxContentType);
}
/// <summary>
/// Выгрузка маршрутов согласования выбранных типов заявок в xlsx (лист на тип).
/// POST — потому что в теле список id типов (см. RouteExportService).
/// </summary>
[HttpPost("routes")]
[Produces(XlsxContentType)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Routes([FromBody] RouteExportRequest request, CancellationToken ct = default)
{
var result = await routeExport.BuildAsync(request.RequestTypeIds, ct);
if (result.IsFailed)
return BadRequest(new { errors = result.Errors.Select(e => e.Message) });
var file = result.Value;
var disposition = new ContentDispositionHeaderValue("attachment");
disposition.SetHttpFileName(RouteExportWorkbook.FileName);
Response.Headers.ContentDisposition = disposition.ToString();
return File(file, XlsxContentType);
}
}