5d2abaf430
Tests / frontend-test (push) Successful in 2m1s
## Что сделано **Чат заявки: отметки и оповещения** - В поле ввода «@» открывает подсказку с участниками заявки: инициатор, согласующие и исполнители из маршрута, а также те, кому открыты «Все заявки» этого типа (список из request-type-access). - Отметка — обычный текст «@ФИО», без штатного упоминания SharePoint, чтобы не уходило его письмо. - После отправки сообщения портал вызывает поток Power Automate (`FLOW_CHAT_NOTIFY_URL`): отмеченным — «Вас упомянули», инициатору — «Новое сообщение по вашей заявке». Сбой потока не мешает отправке. - В письмо добавляется строка «Прикреплены файлы: …», чтобы сообщение из одного файла не приходило пустым. - Свойство веб-части «Чат: уведомлять о своих сообщениях (для теста)», по умолчанию выключено: письма и о своих сообщениях, можно отметить себя. - Когда пишет сам инициатор с отметками, в `initiatorEmail` уходит первый отмеченный — поток шлёт инициатору всегда, если его нет среди отмеченных, и иначе он получил бы письмо о своём же сообщении. **Чат включён для:** Обходной лист, заявка на подбор, заявка на приём, заявка в отдел 1С. **Исправление:** редактор маршрута может удалять исполнителей в Обходном листе, Командировке и заявке в 1С (не передавалось `canRemoveRow`). **Прочее:** убраны 4 предупреждения линтера (`no-new-null`, `no-script-url`). ## Не проверено на тенанте - Ответ потока 200 через AadHttpClient и доставка писем. - Права согласующих на комментарии и на запись в библиотеку «Файлы чата». - Папки в «Файлы чата» наследуют права библиотеки, а не закрыты на заявку — для подбора и приёма это важно решить до выкладки. - Если поток вставляет `messageText` в HTML-письмо, перенос строки перед «Прикреплены файлы» может не отобразиться. --------- Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz> Reviewed-on: http://git.tnsu.kz/timur.gorodetskiy/request-portal/pulls/26
112 lines
5.8 KiB
TypeScript
112 lines
5.8 KiB
TypeScript
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<void> {
|
|
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<boolean> {
|
|
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<void> {
|
|
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<void> {
|
|
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))
|
|
);
|
|
}
|