feat: request portal — bespoke forms + approval mechanism
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user