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 { IApprovalRow } from './models/IApprovalRow'; import { pickRowsToNotify } from './notifyTargets'; // Данные формы для тела запроса к потоку (постоянны для формы). export interface INotifyTarget { requestTypeId: number; // RequestTypeId (id типа заявки во внешнем API маршрутов) dataListName: string; // ListName (список данных заявки) approvalListName: string; // ApproveListName (список согласования) } // POST в поток Power Automate (Entra-триггер) через AadHttpClient — обычный fetch → 401, // AadHttpClient даёт токен Flow Service (и снимает вопрос CORS). Общий для всех // потоков оповещения портала (согласование, чат заявки — shared/chat/chatNotify.service.ts). export async function postToFlow(context: WebPartContext, flowUrl: string, body: object): Promise { if (!flowUrl) { return; } const client = await context.aadHttpClientFactory.getClient(FLOW_RESOURCE); const res = await client.post(flowUrl, 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; } // Одна строка маршрута → один вызов потока. Ошибку глотаем здесь, по строке: // сбой письма одному участнику параллельного шага не должен лишать письма остальных. async function notifyRow( context: WebPartContext, approval: ApprovalListService, requestId: number, target: INotifyTarget, row: IApprovalRow ): Promise { try { if (!(await isNotifyAllowed(approval, row.approverId))) { return; } await postToFlow(context, FLOW_NOTIFY_URL, { RequestTypeId: target.requestTypeId, RowID: row.id, RequestID: requestId, ListName: target.dataListName, ApproveListName: target.approvalListName, }); } catch { // не критично — просто не отправили уведомление } } // Находит АКТИВНЫЙ шаг маршрута (На согласовании / На исполнении) и уведомляет его. // Вызывать после отправки заявки и после каждого положительного решения. Поток по // переданным полям сам находит согласующего/исполнителя и шлёт уведомление. // Уведомление некритично — ошибки глотаем, чтобы не ронять основной сценарий. // // Параллельный шаг (несколько активных строк с одним StepOrder) оповещается // целиком — каждому участнику свой вызов потока (см. pickRowsToNotify). // // rowId — уведомить именно эту активную строку, а не весь шаг: при // замене/удалении исполнителя на параллельном шаге активных строк несколько, и // письмо должно уйти новому человеку, а не тому, кто уже его получил. export async function notifyActiveStep( context: WebPartContext, approval: ApprovalListService, requestId: number, target: INotifyTarget, rowId?: number ): Promise { let rows: IApprovalRow[]; try { rows = await approval.getForRequest(requestId); } catch { return; // не критично — просто не отправили уведомление } await Promise.all( pickRowsToNotify(rows, rowId).map((row) => notifyRow(context, approval, requestId, target, row)) ); }