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; /// /// Выгрузки отчётов файлами. В отличие от остальных контроллеров отдаёт не /// ApiResponse, а сам файл: получатель — кнопка «скачать» в портале и Power /// Automate, обоим нужен готовый xlsx, а не JSON с телом в base64. /// [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"; /// /// Отчёт по вакансиям за период — лист «Список вакансий (все)» по данным /// заявок на подбор и на приём. /// /// Период отбирает заявки по «Дате подачи заявки» (колонка H отчёта). Заявки /// на приём периодом НЕ ограничены: у вакансии, поданной в начале интервала, /// приём мог оформляться уже после его конца. /// [HttpGet("vacancies")] [Produces(XlsxContentType)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task 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); } /// /// Выгрузка маршрутов согласования выбранных типов заявок в xlsx (лист на тип). /// POST — потому что в теле список id типов (см. RouteExportService). /// [HttpPost("routes")] [Produces(XlsxContentType)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task 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); } }