feat: added optin to add delegant for executor and change delegant

This commit is contained in:
Тимур Городецкий
2026-08-10 13:55:05 +05:00
parent 3a15d83ded
commit 99f0946968
23 changed files with 407 additions and 40 deletions
@@ -11,6 +11,13 @@ import { emitRequestsChanged } from '../requestEvents';
// approve/return/reject — согласующий; execute — исполнитель отметил «Выполнено».
export type ApproverDecision = 'approve' | 'return' | 'reject' | 'execute';
// Активная строка текущего пользователя: либо он сам назначен на неё
// (isDelegator=false), либо это его делегат недоступен (в отпуске и т.п.) и он,
// как делегировавший, вправе переназначить шаг ещё раз (isDelegator=true).
export interface IPendingRow extends IApprovalRow {
isDelegator: boolean;
}
// Решение → статус его строки в списке согласования.
const ROW_STATUS: Record<ApproverDecision, string> = {
approve: REQUEST_STATUS.approved,
@@ -35,7 +42,12 @@ export class ApproverService {
// Очередь уже проставлена на запись (см. createSteps/advanceQueue) — активна
// ровно одна строка со статусом «На согласовании» на фазу, искать «первую по
// порядку» не нужно.
public async getPendingRowForCurrentUser(requestId: number): Promise<IApprovalRow | undefined> {
//
// Если пользователь сам не назначен на активную строку, но именно он когда-то
// делегировал её текущему держателю (delegatedById) — строка тоже считается
// «его», но с isDelegator=true: DecisionPanel в этом случае даёт только
// переназначить (не решать за делегата), см. её использование там.
public async getPendingRowForCurrentUser(requestId: number): Promise<IPendingRow | undefined> {
const rows = await this.approval.getForRequest(requestId);
const userId = await this.approval.getCurrentUserId();
// Активные строки текущего шага: согласующий («На согласовании») или, когда
@@ -45,11 +57,17 @@ export class ApproverService {
const active = rows.filter(
(r) => r.status === REQUEST_STATUS.onApproval || r.status === REQUEST_STATUS.onExecution
);
return active.filter((r) => r.approverId === userId)[0];
const own = active.filter((r) => r.approverId === userId)[0];
if (own) return { ...own, isDelegator: false };
const delegated = active.filter((r) => r.delegatedById === userId)[0];
return delegated ? { ...delegated, isDelegator: true } : undefined;
}
// Делегирование: согласующий добавляет делеганта ПЕРЕД собой (Order — между
// предыдущей строкой и своей). Делегат согласует раньше и сам не делегирует.
// предыдущей строкой и своей). Делегат согласует раньше и сам не делегирует
// (allowReassign всегда false у новой строки) — зато делегировавший (текущий
// пользователь) сохраняет право переназначить эту строку ещё раз, если делегат
// тоже окажется недоступен (см. getPendingRowForCurrentUser/isDelegator).
public async delegate(
requestId: number,
currentRowId: number,
@@ -62,6 +80,7 @@ export class ApproverService {
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 delegatedById = await this.approval.getCurrentUserId();
// Делегат наследует фазу той строки, вместо которой согласует (согласование/исполнение).
// Обе строки — делегат и прежний согласующий — уходят в «Ждёт очереди»,
// activateNextQueued затем активирует делегата (у него меньший order).
@@ -70,7 +89,8 @@ export class ApproverService {
{ email: user.email, position, allowReassign: false },
newOrder,
REQUEST_STATUS.queued,
current.phase
current.phase,
delegatedById
);
await this.approval.setStatus(current.id, REQUEST_STATUS.queued);
await this.approval.activateNextQueued(requestId);