Reminders: убрать Category из общей части DTO, добавить ReceiverEmail/ReceiverName, ограничить чтение испытательного срока последними партициями
- ReminderDto: базовый класс сузился до ReceiverEmail/ReceiverName/Url; RequestTypeId/RequestTypeName/RequestId/Status/RequestUniqueCode остались только в ApprovalOverdueReminderDto — ProbationEndingReminderDto они не нужны. Дискриминатор полиморфной сериализации ($type) больше не дублируется отдельным enum-полем Category. - ApprovalOverdueReminderDto: старый Email сохранён рядом с унаследованным ReceiverEmail (то же значение) — контракт можно переключать постепенно. - ApprovalRow/RowSelect: добавлен Title согласующего (TansuSiteApprover/Title) — заполняет ReceiverName для просроченных шагов, когда он есть в SharePoint. - EmployeeDirectorySnapshot.NameByEmail: обратный поиск ФИО руководителя по email — заполняет ReceiverName для напоминаний об испытательном сроке без лишнего похода в api.tnsu.kz (данные уже загружены для EmailOf). - GetProbationEndingAsync: испытательный срок не длиннее 6 месяцев, поэтому читаем только текущую и последнюю архивную партицию (не весь архив) и фильтруем строки по HireDate не старше 6 месяцев — экономим лишние запросы к SharePoint без потери актуальных заявок.
This commit is contained in:
@@ -207,12 +207,13 @@ public sealed class ReminderService(
|
||||
row.ApproverEmail!,
|
||||
(int)Math.Floor((now - deadline).TotalHours),
|
||||
row.AskOverdueReason ?? type.AskOverdueReason ?? false,
|
||||
BuildUrl(type, row, listId),
|
||||
type.Id,
|
||||
type.Name,
|
||||
row.TicketId,
|
||||
row.Status,
|
||||
uniqueCodes.GetValueOrDefault(row.TicketId)));
|
||||
uniqueCodes.GetValueOrDefault(row.TicketId),
|
||||
BuildUrl(type, row, listId),
|
||||
row.ApproverName));
|
||||
}
|
||||
|
||||
return reminders;
|
||||
@@ -249,7 +250,7 @@ public sealed class ReminderService(
|
||||
|
||||
private const string RowSelect =
|
||||
"$select=Id,ApprovalStatus,Phase,Date,SLAhours,AskOverdueReason,TicketId," +
|
||||
"TansuSiteApprover/EMail&$expand=TansuSiteApprover&$top=5000";
|
||||
"TansuSiteApprover/EMail,TansuSiteApprover/Title&$expand=TansuSiteApprover&$top=5000";
|
||||
|
||||
private async Task<IReadOnlyList<ApprovalRow>> ReadRowsAsync(
|
||||
string siteUrl, string listTitle, string filter, CancellationToken cancellationToken)
|
||||
@@ -316,7 +317,16 @@ public sealed class ReminderService(
|
||||
|
||||
var archived = await partitionRegistry.GetArchivedAsync(type, cancellationToken);
|
||||
var current = await partitionRegistry.BuildCurrentAsync(type, archived, cancellationToken);
|
||||
var partitions = archived.Select(x => (x.ListId, x.Number)).Append((current.ListId, current.Number));
|
||||
|
||||
// Испытательный срок — не больше 6 месяцев (см. ProbationMonthsByOption),
|
||||
// поэтому заявка, способная дать напоминание сегодня, обязана лежать в
|
||||
// текущей партиции или в партиции прямо перед ней: более старая партиция
|
||||
// физически не может содержать ещё актуальный испытательный срок. Читать
|
||||
// весь архив, как это делает GetOverdueAsync (там заявка может висеть
|
||||
// непринятой годами), здесь незачем — только лишняя нагрузка на SharePoint.
|
||||
var lastArchived = archived.OrderByDescending(x => x.Number).FirstOrDefault();
|
||||
var partitions = new List<(string ListId, int Number)> { (current.ListId, current.Number) };
|
||||
if (lastArchived is not null) partitions.Add((lastArchived.ListId, lastArchived.Number));
|
||||
|
||||
// Обе базы сотрудников читаются по одному разу на весь обход партиций (а не
|
||||
// на каждого кандидата): точечного поиска по ФИО в api.tnsu.kz нет, а нужны
|
||||
@@ -356,8 +366,14 @@ public sealed class ReminderService(
|
||||
var dataListTitle = await sharePoint.GetListTitleAsync(siteUrl, listId, cancellationToken);
|
||||
if (string.IsNullOrWhiteSpace(dataListTitle)) return [];
|
||||
|
||||
// HireDate не старше 6 месяцев — тот же смысл, что и ограничение на две
|
||||
// последних партиции (см. GetProbationEndingAsync): более старый приём уже
|
||||
// не может дать напоминание, читать его строки незачем даже в пределах
|
||||
// партиции, где они могут лежать вперемешку со свежими.
|
||||
var since = now.AddMonths(-6);
|
||||
var filter =
|
||||
$"$filter=RequestStatus eq '{HireRequestStatuses.Approved}' or RequestStatus eq '{HireRequestStatuses.Closed}'";
|
||||
$"$filter=(RequestStatus eq '{HireRequestStatuses.Approved}' or RequestStatus eq '{HireRequestStatuses.Closed}')" +
|
||||
$" and HireDate ge datetime'{since:yyyy-MM-ddTHH:mm:ssZ}'";
|
||||
var items = await sharePoint.GetItemsByListTitleAsync(
|
||||
siteUrl, dataListTitle, $"{HireRowSelect}&{filter}", cancellationToken);
|
||||
var rows = items.Select(HireRow.TryParse).Where(r => r is not null).Select(r => r!).ToList();
|
||||
@@ -425,11 +441,7 @@ public sealed class ReminderService(
|
||||
ProbationEndsAt: probationEndsAt,
|
||||
DaysUntilProbationEnds: daysLeft,
|
||||
Url: BuildProbationUrl(type, row, listId),
|
||||
RequestTypeId: type.Id,
|
||||
RequestTypeName: type.Name,
|
||||
RequestId: row.Id,
|
||||
Status: row.Status,
|
||||
RequestUniqueCode: row.RequestUniqueCode);
|
||||
ReceiverName: directory.NameByEmail(managerEmail));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user