Files
request-portal/src/webparts/requestPortal/forms/BusinessTrip
gorbenko.pavel 5d2abaf430
Tests / frontend-test (push) Successful in 2m1s
Чат заявки: отметки «@» и оповещения через поток (#26)
## Что сделано

**Чат заявки: отметки и оповещения**
- В поле ввода «@» открывает подсказку с участниками заявки: инициатор, согласующие и исполнители из маршрута, а также те, кому открыты «Все заявки» этого типа (список из 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
2026-10-14 10:05:45 +00:00
..

Заявка на командировку (hr-business-trip)

Назначение: заявка о направлении сотрудника в служебную командировку — куда, когда, с каким заданием и сметой расходов. Категория: Отдел кадров (плитка «Заявка на командировку», пока comingSoon — только тестировщикам) Списки SP: «Заявка на командировку — данные» / «Заявка на командировку — согласование» RequestTypeId (внешний API маршрутов): плейсхолдер — получит числовой id при заведении типа в админке approval-route с pageUrl «hr-business-trip».

Маршрут

  • Маршрут настраивается в админке approval-route другим человеком — форма его только получает.
  • Фазы: согласование + исполнение, обе грузятся общим usePhasedRoute (shared/approval). Нет шагов исполнения — после согласования статус «Согласовано»; нет согласующих — initialSubmitStatus сразу ставит «Выполнение».
  • Планируемый маршрут резолвится по e-mail выбранного сотрудника; согласующих заявитель может править (новая / черновик / доработка), исполнителей — нет.

Особое поведение

  • Можно подать за другого сотрудника. По умолчанию — сам инициатор; при выборе другого подтягиваются его должность и организации (Employee/ByEmail, useTripEmployee).
  • Маршрут — лента точек (components/route, логика — route.ts; гибрид «маршрут + смета» по образцу Concur/Navan/TripIt). Город отправления → точки (город + объект из /Project + даты прибытия/отъезда) → (возвращение — переключатель). Простая поездка — одна точка; «+ Добавить точку маршрута» — для поездок по нескольким объектам.
    • Участки пути не хранятся, а выводятся из точек (routeLegs, ключ откуда>куда); у каждого участка — вид транспорта плашками (legTransport, TRANSPORT_TYPES), обязателен.
    • Объект точки — только выбор из справочника (projectId): по объектам разносят затраты.
    • Два вида маршрута — переключатель «Форма | Схема» (RouteViewSwitch). Схема (RouteScheme) — только просмотр, как схема метро: узлы-точки (город, объект, даты, ночи, расходы по точке из сметы — stopTotals), на линиях — вид транспорта, внизу сводка «дни · объекты · переезды · итог». Клик по точке — форма с прокруткой к ней. Заявитель начинает с формы, согласующий (режим чтения) — со схемы. Широкий экран — слева направо, узкий — сверху вниз. Сумму под точками Тимур может попросить убрать, если будет громоздко.
    • Новая точка начинается с даты отъезда из предыдущей (то, что человек уже ввёл); прибытие раньше отъезда из предыдущей точки — ошибка.
    • Хранение: TripRouteJson; для отчётов из маршрута пишутся TripRoute (текстом), TripCity (города), Project/ProjectId (объект ПЕРВОЙ точки — колонка у всех форм одна), TripProjects (все объекты), TripStartDate/TripEndDate/TripDaysCount (весь период).
  • Задание — нумерованный список задач, хранится в TripTasksJson (+ читаемая копия TripTasks для представлений SP). Пустые строки не сохраняются.
  • Смета (components/estimate, логика — estimate.ts). Всё вводится вручную, кроме суточных:
    • Суточные считаются сами (с 10-09, по просьбе кадров): по позиции на каждую точку маршрута (syncPerDiem, itemsFromRoute в ESTIMATE_KINDS), город и проект — из точки, дни — из её дат включительно, но день переезда считается один раз (stopPerDiemDays). Человек вводит только кол-во МРП в день; ставка = кол-во × MRP_VALUE (захардкожен в model.ts вместе с MRP_YEAR — обновлять раз в год). Ставка сохраняется в заявке (price), поэтому просмотр старой заявки не пересчитывается по новому МРП — syncPerDiem применяется только в форме.
    • Статьи: основные Суточные / Проезд / Проживание (переключатель «Требуется / Не требуется», выключенная печатается «не требуется»), Страховка и Виза (по одной) и «Другой расход» со своим названием (сколько угодно) — кнопками «+», удаляются корзиной.
    • Внутри статьи — позиции («+ Ещё гостиница», «+ Ещё участок пути»; у суточных позиции только из маршрута). Позиция привязана к точке маршрута (суточные, проживание, другой расход) или к участку пути (проезд); страховка/виза — без привязки. Вариант один — выбор скрыт и подставляется сам (resolveLinkId); точку удалили — позицию нужно привязать заново.
    • Сумма позиции — «Суммой» или «Кол-во × цена» (только перемножение введённого), подписи над полями. Пробовали таблицу «в строку» и смету «по точкам маршрута» — Тимур вернул карточки (10-05): «самые понятные».
    • Итог на лету: полоса долей по видам статей (палитра проверена dataviz-валидатором) и, если объектов несколько, итог по объектам (projectShares; участок — к объекту пункта назначения, обратный — к объекту, откуда уезжают; страховка/виза — «без объекта»).
    • Смета сама не обязательна, но у включённой статьи каждая позиция — с суммой и привязкой, у «Другого расхода» — название, у суточных — кол-во МРП.
    • Хранение: TripEstimateJson (статьи + total), читаемо — TripEstimate (позиция на строку), итог числом — TripEstimateTotal. Просмотр/печать — таблица по статьям.
  • Обязательство по авансовому отчёту (текст бланка) — флажок, без него не отправить.
  • Дата подачи (SubmitDate, общий столбец сайта) — ставится сегодняшней, пока заявку можно править (см. shared/submitDate.ts).
  • Подсветка незаполненных полей — FieldIssuesProvider + правила в validation.ts. Черновику нужен только сотрудник.
  • Печать — данные заявки и маршрут согласования/исполнения, без шапки с адресатом: бумажный бланк не повторяется (решение Тимура).
  • Заголовок элемента (Title): Заявка на командировку — {ФИО} — {города}, {дата начала}.
  • Новые колонки — с префиксом Trip*: StartDate в других формах уже занят с другим заголовком (см. грабли коллизий internalName).

Секции формы

EmployeeSection (сотрудник · должность · дата подачи · организация) → RouteSection (маршрут лентой + цель) → TasksSection (задачи) → EstimateSection (смета + итог) → ObligationSection (текст обязательства + согласие).

Точки входа

  • rptype: hr-business-trip
  • Режим согласующего (просмотр): есть (BusinessTripView)
  • «Создать на основе»: нет

Не сделано

  • Авансовый отчёт после командировки — отдельной формой, пока не придумано как.