import { AadHttpClient } from '@microsoft/sp-http'; import { WebPartContext } from '@microsoft/sp-webpart-base'; import { FLOW_RESOURCE, FLOW_NOTIFY_URL, getNotificationsEnabled, getNotifyOnlyTo } from '../config'; import { ApprovalListService } from './approvalList.service'; import { REQUEST_STATUS } from './models/statuses'; // Данные формы для тела запроса к потоку (постоянны для формы). export interface INotifyTarget { requestTypeId: number; // RequestTypeId (id типа заявки во внешнем API маршрутов) dataListName: string; // ListName (список данных заявки) approvalListName: string; // ApproveListName (список согласования) } // POST в поток оповещения (Entra-триггер) через AadHttpClient — обычный fetch → 401, // AadHttpClient даёт токен Flow Service (и снимает вопрос CORS). async function postNotify(context: WebPartContext, body: Record): Promise { if (!FLOW_NOTIFY_URL) { return; } const client = await context.aadHttpClientFactory.getClient(FLOW_RESOURCE); const res = await client.post(FLOW_NOTIFY_URL, AadHttpClient.configurations.v1, { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); if (!res.ok) { throw new Error(`Поток оповещения: ${res.status} ${res.statusText}`); } } // Разрешена ли отправка на согласующего активной строки — по свойствам веб-части // notificationsEnabled / notifyOnlyTo (см. setNotificationSettings в config.ts). // // Ограничение стоит ЗДЕСЬ, на стороне портала, а не в потоке Power Automate, // потому что адресата поток определяет САМ по RowID — тело запроса на выбор // получателя не влияет. Значит, единственный способ не отправить письмо, не // трогая поток, — не дёргать его. // // При заданном notifyOnlyTo неизвестный адрес (пользователь не найден, поле // пустое, запрос упал) — НЕ отправляем: сомнение трактуется в пользу «письмо // живому человеку лучше не слать». async function isNotifyAllowed(approval: ApprovalListService, approverId: number): Promise { if (!getNotificationsEnabled()) return false; const onlyTo = getNotifyOnlyTo(); if (!onlyTo) return true; const email = await approval.resolveUserEmail(approverId); if (email.toLowerCase() === onlyTo.toLowerCase()) return true; console.warn( `[Оповещение] Рассылка ограничена одним адресом: письмо для «${email || 'адрес неизвестен'}» не отправлено ` + `(разрешён только ${onlyTo})` ); return false; } // Находит АКТИВНЫЙ шаг маршрута (На согласовании / На исполнении) и уведомляет его. // Вызывать после отправки заявки и после каждого положительного решения. Поток по // переданным полям сам находит согласующего/исполнителя и шлёт уведомление. // Уведомление некритично — ошибки глотаем, чтобы не ронять основной сценарий. // // rowId — уведомить именно эту активную строку, а не первую попавшуюся: при // замене/удалении исполнителя на параллельном шаге активных строк несколько, и // письмо должно уйти новому человеку, а не тому, кто уже его получил. export async function notifyActiveStep( context: WebPartContext, approval: ApprovalListService, requestId: number, target: INotifyTarget, rowId?: number ): Promise { try { const rows = await approval.getForRequest(requestId); const active = rows.filter( (r) => (r.status === REQUEST_STATUS.onApproval || r.status === REQUEST_STATUS.onExecution) && (rowId === undefined || r.id === rowId) )[0]; if (!active) { return; } if (!(await isNotifyAllowed(approval, active.approverId))) { return; } await postNotify(context, { RequestTypeId: target.requestTypeId, RowID: active.id, RequestID: requestId, ListName: target.dataListName, ApproveListName: target.approvalListName, }); } catch { // не критично — просто не отправили уведомление } }