Отчет по вакансиям (#20)
Tests / frontend-test (push) Successful in 47s
Tests / backend-test (push) Successful in 4m55s

Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz>
Reviewed-on: http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/pulls/20
This commit was merged in pull request #20.
This commit is contained in:
2026-08-31 11:00:09 +00:00
parent 95d460c80a
commit e2b6227015
16 changed files with 2198 additions and 0 deletions
@@ -0,0 +1,72 @@
namespace Tnsu.ApprovalRoute.Application.Reports;
/// <summary>
/// Отчёт по вакансиям за период — ровно то, что ложится на лист «Список вакансий
/// (все)» в xlsx: интервал плюс вакансии, разбитые по объектам/проектам.
/// </summary>
public sealed record VacancyReport(DateOnly From, DateOnly To, IReadOnlyList<VacancyReportGroup> Groups);
/// <summary>
/// Объект/проект и его вакансии. Имя группы становится строкой-разделителем на
/// листе (как «КЭС КУРЧАТОВ» в образце отчёта); пустое имя — вакансии, у которых
/// объект в заявке не указан, они идут первым блоком без разделителя.
/// </summary>
public sealed record VacancyReportGroup(string Name, IReadOnlyList<VacancyReportRow> Rows);
/// <summary>
/// Одна ВАКАНСИЯ (заявка на подбор). Поля этой записи — ОБЩИЕ для всех её строк
/// на листе, там они объединяются по вертикали; то, что различается от приёма к
/// приёму, лежит в Lines.
///
/// Колонки нумерации, «в работе», «срок отработки» и итоги здесь не хранятся:
/// их считает сам xlsx формулами, чтобы цифры пересчитывались, если строку в
/// файле поправят руками.
/// </summary>
public sealed record VacancyReportRow(
// «Вакансия» — должность из заявки на подбор.
string Position,
// «Подразделение» ответственного за подачу заявки.
string? Department,
// «Кол-во вакансий».
int VacanciesCount,
// «количество закрытых вакансий»: сколько заявок на приём завели по этой заявке на подбор.
int ClosedCount,
// «Дата подачи заявки».
DateOnly? SubmitDate,
// «Дата закрытия вакансии»: дата решения по последнему шагу исполнения.
DateOnly? ClosingDate,
// «Инициатор заявки».
string? Initiator,
// «Закрыта вакансия»: да / нет / частично.
string ClosedFlag,
// «Рекрутер»: исполнитель фазы исполнения заявки на подбор.
string? Recruiter,
// Строки листа по этой вакансии — ВСЕГДА не меньше одной (см. VacancyReportLine).
IReadOnlyList<VacancyReportLine> Lines);
/// <summary>
/// Одна СТРОКА ЛИСТА в пределах вакансии — то, что различается между её строками.
/// Обычно это один приём: на заявку на подбор «Разнорабочий, 4 вакансии»
/// приходится четыре заявки на приём, у каждой свой кандидат, своя дата выхода и
/// свой «взамен кого».
///
/// Именно СТРОКА, а не «приём»: у вакансии, по которой приёмов ещё нет (или все
/// отклонены), строка всё равно одна — с пустыми ФИО и датой выхода, но с
/// заполненным «Вместо сотрудника» из причины открытия вакансии. Реальное число
/// приёмов лежит в VacancyReportRow.ClosedCount, так что информация не теряется.
/// </summary>
public sealed record VacancyReportLine(
// «Ф.И.О. нанимаемого сотрудника» — кандидат из заявки на приём.
string? CandidateName,
// «Дата выхода» — дата приёма этого кандидата.
DateOnly? StartDate,
// «Вместо сотрудника Ф.И.О.».
string? ReplacedEmployee);
/// <summary>Значения колонки O. «Пауза» из образца отчёта не выводится — такого статуса в заявке нет.</summary>
public static class VacancyClosedFlags
{
public const string Yes = "да";
public const string No = "нет";
public const string Partial = "частично";
}