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) : 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, потому что строки собирает админка — у неё уже есть справочники /// сотрудников и подписи шагов (см. RouteExportWorkbook). /// [HttpPost("routes")] [Produces(XlsxContentType)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public IActionResult Routes([FromBody] RouteExportRequest request) { if (request.Sheets is not { Count: > 0 }) return BadRequest(new { errors = new[] { "Не выбрано ни одного типа заявки." } }); var file = RouteExportWorkbook.Build(request); var disposition = new ContentDispositionHeaderValue("attachment"); disposition.SetHttpFileName(RouteExportWorkbook.FileName); Response.Headers.ContentDisposition = disposition.ToString(); return File(file, XlsxContentType); } }