переработано переназначение
Tests / frontend-test (push) Successful in 1m8s

This commit is contained in:
gorbenko.pavel
2026-08-13 17:07:05 +05:00
parent bcd54d7bc8
commit 293876dd50
3 changed files with 204 additions and 35 deletions
@@ -63,35 +63,52 @@ export class ApproverService {
return delegated ? { ...delegated, isDelegator: true } : undefined;
}
// Делегирование: согласующий добавляет делеганта ПЕРЕД собой (Order — между
// предыдущей строкой и своей). Делегат согласует раньше и сам не делегирует
// (allowReassign всегда false у новой строки) — зато делегировавший (текущий
// пользователь) сохраняет право переназначить эту строку ещё раз, если делегат
// тоже окажется недоступен (см. getPendingRowForCurrentUser/isDelegator).
// Делегирование: согласующий добавляет одного или нескольких делегантов ПЕРЕД
// собой. Order каждому — из равных долей промежутка между предыдущей строкой и
// своей, В ПОРЯДКЕ ВЫБОРА: выбранный первым согласует первым. Делеганты сами не
// делегируют (allowReassign всегда false) — зато делегировавший (текущий
// пользователь) сохраняет право переназначить строку ещё раз, если кто-то из них
// окажется недоступен (см. getPendingRowForCurrentUser/isDelegator).
//
// Дробный Order — осознанно: он позволяет вклиниться между уже существующими
// шагами, не переписывая их (в SharePoint StepOrder — Number, дробные значения
// хранит). Делители растут с числом делегантов, но при реальных количествах
// (единицы) точности double хватает с колоссальным запасом.
public async delegate(
requestId: number,
currentRowId: number,
user: ISiteUser
users: ISiteUser[]
): Promise<void> {
if (users.length === 0) return;
const rows = await this.approval.getForRequest(requestId);
const current = rows.filter((r) => r.id === currentRowId)[0];
if (!current) return;
// Предыдущий шаг — с меньшим Order. У параллельных шагов Order общий, поэтому
// берём именно последний меньший, а не «строку выше по списку».
const before = rows.filter((r) => r.order < current.order);
const prevOrder = before.length > 0 ? before[before.length - 1].order : current.order - 1;
const newOrder = (prevOrder + current.order) / 2;
const position = await this.approval.getJobTitle(user.loginName);
const step = (current.order - prevOrder) / (users.length + 1);
const delegatedById = await this.approval.getCurrentUserId();
// Делегат наследует фазу той строки, вместо которой согласует (согласование/исполнение).
// Обе строки — делегат и прежний согласующий — уходят в «Ждёт очереди»,
// activateNextQueued затем активирует делегата (у него меньший order).
await this.approval.addApproverRow(
requestId,
{ email: user.email, position, allowReassign: false },
newOrder,
REQUEST_STATUS.queued,
current.phase,
delegatedById
);
// Последовательно, а не Promise.all: добавление строки резолвит пользователя и
// пишет в список — параллельные пачки упираются в троттлинг SharePoint, а
// выигрыш во времени на единицах строк незаметен.
for (let i = 0; i < users.length; i += 1) {
const user = users[i];
const position = await this.approval.getJobTitle(user.loginName).catch(() => '');
// Делегат наследует фазу той строки, вместо которой согласует (согласование/исполнение).
await this.approval.addApproverRow(
requestId,
{ email: user.email, position, allowReassign: false },
prevOrder + step * (i + 1),
REQUEST_STATUS.queued,
current.phase,
delegatedById
);
}
// Все строки — и делеганты, и прежний согласующий — уходят в «Ждёт очереди»,
// activateNextQueued затем активирует первого делеганта (у него меньший order).
await this.approval.setStatus(current.id, REQUEST_STATUS.queued);
await this.approval.activateNextQueued(requestId);
emitRequestsChanged();