Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz> Reviewed-on: http://git.tnsu.kz/timur.gorodetskiy/request-portal/pulls/23
Заявление на увольнение (hr-dismissal)
Назначение: заявление работника о расторжении трудового договора по собственному
желанию (штатный сотрудник). Бумажный первоисточник — «Шаблон Заявление на увольнение ШТАТ.docx».
Категория: Отдел кадров
Списки SP: «Заявление на увольнение 1 — данные» / «Заявление на увольнение 1 — согласование»
RequestTypeId (внешний API маршрутов): не хардкодится — резолвится в рантайме по
pageUrl («hr-dismissal») через useRequestTypeId (см. «Формы больше не хардкодят
RequestTypeId» в docs/forms.md). Тип заведён в админке approval-route (id 16,
список данных /sites/requests/Lists/18).
Маршрут
- Фазы: только согласование. Фазы исполнения нет — после согласования заявка получает статус «Согласовано» (как «Обходной лист» / «Отпуск без содержания»).
- Матрица редактируема заявителем при новой заявке / черновике / возврате:
useMatrixEditor(editable = !readOnly). - Условие
IsManagerв маршруте (если настроено в админке) резолвится по полю Position ЭТОЙ заявки, поэтомуPositionпередаётся вfieldValuesпри планировании (getPlannedMatrix(..., [{ fieldName: 'Position', ... }])) — как в «Обходном листе». - Настроенный шаблон маршрута (approval-route, route id 17): шаг 1 —
HeadType.Chief(непосредственный руководитель увольняемого, тот жеEmployee/Manager?managerType=chief, что резолвит «Лицо, принимающее ОС/НМА», см. ниже), шаг 2 —HeadType.CompanyDirectors(см. следующий раздел).
Особое поведение
-
По умолчанию заявка на себя, но подать можно и за другого: «Сотрудник» — People-picker (
Employee), маршрут резолвится по e-mail выбранного сотрудника, reloadKey матрицы включаетdata.Employeeи должность основной компании (см. ниже). -
Автоподстановка при выборе сотрудника: вся таблица организаций — из ОДНОГО справочника
Employee/ByEmail(shared/dictionary) — см. ниже. -
Таблица организаций (
OrganizationsTable,forms/Dismissal/sections/, без подписи над собой — у неё уже есть заголовки колонок «Компания» / «Структурное подразделение» / «Должность») — ЕДИНСТВЕННОЕ место для компании, подразделения и должности, и на форме подачи (редактируемая), и в режиме просмотра (read-only) — отдельных полей для них нет (были бы дублированием).data.Organization: IOrganizationRecord[]— массив{company, department, position}; поле называетсяdepartment(тот же справочникSection, что и раньше — см. следующий абзац), колонка называется «Подразделение», и то и другое read-only. Должность редактируема В ЛЮБОЙ строке (справочник не всегда точен), но маршрут (условие «IsManager») и обязательность по валидации завязаны ТОЛЬКО на должность ПЕРВОЙ (основной) записи —primaryPosition(data.Organization); должности остальных компаний правятся, но ни на что не влияют. Справочник ничего не вернул (сотрудник не найден в api.tnsu.kz) — таблица всё равно рендерится одной строкой с прочерками вместо компании/подразделения и редактируемой должностью: иначе такому человеку нечем было бы заполнить обязательное поле.Хранение в SharePoint — три ПАРАЛЛЕЛЬНЫХ JSON-столбца, не один общий блок:
Organization/Department/Position— каждый содержит JSON-массив строк (компании / департаменты / должности), один и тот же индекс = одна и та же запись во всех трёх. Сборка при чтении и разборка при записи —zipOrganization/organizationColumnвmodel.ts/submit.ts; не-JSON значение (заявка, сохранённая до перехода на этот формат) не роняет чтение — трактуется как одна компания (см.parseOrganizationColumn). -
Последний рабочий день (
LastWorkingDay) — обязателен, не раньше чем черезMIN_NOTICE_MONTHS(1) календарный месяц от сегодня (isoDatePlusMonths,minуinput type=date+ проверка вvalidateна случай обхода UI) — срок предупреждения об увольнении по ТК. Более позднюю дату (по договорённости с работодателем) форма не ограничивает. -
Контактный телефон (
Phone) — обязателен,PhoneInput(маска). -
Комментарий (
Comment) — необязательный свободный текст. Причины увольнения отдельного поля нет: в шаблоне формулировка фиксированная («по собственному желанию»). -
Заголовок элемента (
Title):Заявление на увольнение — {ФИО} — дд.мм.гггг.
Совместители: организации таблицей, удаление синхронизировано с маршрутом
Сотрудник может числиться сразу в НЕСКОЛЬКИХ компаниях (совместительство).
Approval-route резолвит маршрут через HeadType.CompanyDirectors — ОДИН шаг
разворачивается в по одному согласующему НА КАЖДУЮ компанию сотрудника (директор,
а где нет такой штатной единицы — должностной эквивалент: Главный механик для
«ТОО KazPromService», Генеральный директор» для «ТОО СТ Global», иначе Директор, см. DirectorPositionByCompanyв approval-routeTemplateRouteService). Это уже работает БЕЗ участия портала — сам approval-route резолвит все компании сотрудника через Employee/ByEmail`.
OrganizationsTable (см. предыдущий раздел) на форме подачи показывает кнопку
«убрать» у каждой строки, когда компаний больше одной. Если сотрудник реально не
увольняется из ВСЕХ компаний сразу, HR убирает лишние: DismissalForm.handleRemoveOrganization
- снимает запись из
data.Organization(массив всегда отражает ТЕКУЩИЙ, не первоначальный, набор — так же восстанавливается при повторном открытии черновика/возврата:loadDismissalRequestчитает его из уже сохранённыхOrganization/Department/Position, без обращения к справочнику заново); - независимо резолвит директора/эквивалента этой компании через
getCompanyDirectorEmail(shared/dictionary/employeeLookup.service.ts— зеркало того жеDirectorPositionByCompany, что и на бэкенде) и удаляет строку с этим email из уже загруженной ПЛАНИРУЕМОЙ матрицы (matrix.remove).
Убирать согласующего из матрицы вручную (кнопкой самой матрицы) при этом не нужно — это best-effort синхронизация: если матрица ещё не загрузилась или директор не нашёлся, компания всё равно снимается из заявления, а несовпадение в матрице HR поправит вручную через саму матрицу (она и так редактируема).
Согласовано → обходной лист создаётся сам
Как только заявление получает статус «Согласовано» (маршрут пройден целиком),
портал заводит по нему обходной лист — ensureClearanceForDismissal
(createClearance.ts), вызывается из DismissalView в onDecided последнего
согласующего.
Форма обходного листа при этом не открывается: маршрут резолвится через
approval-route (getPlannedMatrix для фаз «Согласование» и «Исполнение» — по
Employee и Position заявления), элемент пишется в текущую партицию списка
«Обходной лист … — данные» и сразу уходит на согласование
(initialSubmitStatus + notifyActiveStep, как при обычной подаче). Поля целиком
выводятся из заявления: сотрудник, должность, подразделение, организация, а
последний рабочий день становится датой расторжения договора; отметки
(парковка, СКУД, обучение) остаются пустыми — их заполняют профильные согласующие.
Пара связывается перекрёстными колонками-гиперссылками (тип Url):
ClearanceUrl в заявлении → на обходной лист, DismissalUrl в обходном листе → на
заявление. Не Lookup намеренно: ссылка ведёт на страницу портала
(…?rpmode=approve&rptype=…&rpid=…&rplist=…, см. docs/routing.md), а не на элемент
списка, и не ломается, когда парная заявка уезжает в другую партицию.
Ссылки видны на обеих формах и кликабельны: поле «Обходной лист» в заявлении и
«Заявление на увольнение» в обходном листе (LinkField, shared/ui) — read-only,
открывают парную заявку в ТОЙ ЖЕ вкладке и появляются только когда связь есть.
Переход идёт без перезагрузки страницы: ссылка ведёт на ту же страницу портала с
другими rp-параметрами, поэтому клик перехватывается и уходит в navigate()
(navigateToRequestLink в shared/routing.ts) — а Ctrl/⌘+клик по-прежнему открывает
заявку в отдельной вкладке, потому что href настоящий. У согласующего, только что
одобрившего заявление, ссылка появляется сразу, без перезагрузки.
Что важно знать про это поведение:
- Идемпотентность — по непустой
ClearanceUrl. Заявление можно согласовать повторно (возврат на доработку → отправка → согласование), и без этой проверки на каждый цикл появлялся бы новый обходной лист. - Работает в браузере последнего согласующего. Если создание упало (или он закрыл вкладку в этот момент), заявление всё равно согласовано, а обходного листа нет — портал покажет предупреждение «заведите вручную». Серверного повтора нет: фонового обработчика у портала пока не существует, всё делает клиент.
- Автором обходного листа в SharePoint станет этот согласующий (в «Мои заявки»
лист попадёт к нему). На маршрут это не влияет — он резолвится по
Employee. - Тип заявки
clearance-formдолжен быть заведён в админке approval-route — иначе создание падает на резолвеRequestTypeId.
Печать заблокирована до согласования обходного листа
DismissalView не показывает PrintButton, пока парный обходной лист не дошёл
хотя бы до статуса «Согласовано» (или дальше — «Выполнение»/«Закрыта», если у
него есть исполнители): печатать заявление раньше значит выдать документ до того,
как процесс увольнения (карты, суммы к списанию) подтверждён целиком. Статус
читается НАПРЯМУЮ по ClearanceUrl (parseRoute вытаскивает itemId/listId
из самой ссылки, loadClearanceRequest — статус) — отдельного поля-дублёра нет.
Пока ClearanceUrl пуст (обходной лист ещё не создан/не удалось создать) —
печать тоже заблокирована, печатать нечего.
«Лицо, принимающее ОС/НМА»
Колонка AssetsAcceptor (People) — кто принимает основные средства/НМА
увольняемого. Обязательна, но заполняет её не заявитель, а непосредственный
руководитель увольняемого — тот, кого approval-route резолвит для
HeadType.Chief (Employee/Manager?managerType=chief, см. «Маршрут» выше).
- В форме подачи поле показывается read-only (обычно пустое) — просто информирует, что это будет заполнено позже.
- В
DismissalViewполе редактируемо ТОЛЬКО когда одновременно: (а) у текущего пользователя сейчас активный шаг решения (getPendingRowForCurrentUser), и (б) его e-mail совпадает с результатомgetManagerEmail(..., 'chief')дляdata.Employee— проверка по ИДЕНТИЧНОСТИ, а не по должности (в отличие отapproverRights.ts«Обходного листа»: здесь должность руководителя может быть любой). - Обязательность применяется НЕ на уровне списка (иначе создание заявки падало
бы — поле нечем заполнить до решения руководителя), а через
DecisionPanel.onBeforeDecide: пустое поле бросает ошибку ДО применения решения (approve/return/reject — API не различает, какое именно) и сохраняет значение сразу перед ним, тем же приёмом, что «Обходной лист» использует для своих отметок.
Отличия от «Обходной лист» (clearance-form)
Обходной лист оформляет HR после решения об увольнении (обход подразделений, карты, обучение). Это заявление — вход в процесс: его подаёт сам работник, полей всего восемь, отметок подразделений нет.
Секции формы
RequestSection (сотрудник · организация/департамент/должность (таблица — редактируемая на форме подачи, read-only в просмотре) · последний рабочий день · телефон · комментарий · ссылка на обходной лист · лицо, принимающее ОС/НМА)
Точки входа
- rptype:
hr-dismissal - Режим согласующего (просмотр): есть (
DismissalView) - «Создать на основе»: нет