Мониторинг: логи создания заявок, отправки на согласование и решений согласующих
Tests / frontend-test (pull_request) Successful in 4m39s

saveRequestItem — «Создана заявка» (тип, id, код); writeRoute/writeApprovers — «Заявка отправлена на
согласование» (статус, число согласующих и исполнителей); ApproverService.decide — «Заявка согласована /
возвращена / отклонена / выполнена исполнителем» (новый статус, строка маршрута, комментарий до 500
символов). Пользователь — из токена мониторинга.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
gorbenko.pavel
2026-10-30 17:18:18 +05:00
parent cce5166633
commit eaa1c855d9
3 changed files with 33 additions and 0 deletions
@@ -1,4 +1,5 @@
import { WebPartContext } from '@microsoft/sp-webpart-base'; import { WebPartContext } from '@microsoft/sp-webpart-base';
import { log } from '@tnsu/monitoring-web';
import { resolveRoute } from './approvalRoute.service'; import { resolveRoute } from './approvalRoute.service';
import { ApprovalListService, IApproverToWrite } from './approvalList.service'; import { ApprovalListService, IApproverToWrite } from './approvalList.service';
import { IMatrixRow } from './models/IMatrixRow'; import { IMatrixRow } from './models/IMatrixRow';
@@ -68,6 +69,11 @@ export async function writeApprovers(
await approval.createSteps(requestId, approvers, rowStatus, APPROVAL_PHASE.approval, 1); await approval.createSteps(requestId, approvers, rowStatus, APPROVAL_PHASE.approval, 1);
if (submitted) { if (submitted) {
await approval.activateNextQueued(requestId); await approval.activateNextQueued(requestId);
log.info('Заявка отправлена на согласование', {
'request.id': requestId,
'request.status': status,
'route.approvers': approvers.length,
});
} }
} }
@@ -138,5 +144,11 @@ export async function writeRoute(
if (submitted) { if (submitted) {
await approval.activateNextQueued(requestId); await approval.activateNextQueued(requestId);
log.info('Заявка отправлена на согласование', {
'request.id': requestId,
'request.status': status,
'route.approvers': approvers.length,
'route.executors': executors.length,
});
} }
} }
@@ -1,3 +1,4 @@
import { log } from '@tnsu/monitoring-web';
import { getSP } from '../pnp/pnpjs'; import { getSP } from '../pnp/pnpjs';
import '@pnp/sp/webs'; import '@pnp/sp/webs';
import '@pnp/sp/lists'; import '@pnp/sp/lists';
@@ -25,6 +26,14 @@ export interface IPendingRow extends IApprovalRow {
isDelegator: boolean; isDelegator: boolean;
} }
// Решение → текст события в логах мониторинга.
const DECISION_MESSAGE: Record<ApproverDecision, string> = {
approve: 'Заявка согласована',
return: 'Заявка возвращена на доработку',
reject: 'Заявка отклонена',
execute: 'Заявка выполнена исполнителем',
};
// Решение → статус его строки в списке согласования. // Решение → статус его строки в списке согласования.
const ROW_STATUS: Record<ApproverDecision, string> = { const ROW_STATUS: Record<ApproverDecision, string> = {
approve: REQUEST_STATUS.approved, approve: REQUEST_STATUS.approved,
@@ -336,6 +345,14 @@ export class ApproverService {
const rows = await this.approval.getForRequest(requestId); const rows = await this.approval.getForRequest(requestId);
const requestStatus = this.computeRequestStatus(rows); const requestStatus = this.computeRequestStatus(rows);
await this.updateRequestStatus(requestId, requestStatus); await this.updateRequestStatus(requestId, requestStatus);
log.info(DECISION_MESSAGE[decision], {
'request.id': requestId,
'request.list': this.dataListRef,
'request.status': requestStatus,
'approval.decision': decision,
'approval.rowId': rowId,
'approval.comment': comment.slice(0, 500),
});
emitRequestsChanged(); emitRequestsChanged();
return requestStatus; return requestStatus;
} }
@@ -2,6 +2,7 @@ import '@pnp/sp/webs';
import '@pnp/sp/lists'; import '@pnp/sp/lists';
import '@pnp/sp/items'; import '@pnp/sp/items';
import { IList } from '@pnp/sp/lists'; import { IList } from '@pnp/sp/lists';
import { log } from '@tnsu/monitoring-web';
import { getSP } from './pnp/pnpjs'; import { getSP } from './pnp/pnpjs';
import { buildRequestUniqueCode } from './requestCode'; import { buildRequestUniqueCode } from './requestCode';
import { dataListTitle } from './listTitles'; import { dataListTitle } from './listTitles';
@@ -58,5 +59,8 @@ export async function saveRequestItem(
// Код известен только после создания элемента (нужен его Id). // Код известен только после создания элемента (нужен его Id).
const code = buildRequestUniqueCode(codePrefix, id, new Date()); const code = buildRequestUniqueCode(codePrefix, id, new Date());
await list.items.getById(id).update({ RequestUniqueCode: code }); await list.items.getById(id).update({ RequestUniqueCode: code });
// Событие для мониторинга: кто (пользователь — из токена) и какую заявку создал. Отправка на согласование
// и решения пишутся отдельно — approvalSubmission.service.ts и approverDecision.service.ts.
log.info('Создана заявка', { 'request.type': formId, 'request.id': id, 'request.code': code });
return { id, code }; return { id, code };
} }