Заявление на увольнение #15

Merged
gorbenko.pavel merged 15 commits from fired-request into main 2026-08-26 07:07:03 +00:00
Collaborator
No description provided.
gorbenko.pavel added 4 commits 2026-08-25 08:25:28 +00:00
Новая форма отдела кадров по бумажному шаблону «Заявление на увольнение ШТАТ»:
сотрудник, должность, структурное и самостоятельное подразделение, последний
рабочий день, контактный телефон, комментарий. По умолчанию заявка на себя, но
можно подать за другого — маршрут резолвится по выбранному сотруднику.

Подразделение подтягивается из профиля (SPS-Department, read-only), должность и
организации — из справочника Employee/ByEmail. Последний рабочий день не может
быть в прошлом; срок предупреждения об увольнении форма не проверяет — дату
согласуют с работодателем.

Перекрёстные ссылки с «Обходным листом»: колонки-гиперссылки ClearanceUrl /
DismissalUrl (только в списках, формы их пока не пишут). Для них в провижинере
появился тип колонки Url, а сам скрипт разбит на два прохода — сначала все
списки, потом колонки: иначе Lookup на список другой формы держится на алфавитном
порядке обхода манифестов.

Списки «Заявление на увольнение 1 — данные» / «… — согласование» созданы на
/sites/requests. Тип заявки в админке approval-route ещё не заведён — до этого
форма откроется, но отправить заявку не даст.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Как только маршрут заявления пройден целиком (статус «Согласовано»), портал
заводит обходной лист по нему: форма не открывается — маршрут резолвится через
approval-route (обе фазы, по Employee и Position заявления), элемент пишется в
текущую партицию списка «Обходной лист … — данные» и сразу уходит на согласование
с оповещением активного шага.

Поля выводятся из заявления (сотрудник, должность, подразделение, организация,
последний рабочий день → дата расторжения договора); отметки парковки/СКУД/обучения
остаются профильным согласующим. Пара связывается перекрёстными гиперссылками
ClearanceUrl / DismissalUrl — для них добавлен requestLinkUrl: абсолютная ссылка на
заявку без посторонних параметров страницы, в отличие от routeUrl.

Повторное создание отсекается непустой ClearanceUrl: заявление можно согласовать
второй раз после возврата на доработку, и без этой проверки на каждый цикл
появлялся бы новый обходной лист. Создание идёт в браузере последнего согласующего —
если оно упало, согласование не отменяется, а портал показывает предупреждение
«заведите вручную».

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Поле-ссылка LinkField (shared/ui): read-only «карточка» как у OrganizationField,
но кликабельная, открывается в новой вкладке (портал — одна страница, переход в
текущей означал бы перезагрузку с потерей открытого). Пустой адрес — поле не
рендерится: связь есть не у всех заявок.

В заявлении показывается «Обходной лист» (ClearanceUrl), в обходном листе —
«Заявление на увольнение» (DismissalUrl); обе колонки читаются в load.ts (адрес из
объекта {Url, Description}) и не пишутся формами. Согласующему, который только что
одобрил заявление, ссылка дописывается в модель сразу после автосоздания листа —
иначе появилась бы только после перезагрузки.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Merge remote-tracking branch 'origin/main' into fired-request
Tests / frontend-test (pull_request) Successful in 1m22s
cda68f86b0
gorbenko.pavel added 2 commits 2026-08-25 08:46:38 +00:00
Было target="_blank" — на проде это читалось как «портал зачем-то плодит окна».
Ссылка ведёт на ту же страницу портала с другими rp-параметрами, так что обычная
навигация даёт нормальный переход с работающей кнопкой «назад».

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
navigateToRequestLink (shared/routing): если абсолютная ссылка ведёт на ЭТУ ЖЕ
страницу портала и на просмотр заявки — открываем её через navigate(), то есть
сменой rp-параметров, а не перезагрузкой SPFx-страницы (она грузится секунды, а
показать нужно ту же страницу с другими параметрами). Раздел бокового меню берётся
текущий: заявка показывается в панели поверх раздела, и переход по ссылке не должен
переключать раздел под ней.

LinkField перехватывает только «обычный» клик: с Ctrl/⌘/Shift/Alt и не левой
кнопкой отдаёт браузеру, href настоящий — ссылку по-прежнему можно скопировать или
открыть в отдельной вкладке. Чужой сайт, другая страница и ссылка не на заявку
возвращают false и обрабатываются браузером как раньше.

Тесты на requestLinkUrl/navigateToRequestLink в routing.test.ts; heft test — 179
успешных, 0 падений. В docs/routing.md добавлены hr-dismissal и clearance-form.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gorbenko.pavel added 5 commits 2026-08-26 05:33:14 +00:00
1) DecisionPanel ждёт onDecided, если тот вернул Promise: «Решение сохранено.
Статус заявки: «Согласовано»» появляется только после побочных действий решения —
для заявления на увольнение это созданный обходной лист. Пока идёт создание,
кнопки остаются заблокированными, а на месте результата — «Создаём обходной
лист…». Провал побочного действия статус решения не отменяет (оно уже применено на
сервере) — о проблеме сообщает сам обработчик своим предупреждением.

