Files
gorbenko.pavel 0e6a7064d2
Tests / backend-test (push) Successful in 6m7s
Tests / frontend-test (push) Successful in 1m29s
Feature/routes export (#29)
Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz>
Reviewed-on: http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/pulls/29
2026-10-14 05:06:58 +00:00

78 lines
3.7 KiB
C#
Raw Permalink 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(file.FileName);
Response.Headers.ContentDisposition = disposition.ToString();
return File(file.Content, XlsxContentType);
}
}