Чат заявки: отметки «@» и оповещения через поток (#26)
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
This commit was merged in pull request #26.
This commit is contained in:
2026-10-14 10:05:45 +00:00
parent 96f53da020
commit 5d2abaf430
27 changed files with 754 additions and 46 deletions
@@ -0,0 +1,70 @@
import {
buildParticipants,
extractMentionedEmails,
filterParticipants,
findMentionQuery,
insertMention
} from './chatMentions';
const ivanov = { name: 'Иван Иванов', email: 'ivanov@tnsu.kz' };
const petrova = { name: 'Анна Петрова', email: 'petrova@tnsu.kz' };
describe('buildParticipants', () => {
it('убирает повторы, пустые адреса и самого пишущего', () => {
const result = buildParticipants(
[ivanov, { name: 'Иван Иванов', email: 'IVANOV@tnsu.kz' }, { name: 'Без почты', email: '' }, undefined, petrova],
'petrova@tnsu.kz'
);
expect(result).toEqual([ivanov]);
});
it('без ФИО показывает почту', () => {
expect(buildParticipants([{ email: 'x@tnsu.kz' }], '')).toEqual([{ name: 'x@tnsu.kz', email: 'x@tnsu.kz' }]);
});
});
describe('findMentionQuery', () => {
it('находит отметку в начале и после пробела', () => {
expect(findMentionQuery('@Ив', 3)).toEqual({ start: 0, query: 'Ив' });
expect(findMentionQuery('Привет @Иван Ив', 15)).toEqual({ start: 7, query: 'Иван Ив' });
});
it('не считает отметкой адрес почты и перенос строки', () => {
expect(findMentionQuery('mail@tnsu', 9)).toBeUndefined();
expect(findMentionQuery('@Иван\nдалее', 11)).toBeUndefined();
expect(findMentionQuery('без отметки', 5)).toBeUndefined();
});
});
describe('filterParticipants', () => {
it('ищет по началу слов ФИО и по почте', () => {
expect(filterParticipants([ivanov, petrova], 'пет')).toEqual([petrova]);
expect(filterParticipants([ivanov, petrova], 'иван ив')).toEqual([ivanov]);
expect(filterParticipants([ivanov, petrova], 'petrova@')).toEqual([petrova]);
expect(filterParticipants([ivanov, petrova], '')).toEqual([ivanov, petrova]);
});
});
describe('insertMention', () => {
it('заменяет набранное на ФИО и ставит курсор за пробелом', () => {
const text = 'Привет @Ив, глянь';
const result = insertMention(text, { start: 7, query: 'Ив' }, 10, ivanov);
expect(result.text).toBe('Привет @Иван Иванов , глянь');
expect(result.caret).toBe('Привет @Иван Иванов '.length);
});
});
describe('extractMentionedEmails', () => {
it('берёт отметки из текста без повторов', () => {
expect(extractMentionedEmails('@Иван Иванов и @Анна Петрова, @Иван Иванов', [ivanov, petrova])).toEqual([
'ivanov@tnsu.kz',
'petrova@tnsu.kz'
]);
});
it('стёртая отметка не считается, короткое ФИО не цепляется за длинное', () => {
const ivan = { name: 'Иван', email: 'ivan@tnsu.kz' };
expect(extractMentionedEmails('@Иван Иванов, привет', [ivan, ivanov])).toEqual(['ivanov@tnsu.kz']);
expect(extractMentionedEmails('Иван Иванов, привет', [ivanov])).toEqual([]);
});
});