93 lines
4.4 KiB
TypeScript
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,
|
|
});
|
|
}
|
|
}
|