2) «Вы ещё не создавали заявок» больше не мигает на недогруженном списке.
«Мои заявки» и «Доработка» собираются из двух источников (Search + REST-довесок по
свежести), и onPartial показывал тот, что ответил первым, даже если он пустой — на
экране на мгновение появлялась пустота вместо «Загрузка…». Теперь пустой
промежуточный результат не показывается вовсе, а вывод «заявок нет» делается только
по ЗАВЕРШЁННОЙ загрузке именно этой таблицы (loadedKey), а не по любому пустому rows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Четыре независимых доработки поверх связки «Заявление на увольнение» ↔
«Обходной лист»:

1. Совместители (несколько компаний у одного сотрудника): approval-route уже
   резолвит директора/должностного эквивалента КАЖДОЙ компании отдельным шагом
   маршрута (HeadType.CompanyDirectors, шаблон route 17: Chief → CompanyDirectors).
   Organization на форме подачи заявления теперь интерактивная таблица
   (OrganizationsTable) вместо read-only списка — строку компании можно убрать,
   если сотрудник реально не увольняется отовсюду сразу; убранная компания сразу
   снимается и из уже спланированной матрицы (matrix.remove) — искать нужную
   строку вручную не надо. Новый shared/dictionary/employeeLookup.service.ts
   зеркалит DirectorPositionByCompany бэкенда (KazpromService → Главный механик,
   СТ Global → Генеральный директор, иначе Директор) и Employee/Manager
   (managerType=chief) — используется и здесь, и в следующем пункте.

2. Печать заявления заблокирована, пока парный обходной лист не согласован
   (статус читается напрямую по ClearanceUrl, без отдельного поля-дублёра).

3. «Лицо, принимающее ОС/НМА» (AssetsAcceptor, People) — обязательное поле,
   которое заполняет НЕ заявитель, а непосредственный руководитель увольняемого
   (identity-check по Employee/Manager, а не по должности, как в approverRights
   «Обходного листа») прямо перед своим решением (DecisionPanel.onBeforeDecide);
   пустое поле блокирует решение.

4. В обходном листе — 4 независимые пары «сумма к списанию + обязательный
   комментарий» (ОС/СИЗ/подотчёт, обучение, НМА, ТМЦ), отдельно от уже
   существующего TrainingAmount. Заполнить может любой активный согласующий
   (не только профильный), но как только сумма введена — пара блокируется
   навсегда для всех (лок по снимку на момент загрузки, не реактивно от текущего
   ввода — иначе поле блокировалось бы прямо во время набора цифр).

Списки на /sites/requests обновлены: AssetsAcceptor (Заявление на увольнение),
8 колонок WriteOff* (Обходной лист). tsc --noEmit и heft test (build+lint+179
тестов) чистые.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Было одной парой «ОС/СИЗ/подотчётная сумма» — стало тремя независимыми, всего
6 категорий (ОС, СИЗ, подотчётная сумма, обучение, НМА, ТМЦ). Списывают их
разные подразделения и за разное, поэтому и сумма с комментарием у каждой своя,
и блокируется каждая отдельно (правило «введено — больше не меняется» работает
на пару, а не на весь блок).

Существующая колонка WriteOffAssetsAmount/Comment переиспользована под «ОС»
(данных по ней ещё нет), добавлены WriteOffPpe* и WriteOffAccountable*.
Список типов полей теперь выводится из WriteOffFieldKey — IWriteOffFields в
submit.ts больше не дублирует их перечислением.

На тенанте: 4 новые колонки созданы провижинером; отображаемые имена ОС-пары
досинхронизированы отдельно — Provision-Lists.ps1 колонки только СОЗДАЁТ и
переименование в манифесте существующей колонке не применяет.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Заявление на увольнение:

1. Последний рабочий день — минимум через MIN_NOTICE_MONTHS (1) КАЛЕНДАРНЫЙ
   месяц от сегодня (новый isoDatePlusMonths в shared/dateUtils.ts, а не
   приближение через 30 дней — setMonth сам разносит месяцы разной длины).

2. Департамент (Section из Employee/ByEmail) — колонка в OrganizationsTable
   вместо отдельного профильного поля: источник сменился со SharePoint-профиля
   (SPS-Department) на тот же справочник api.tnsu.kz, что уже даёт компанию и
   должность. Заодно убраны отдельные «Должность»/«Структурное подразделение»
   над таблицей — дублировали то, что таблица и так показывает. Редактируется
   по-прежнему только должность ПЕРВОЙ (основной) компании — она и есть
   data.Position, читает её маршрут; должности остальных компаний и
   департамент — информационные. Пустой справочник (сотрудник не найден) —
   таблица не исчезает, а показывает одну строку с прочерками и редактируемой
   должностью, иначе заявление на такого человека нельзя было бы подать.
   Режим просмотра (DismissalView) не тронут — там по-прежнему простые
   read-only поля, без live-запроса к справочнику.

