Files
tnsu-approval-route/backend/Tnsu.ApprovalRoute.Application/Reports/SelectionRow.cs
T
gorbenko.pavel 24d2cd7a6a 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>
2026-08-27 18:01:24 +05:00

57 lines
2.9 KiB
C#

using System.Text.Json;
namespace Tnsu.ApprovalRoute.Application.Reports;
/// <summary>
/// Строка списка «Заявка на подбор — данные». Имена колонок — как в request-portal
/// (provisioning.json формы RecruitingSelection).
/// </summary>
internal sealed record SelectionRow(
int Id,
string ListId,
string Status,
string? Position,
string? DepartmentName,
string? ObjectProject,
int VacanciesCount,
DateOnly? SubmitDate,
string? Initiator,
string? OpenReason,
string? OpenReasonDetails)
{
/// <summary>
/// Поля, которые нужны отчёту, — перечислены явно: список данных широкий
/// (полсотни колонок, часть — Note с многострочным текстом), и тянуть его
/// целиком по всем партициям только ради шестнадцати колонок незачем.
/// </summary>
public const string Select =
"$select=Id,RequestStatus,Position,DepartmentName,ObjectProject,OpenVacanciesCount," +
"SubmitDate,Created,OpenReason,OpenReasonDetails,Responsible/Title&$expand=Responsible&$top=5000";
public static SelectionRow? TryParse(JsonElement item, string listId)
{
var id = SharePointJson.Int(item, "Id");
if (id is null) return null;
return new SelectionRow(
id.Value,
listId,
SharePointJson.String(item, "RequestStatus") ?? string.Empty,
SharePointJson.String(item, "Position"),
SharePointJson.String(item, "DepartmentName"),
SharePointJson.String(item, "ObjectProject"),
// Пустое «Количество открытых вакансий» — это одна вакансия: заявку
// подают на конкретную должность, и ноль вакансий в отчёте сломал бы
// и итог по колонке E, и признак «закрыта» (0 из 0).
SharePointJson.Int(item, "OpenVacanciesCount") is { } count and > 0 ? count : 1,
// Дата подачи — поле формы, заполняется руками и может быть пустым;
// тогда берём дату создания элемента. Тем же правилом заявки
// отбираются по интервалу (см. VacancyReportService.PeriodFilter) —
// иначе заявка попадала бы в отчёт, но с пустой колонкой H.
SharePointJson.Date(item, "SubmitDate") ?? SharePointJson.Date(item, "Created"),
SharePointJson.PersonTitle(item, "Responsible"),
SharePointJson.String(item, "OpenReason"),
SharePointJson.String(item, "OpenReasonDetails"));
}
}