240e5984b1
На одну заявку на подбор приходится несколько заявок на приём («Разнорабочий, 4 вакансии» → 4 кандидата), и раньше они схлопывались в одну строку: ФИО не было вовсе, а «Дата выхода» показывала только САМУЮ ПОЗДНЮЮ из четырёх. Кто именно вышел и когда — из отчёта было не узнать. Теперь вакансия занимает столько строк, сколько по ней приёмов. Построчные колонки — «Дата выхода», новая «Ф.И.О. нанимаемого сотрудника» и «Вместо сотрудника Ф.И.О.» (у каждого приёма своё «взамен кого»); все остальные общие и объединяются по вертикали. Модель: VacancyReportRow.Lines — именно СТРОКИ ЛИСТА, а не «приёмы». У вакансии без приёмов строка всё равно одна (иначе она пропала бы из отчёта), с пустыми ФИО и датой, но с «Вместо сотрудника» из причины открытия вакансии — прежнее поведение сохранено. Реальное число приёмов осталось в ClosedCount. Отклонённые приёмы теперь отбрасываются целиком, а не только из счётчика: отдельная строка с ФИО отклонённого кандидата утверждала бы «вот кого приняли». Объединение заводится только при lineCount > 1 — область 1×1 Excel считает некорректной и портит файл. Значение общих колонок пишется ТОЛЬКО в первую строку: у объединённой области Excel хранит его в верхней левой ячейке, а дубли сломали бы SUM в итогах (посчитал бы вакансию по разу на каждый приём) — на это есть отдельный тест. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
73 lines
4.6 KiB
C#
73 lines
4.6 KiB
C#
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 = "частично";
|
|
}
|