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:
gorbenko.pavel
2026-08-27 18:01:24 +05:00
parent 95d460c80a
commit 24d2cd7a6a
15 changed files with 1444 additions and 0 deletions
@@ -0,0 +1,19 @@
namespace Tnsu.ApprovalRoute.Application.Reports;
/// <summary>Настройки отчётов (секция Reports в конфигурации).</summary>
public sealed class ReportsOptions
{
/// <summary>
/// PageUrl типа заявки «Заявка на подбор» (см. request-portal,
/// forms/RecruitingSelection/model.ts, FORM_ID) — по нему ищется RequestType,
/// из партиций которого берутся сами вакансии.
/// </summary>
public string RecruitingSelectionPageUrl { get; set; } = "recruiting-selection";
/// <summary>
/// PageUrl типа заявки «Приём на работу» — из его партиций берутся закрытия
/// вакансий: заявка на приём ссылается на заявку на подбор колонками
/// SelectionRequestId / SelectionRequestListId.
/// </summary>
public string RecruitingHirePageUrl { get; set; } = "recruiting-hire";
}