На тенанте: Title колонки Department синхронизирован под «Департамент» —
ТОЛЬКО на списке «Заявление на увольнение», списочным override, а не через
общий столбец сайта. Через -UpdateExistingLists один раз случайно затёрло
Department и на «Обходном листе», и на «Заявке в 1С» (internalName у них общий,
Title разный) — заметил и починил в этой же сессии; см. memory
provisioning-shared-site-columns для деталей на будущее.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Position, Department, Organization (Заявление на увольнение) теперь хранят
JSON-массив строк каждый — компания/департамент/должность по каждой компании
сотрудника, один и тот же индекс во всех трёх столбцах = одна запись
(model.ts: IOrganizationRecord, zipOrganization/parseOrganizationColumn на
чтение, submit.ts: organizationColumn на запись). Раньше Position/Department
хранили только ПЕРВУЮ компанию, а Organization — плоский текст без
департамента; полную таблицу теперь можно восстановить и показать где угодно.

Модель данных формы существенно упростилась: вместо трёх независимых
data.Position/Department/Organization + отдельного live-состояния orgRecords —
один data.Organization: IOrganizationRecord[], primaryPosition() читает
должность основной компании для маршрута и валидации. Загрузка на
редактирование больше не ходит в Employee/ByEmail за живыми организациями —
таблица целиком восстанавливается из уже сохранённой заявки.

OrganizationsTable — теперь ЕДИНСТВЕННЫЙ рендерер таблицы и на форме подачи
(редактируемая, editable-проп передан), и в режиме просмотра (read-only, проп
не передан) — отдельных read-only полей для просмотра больше нет.

createClearance.ts обновлён под новый формат (иначе автосоздание обходного
листа читало бы Position/Department как сырой JSON-текст).

Легаси: значения, сохранённые ДО перехода на JSON (не валидный JSON), не роняют
чтение — трактуются как одна компания (parseOrganizationColumn).

Риск: Position/Department — ОБЩИЙ столбец сайта на 8 форм портала, тип Text
(лимит SharePoint 255 символов) — менять тип нельзя, задело бы все 8. Явная
ошибка при сохранении, если JSON не укладывается (TEXT_COLUMN_LIMIT в
submit.ts), вместо тихого обрезания данных.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gorbenko.pavel added 4 commits 2026-08-26 07:05:59 +00:00
Три правки в OrganizationsTable по месту (без изменений схемы/формата хранения
— поле по-прежнему называется department, источник тот же Section):

1. Заголовок колонки «Департамент» → «Структурное подразделение».
2. Должность редактируема В КАЖДОЙ строке, не только у первой (основной)
   компании — справочник не всегда точен, поправить нужно любую. На маршрут и
   валидацию по-прежнему влияет только должность ПЕРВОЙ записи
   (primaryPosition) — остальные редактируются, но ни на что не влияют.
   onChangePosition получил индекс строки вместо неявного «всегда нулевая».
3. Убрана обёртка FormField с подписью «Организация · подразделение ·
   должность *» над таблицей — избыточна, у таблицы уже есть заголовки
   колонок. full-ширина (grid-column: 1/-1) взята напрямую в CSS, без FormField
   (у него label обязателен).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Поле почти всегда пустое, пока непосредственный руководитель его не заполнил
(см. assetsAcceptorEditable в DismissalView), и до этого момента плейсхолдер
«Начните вводить Ф.И.О. сотрудника…» в заблокированном поле выглядел так,
будто в него можно печатать. Плейсхолдер теперь только когда поле реально
редактируемо.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Сравнивал непосредственного руководителя с context.pageContext.user.email —
РЕАЛЬНЫМ логином, а не с эффективным. getPendingRowForCurrentUser (через
ApprovalListService.getCurrentUserId) отладочную подмену (shared/
impersonation.ts) уже учитывает, поэтому «активный шаг есть» под подменой
срабатывал верно, а «это я» — нет: сравнение велось с разными личностями.
Теперь оба используют effectiveUserEmail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Было в самой шапке, над заявлением — теперь рядом с панелью решения: это
следствие решения «Согласовать», логичнее видеть его там же, а не отдельно
от того действия, которое его вызвало.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gorbenko.pavel merged commit df29d080ed into main 2026-08-26 07:07:03 +00:00
gorbenko.pavel deleted branch fired-request 2026-08-26 07:07:04 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: timur.gorodetskiy/request-portal#15