SharePoint хранит поле «только дата» как полночь по поясу сайта (UTC+5) в UTC: «05.10» приходит из REST как 2026-10-04T19:00:00Z. Формы брали первые 10 символов и показывали 04.10, а каждое пересохранение (полное право, исполнитель, повторная отправка) записывало дату ещё на день раньше. Чтение всех дат идёт через spDateOnlyToIso (округление до ближайшей полуночи UTC), «сегодня» — через todayIsoDate вместо toISOString, который до 05:00 давал вчерашнее число. Уже сдвинутые записи в SP не исправляются. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Обходной лист (clearance-form)
Назначение: оформление увольнения — обход подразделений (склад, СКУД, обучение) с отметками и согласованием.
Категория: Отдел кадров
Списки SP: «Обходной лист — данные» / «Обходной лист — согласование»
RequestTypeId (внешний API маршрутов): не хардкодится — резолвится в рантайме по
pageUrl («clearance-form») через useRequestTypeId (см. «Формы больше не хардкодят
RequestTypeId» в docs/forms.md). Тип уже зарегистрирован в админке approval-route
(список данных «Обходной лист — данные», список SharePoint /sites/requests/Lists/List10).
Маршрут
- Фазы: только согласование. Фазы исполнения нет — после согласования заявка просто получает статус «Согласовано» (как у «Заявки на отпуск»).
- Матрица редактируема заявителем при новой заявке / черновике / возврате:
useMatrixEditor(editable = !readOnly). Заявитель (HR) может добавлять/удалять/ переставлять согласующих поверх планируемого маршрута. - Условие
IsManagerв маршруте (если настроено в админке) резолвится по полю Position ЭТОЙ ЗАЯВКИ — «должность» увольняемого сотрудника, а не должность того, кто заполняет форму. ПоэтомуPositionпередаётся вfieldValuesпри планировании маршрута (getPlannedMatrix(..., [{ fieldName: 'Position', ... }])). Если поле пустое, шаг с этим условием попадёт в маршрут с предупреждением «должность не передана» (см. approval-routeTemplateRouteService.ResolveAsync).
Особое поведение
- Заявку подаёт не сам увольняемый, а HR/руководитель — за другого сотрудника.
Поле «Сотрудник» — People-picker (колонка User
Employee), содержит e-mail увольняемого. Планируемый маршрут резолвится по этому e-mail (getPlannedMatrix(..., data.Employee, ...)), а не по e-mail текущего пользователя — как в «Заявке на отпуск». - Автоподстановка должности при выборе сотрудника — только отправная точка:
подтягивается текущая должность из профиля (
SPS-JobTitleчерезapproval.getJobTitle), но её можно поправить вручную (например, если человек занимал руководящую позицию до перевода на другую). - Да/нет-поля («Парковочная карта», «Карта СКУД») —
Toggle, колонкиParkingCard/AccessCard(Boolean). - Сумма обучения —
MoneyInput(разделитель тысяч в UI, в списке — «сырое» число), колонкаTrainingAmount(Number, 2 знака), поле необязательное. - Дата расторжения договора — обычный
input type=date, без вычислений (в отличие отEndDateв «Заявке на отпуск», которая считается). - Заголовок элемента (
Title):Обходной лист — {ФИО} — дд.мм.гггг.
Связь с заявлением на увольнение
Обходной лист может появиться сам, без этой формы: когда согласовано «Заявление
на увольнение» (hr-dismissal), портал создаёт лист по нему и сразу отправляет на
согласование — см. forms/Dismissal/createClearance.ts и README той формы. Такой
лист приходит уже с заполненными сотрудником, должностью, подразделением и датой
расторжения; отметки (парковка, СКУД, обучение) по-прежнему проставляют профильные
согласующие. Подача этой формы руками остаётся — для увольнений, оформленных без
заявления в портале.
Пара связана колонками-гиперссылками: DismissalUrl здесь → на заявление,
ClearanceUrl в заявлении → на лист. Форма их не пишет (заполняет автосоздание либо
вручную в SharePoint), но показывает: поле «Заявление на увольнение» —
LinkField из shared/ui, read-only, открывается в той же вкладке; у листов,
заведённых руками, ссылки нет и поле не рендерится.
Суммы к списанию (согласование)
Отдельная CollapseSection title="Суммы к списанию" (в стиле бывшего общего
заголовка формы «Заявка», удалённого — см. docs/forms.md) объединяет
визуально ДВЕ РАЗНЫЕ по правам вещи:
TrainingAmount(«Обучение») — доступность КАК И РАНЬШЕ: правит только КДП (hrRecordsEditable,approverRights.ts), сумма НЕ блокируется после ввода, никак не участвует в WRITE_OFF_CATEGORIES/writeOffTouchedCategories(не требует комментария решения). Изменилась ТОЛЬКО колонка хранения — пишется и читается черезWriteOffTrainingAmount(ClearanceView.saveApproverFields,submit.ts.buildItem,load.ts), а не через одноимённуюTrainingAmount— та осталась в схеме списка неиспользуемой. Секция видна независимо отwriteOffEditableKeys(само поле — без этого условия), поэтому на форме подачи (ClearanceForm,writeOffEditableKeysне передан) поле видно и без пяти сумм ниже.- 5 НЕЗАВИСИМЫХ сумм, компактной сеткой (короткие подписи, обычная
2-колоночная сетка секции), колонки
WriteOff{Assets,Ppe,Accountable,Nma, Tmc}Amount(см.model.ts WRITE_OFF_CATEGORIES): «ОС», «СИЗ», «Подотчётная сумма», «НМА», «ТМЦ» — правит любой активный согласующий, см. ниже. Раньше среди них было ещё и «Обучение» — убрали как дубльTrainingAmountс ДРУГИМ поведением под тем же названием; общий заголовок группы остался.
- Не привязаны к должности, в отличие от отметок выше: заполнить может ЛЮБОЙ
согласующий, у которого сейчас активный шаг (
ClearanceView.canEditWriteOffs, фаза «Согласование» — исполнителям недоступно, как и остальным отметкам). - Каждая сумма блокируется НАВСЕГДА после первого ввода — как только она
непустая, поле становится read-only для всех, включая того, кто её ввёл.
Признак блокировки — сама непустая сумма на МОМЕНТ ЗАГРУЗКИ страницы
(
ClearanceView.lockedWriteOffs, снимок при загрузке, а не реактивно от текущегоdata— иначе поле блокировалось бы прямо во время набора цифр). - «За что конкретно» — своего поля НЕТ. Заполнил хоть одну сумму
(
ClearanceView.writeOffTouchedCategories) — становится обязательным СТАНДАРТНЫЙ комментарий решения: то самое поле «Комментарий» в блоке «Ваше решение» (DecisionPanel), которым и так сопровождают Согласовать/Вернуть/ Отклонить — обычно обязательное только при возврате/отклонении, но не здесь (commentRequiredуDecisionPanel, см. её же новый проп). Плейсхолдер в этот момент называет категории, которые нужно объяснить («Введите, за что сумма: ОС, ТМЦ», см.commentPlaceholder). Комментарий уходит в СТРОКУ маршрута этого согласующего (колонкаCommentсписка согласования) — как обычно, отдельного поля под него на данных заявки не заводили. - Суммы сохраняются кнопкой «Сохранить» (
reviewBar) и тем жеonBeforeDecide— при решении несохранённые суммы уйдут вместе с ним, ДО проверки обязательности комментария (см.DecisionPanel.decide). - Это ОТДЕЛЬНЫЙ механизм от
TrainingAmount(«Обучение» выше, привязана к КДП) — сознательно не объединялись, чтобы не менять уже установившееся поведение существующего поля.
Секции формы
RequestSection (сотрудник · должность · подразделение · дата расторжения ·
парковочная карта · карта СКУД — без заголовка, FormGrid) + отдельная
CollapseSection «Суммы к списанию» (обучение + 5 сумм списания) + ссылка на
заявление
Точки входа
- rptype:
clearance-form - Режим согласующего (просмотр): есть (
ClearanceView) - «Создать на основе»: нет