feat: отчёт по вакансиям в xlsx за период
GET api/reports/vacancies?from=&to= отдаёт лист «Список вакансий (все)» по образцу ручного отчёта HR: те же шестнадцать колонок, разделители по объектам, двойная нумерация, итоги формулами. Данные — из двух форм портала: «Заявка на подбор» даёт вакансию, «Приём на работу» — её закрытие (связь по SelectionRequestId + SelectionRequestListId). Рекрутёр и дата закрытия берутся из фазы «Исполнение» парного списка «— согласование»: в самой заявке их нет. Период отбирает заявки по «Дате подачи заявки» с откатом на Created — поле заполняется руками и часто пустое. Заявки на приём периодом не ограничены: приём по вакансии из начала интервала мог оформляться уже после его конца. Колонка «Комментарии» остаётся пустой — в ручном отчёте это пометки рекрутёра, которым в заявках пока негде храниться. Генерация — ClosedXML (MIT): нужны стили, формулы и autofilter, а EPPlus с v5 под Polyform Noncommercial. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
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) : 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);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ using Tnsu.ApprovalRoute.Api.Filters;
|
||||
using Tnsu.ApprovalRoute.Application;
|
||||
using Tnsu.ApprovalRoute.Application.ListPartitioning;
|
||||
using Tnsu.ApprovalRoute.Application.Reminders;
|
||||
using Tnsu.ApprovalRoute.Application.Reports;
|
||||
using Tnsu.ApprovalRoute.Application.Common.Interfaces;
|
||||
using Tnsu.ApprovalRoute.Infrastructure;
|
||||
|
||||
@@ -57,6 +58,7 @@ builder.Services.AddApprovalRouteInfrastructure(builder.Configuration);
|
||||
builder.Services.AddApprovalRouteApplication();
|
||||
builder.Services.Configure<RemindersOptions>(builder.Configuration.GetSection("Reminders"));
|
||||
builder.Services.Configure<ListPartitioningOptions>(builder.Configuration.GetSection("ListPartitioning"));
|
||||
builder.Services.Configure<ReportsOptions>(builder.Configuration.GetSection("Reports"));
|
||||
|
||||
builder.Services.AddHangfire(configuration => configuration
|
||||
.SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
|
||||
|
||||
Reference in New Issue
Block a user