Files
tnsu-approval-route/backend/Tnsu.ApprovalRoute.Application/Reminders/EmployeeDirectorySnapshot.cs
T
gorbenko.pavel 95d460c80a
Tests / frontend-test (push) Successful in 46s
Tests / backend-test (push) Successful in 2m22s
Расширение reminders (#19)
Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz>
Reviewed-on: http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/pulls/19
2026-08-27 11:43:45 +00:00

96 lines
5.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Tnsu.ApprovalRoute.Infrastructure.TnsuPortal;
namespace Tnsu.ApprovalRoute.Application.Reminders;
internal enum EmploymentStatus
{
/// <summary>
/// Статус не определяли. EmploymentOf такого не возвращает — значение существует
/// только как значение по умолчанию (default(EmploymentStatus)); трактуется
/// наравне с <see cref="NotFound"/>, то есть «трудоустройство не подтверждено».
/// </summary>
None = 0,
/// <summary>Сотрудника с таким ФИО в справочнике нет вовсе — трудоустройство не подтверждено.</summary>
NotFound = 1,
/// <summary>Работает хотя бы в одной компании.</summary>
Employed = 2,
/// <summary>Есть в справочнике, но уволен из всех компаний, где числился.</summary>
Dismissed = 3,
}
/// <summary>
/// Оба справочника api.tnsu.kz разом, разобранные по ФИО: дата увольнения есть
/// только в /Employee/FullBase, а email — только в /Employee/All, поэтому нужны
/// оба. Ищем ПО ФИО, потому что точечного поиска по нему у api.tnsu.kz нет
/// (эндпоинта /Employee/ByFio не существует), а в заявке на приём кандидат
/// записан именно ФИО (колонка CandidateName) — временное решение, см.
/// <see cref="ITnsuPortalClient.GetAllEmployeesAsync"/>.
///
/// ФИО не уникально: один человек может числиться в нескольких компаниях (тогда
/// это несколько записей одного и того же человека), но с тем же успехом могут
/// совпасть ФИО двух разных людей — различить эти два случая по одному ФИО нельзя,
/// поэтому все совпадения трактуются как записи одного человека.
/// </summary>
internal sealed class EmployeeDirectorySnapshot
{
private readonly ILookup<string, EmployeeFullBaseInfo> _fullBaseByFio;
private readonly ILookup<string, EmployeeContactInfo> _contactsByFio;
private readonly IReadOnlyDictionary<string, string> _fioByEmail;
public EmployeeDirectorySnapshot(
IReadOnlyList<EmployeeFullBaseInfo> fullBase, IReadOnlyList<EmployeeContactInfo> contacts)
{
_fullBaseByFio = fullBase.ToLookup(e => Normalize(e.Fio));
_contactsByFio = contacts.ToLookup(e => Normalize(e.Fio));
// Обратный индекс email → ФИО — нужен, чтобы подписать напоминание именем
// получателя, когда известен только его email (руководитель кандидата,
// см. GetManagerAsync). При дублях email в справочнике побеждает первая
// запись — этого достаточно для отображения имени, не для идентификации.
_fioByEmail = contacts
.Where(e => !string.IsNullOrWhiteSpace(e.Email))
.GroupBy(e => e.Email!.Trim().ToLowerInvariant())
.ToDictionary(g => g.Key, g => g.First().Fio);
}
/// <summary>
/// Уволенным считаем только того, у кого дата увольнения стоит во ВСЕХ его
/// записях: сотрудник, уволенный из одной компании холдинга, но оставшийся в
/// другой, продолжает работать — и напоминание ему по-прежнему нужно.
/// </summary>
public EmploymentStatus EmploymentOf(string fio)
{
var entries = _fullBaseByFio[Normalize(fio)].ToList();
if (entries.Count == 0) return EmploymentStatus.NotFound;
return entries.All(e => e.DismissalDate is not null)
? EmploymentStatus.Dismissed
: EmploymentStatus.Employed;
}
/// <summary>
/// Первый непустой email среди записей этого ФИО. У одного человека записей
/// несколько (по компании на каждую), а email у него один и тот же — но
/// заполнен он может быть не в каждой записи.
/// </summary>
public string? EmailOf(string fio) =>
_contactsByFio[Normalize(fio)]
.Select(e => e.Email)
.FirstOrDefault(email => !string.IsNullOrWhiteSpace(email));
/// <summary>ФИО по email — для ReceiverName, когда известен только email получателя (см. EmailOf).</summary>
public string? NameByEmail(string email) =>
_fioByEmail.GetValueOrDefault(email.Trim().ToLowerInvariant());
/// <summary>
/// В справочнике и в заявке ФИО набирают руками, поэтому сравниваем без учёта
/// регистра и лишних пробелов между словами (в живых данных встречаются и
/// двойные пробелы, и неразрывный пробел — см. поле department в /Employee/All).
/// </summary>
private static string Normalize(string fio) =>
string.Join(' ', fio.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries)).ToLowerInvariant();
}