Files
request-portal/src/webparts/requestPortal/shared/approval/approverDecision.service.ts
T
2026-08-03 09:35:14 +05:00

93 lines
4.4 KiB
TypeScript

import { getSP } from '../pnp/pnpjs';
import '@pnp/sp/webs';
import '@pnp/sp/lists';
import '@pnp/sp/items';
import { ApprovalListService } from './approvalList.service';
import { REQUEST_STATUS } from './models/statuses';
import { IApprovalRow } from './models/IApprovalRow';
import { ISiteUser } from './models/ISiteUser';
export type ApproverDecision = 'approve' | 'return' | 'reject';
// Решение согласующего → статус его строки в списке согласования.
const ROW_STATUS: Record<ApproverDecision, string> = {
approve: REQUEST_STATUS.approved,
return: REQUEST_STATUS.returned,
reject: REQUEST_STATUS.rejected,
};
// Действия согласующего: определение его строки, применение решения,
// делегирование и пересчёт статуса заявки в контентном списке.
//
// ВНИМАНИЕ: используется в режиме согласующего, который открывается по конкретной
// заявке (requestId). Пока в портале нет маршрутизации на форму заявки, этот сервис
// готов, но UI-режим согласующего ещё не подключён (см. план).
export class ApproverService {
constructor(
private readonly approval: ApprovalListService,
private readonly dataListTitle: string
) {}
// Ожидающая строка текущего пользователя, если сейчас его очередь; иначе undefined.
// Последовательное согласование: активна первая строка со статусом «На согласовании».
public async getPendingRowForCurrentUser(requestId: number): Promise<IApprovalRow | undefined> {
const rows = await this.approval.getForRequest(requestId);
const userId = await this.approval.getCurrentUserId();
const firstPending = rows.filter((r) => r.status === REQUEST_STATUS.onApproval)[0];
if (firstPending && firstPending.approverId === userId) return firstPending;
return undefined;
}
// Делегирование: согласующий добавляет делеганта ПЕРЕД собой (Order — между
// предыдущей строкой и своей). Делегат согласует раньше и сам не делегирует.
public async delegate(
requestId: number,
currentRowId: number,
user: ISiteUser
): Promise<void> {
const rows = await this.approval.getForRequest(requestId);
const current = rows.filter((r) => r.id === currentRowId)[0];
if (!current) return;
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);
await this.approval.addApproverRow(
requestId,
{ email: user.email, position, allowReassign: false },
newOrder,
REQUEST_STATUS.onApproval
);
}
// Применяет решение: обновляет строку и пересчитывает статус заявки.
// Возвращает новый статус заявки.
public async decide(
requestId: number,
rowId: number,
decision: ApproverDecision,
comment: string
): Promise<string> {
await this.approval.setDecision(rowId, ROW_STATUS[decision], comment);
const rows = await this.approval.getForRequest(requestId);
const requestStatus = this.computeRequestStatus(rows);
await this.updateRequestStatus(requestId, requestStatus);
return requestStatus;
}
private computeRequestStatus(rows: IApprovalRow[]): string {
if (rows.some((r) => r.status === REQUEST_STATUS.rejected)) return REQUEST_STATUS.rejected;
if (rows.some((r) => r.status === REQUEST_STATUS.returned)) return REQUEST_STATUS.returned;
if (rows.length > 0 && rows.every((r) => r.status === REQUEST_STATUS.approved)) {
return REQUEST_STATUS.approved;
}
return REQUEST_STATUS.onApproval;
}
private async updateRequestStatus(id: number, status: string): Promise<void> {
await getSP().web.lists.getByTitle(this.dataListTitle).items.getById(id).update({
Status: status,
});
}
}