diff --git a/src/webparts/requestPortal/forms/ContractChange/ContractChangeForm.tsx b/src/webparts/requestPortal/forms/ContractChange/ContractChangeForm.tsx index b937283..553438d 100644 --- a/src/webparts/requestPortal/forms/ContractChange/ContractChangeForm.tsx +++ b/src/webparts/requestPortal/forms/ContractChange/ContractChangeForm.tsx @@ -42,7 +42,6 @@ import { EMPTY_DATA, SetField, REQUIRED_FIELDS_MESSAGE, - RECEIVING_HEAD_REQUIRED_MESSAGE, routeEmail } from './model'; import { saveContractChangeRequest, contractChangeApprovalService } from './submit'; @@ -50,7 +49,8 @@ import { loadContractChangeRequest } from './load'; import { useContractEmployee } from './useContractEmployee'; import { RequestSection } from './sections/RequestSection'; import { ChangeTypeSections } from './byType/ChangeTypeSections'; -import { missingRequiredAttachments, prepareFilesFor } from './byType/attachmentSlots'; +import { prepareFilesFor } from './byType/attachmentSlots'; +import { changeTypeSubmitError } from './byType/submitChecks'; import { useContractRoute, matrixToApprovers } from './useContractRoute'; import { useAttachmentEditor } from '../../shared/attachments/useAttachmentEditor'; import { getAttachments } from '../../shared/attachments/attachments.service'; @@ -185,10 +185,9 @@ const ContractChangeForm: React.FC = ({ const validateSubmit = (): string | undefined => { const formError = validate(); if (formError) return formError; - // Обязательные документы — только на отправку: черновик можно сохранить и без файла. - const missingFiles = missingRequiredAttachments(data, attachments); - if (missingFiles.length > 0) return `Приложите обязательные документы: ${missingFiles.join(', ')}.`; - if (isDepartmentTransfer && !data.DepartmentTransferReceivingHead) return RECEIVING_HEAD_REQUIRED_MESSAGE; + // Обязательное по виду изменения (документы, принимающий руководитель и т.п.) — только на отправку. + const typeError = changeTypeSubmitError(data, attachments); + if (typeError) return typeError; if (matrix.error) { return `Не удалось определить маршрут согласования: ${matrix.error}. Попробуйте ещё раз или обратитесь к администратору.`; } diff --git a/src/webparts/requestPortal/forms/ContractChange/README.md b/src/webparts/requestPortal/forms/ContractChange/README.md index 75d5d7e..631ac92 100644 --- a/src/webparts/requestPortal/forms/ContractChange/README.md +++ b/src/webparts/requestPortal/forms/ContractChange/README.md @@ -61,7 +61,11 @@ RequestTypeId» в `docs/forms.md`). Тип и маршрут в админке | Установление надбавки / доплаты | `Allowance` | основание (а–д: надбавка или доплата видно из пункта), дата начала, дата окончания или «бессрочно» (флажок прячет и очищает дату), размер + переключатель «% / ₸» (`UnitSwitch`, по умолчанию %), обоснование — в т.ч. объём доп. работы при совмещении и текст «иное». Письменное согласие работника на совмещение оформляет кадровик после согласования — не в форме. Вложение **«Результаты аттестации»** — только при основании «г», обязательно | | Перевод на другую должность (постоянный) | `PositionTransfer` | новая должность (`PositionPicker`: справочник `Dictionary/Positions` + ручной ввод), дата перевода, причина (карьерный рост / вакантная должность / новый объект, перераспределение функций / иное), обоснование. Работника и работодателя не различаем — причины видны всем, работник пишет текст заявления в обоснование. Вложение **«Должностная инструкция»** — всегда, обязательно | | Перевод в другое подразделение | `DepartmentTransfer` | новое подразделение (`DepartmentPicker`, имя + id), дата перевода, причина (усиление подразделения / перераспределение функций / вакансия / иное), обоснование, **принимающий руководитель** (People, обязателен при отправке) — см. ниже | -| Остальные 6 | — | ещё не разобраны с кадрами | +| Изменение места работы (другая местность) | `WorkplaceChange` | место работы «было → стало» парой **организация + проект**: текущая организация — из «Организации» сотрудника (только чтение, отдельно не хранится — это `EmployeeCompany`), текущий проект (`ProjectPicker`); новая организация (`Dictionary/Companies`, **обязательна при отправке**, не подставляется) и новый проект; адрес нового места, дата, причина (новый объект / переезд подразделения или работодателя / иное), условия переезда и компенсации; переключатель **«Вахта»** → «График работы» (тот же набор, что в подборе и приёме, «Другое» → ручной ввод; без вахты график очищается); обоснование | +| Остальные 5 | — | ещё не разобраны с кадрами | + +Обязательное по виду при отправке (документы, принимающий руководитель, новая +организация) — одна функция `byType/submitChecks.ts`; черновик сохраняется без них. **Принимающий руководитель — временное решение (2026-10-29).** По ТЗ (лист «Маршрут»: А шаг 2, Б шаг 3 параллельно с непосредственным руководителем) его diff --git a/src/webparts/requestPortal/forms/ContractChange/byType/ChangeTypeSections.tsx b/src/webparts/requestPortal/forms/ContractChange/byType/ChangeTypeSections.tsx index fa69061..a1416ee 100644 --- a/src/webparts/requestPortal/forms/ContractChange/byType/ChangeTypeSections.tsx +++ b/src/webparts/requestPortal/forms/ContractChange/byType/ChangeTypeSections.tsx @@ -5,6 +5,7 @@ import { SalaryIncreaseSection } from './SalaryIncrease/SalaryIncreaseSection'; import { AllowanceSection } from './Allowance/AllowanceSection'; import { PositionTransferSection } from './PositionTransfer/PositionTransferSection'; import { DepartmentTransferSection } from './DepartmentTransfer/DepartmentTransferSection'; +import { WorkplaceChangeSection } from './WorkplaceChange/WorkplaceChangeSection'; /** * Вид изменения → секция с его полями. Partial: поля видов добавляются по мере @@ -15,7 +16,8 @@ const SECTION_BY_TYPE: Partial>> = { 'salary-increase': SalaryIncreaseSection, allowance: AllowanceSection, 'position-transfer': PositionTransferSection, - 'department-transfer': DepartmentTransferSection + 'department-transfer': DepartmentTransferSection, + 'workplace-change': WorkplaceChangeSection }; /** Секции всех выбранных видов, в порядке выбора (сейчас вид один — см. MAX_CHANGE_TYPES). */ diff --git a/src/webparts/requestPortal/forms/ContractChange/byType/WorkplaceChange/WorkplaceChangeSection.module.scss b/src/webparts/requestPortal/forms/ContractChange/byType/WorkplaceChange/WorkplaceChangeSection.module.scss new file mode 100644 index 0000000..f28b027 --- /dev/null +++ b/src/webparts/requestPortal/forms/ContractChange/byType/WorkplaceChange/WorkplaceChangeSection.module.scss @@ -0,0 +1,8 @@ +@import '../../../../../../styles/design-tokens'; + +// Ошибка загрузки справочника под полем (не прячем за пустым списком). +.fieldError { + margin-top: $sp-1; + font-size: $fs-label; + color: $red-500; +} diff --git a/src/webparts/requestPortal/forms/ContractChange/byType/WorkplaceChange/WorkplaceChangeSection.tsx b/src/webparts/requestPortal/forms/ContractChange/byType/WorkplaceChange/WorkplaceChangeSection.tsx new file mode 100644 index 0000000..522b703 --- /dev/null +++ b/src/webparts/requestPortal/forms/ContractChange/byType/WorkplaceChange/WorkplaceChangeSection.tsx @@ -0,0 +1,144 @@ +import * as React from 'react'; +import { CollapseSection, FormField, TextInput, TextArea, Select, Toggle } from '../../../../shared/ui'; +import { ProjectPicker } from '../../../../shared/dictionary'; +import { CHANGE_TYPE_LABELS } from '../../changeTypes'; +import { ISectionProps } from '../../model'; +import { WORKPLACE_CHANGE_REASONS, WORK_SCHEDULE_OPTIONS, SCHEDULE_OTHER } from './options'; +import { useCompanyOptions } from './useCompanyOptions'; +import styles from './WorkplaceChangeSection.module.scss'; + +const PROJECT_PLACEHOLDER = 'Начните вводить название или код проекта…'; + +/** + * Поля вида «Изменение места работы (другая местность)». Состав согласован с + * Тимуром по ТЗ кадров: место работы «было → стало» парой «организация + проект» + * (текущая организация — из выбранной в шапке, новая обязательна), адрес, дата, + * причина (а–в), условия переезда, вахта с графиком работы, обоснование. + * + * Поля — прямо в CollapseSection, без FormGrid (тело секции само сетка). + */ +export const WorkplaceChangeSection: React.FC = ({ data, set, context }) => { + const companies = useCompanyOptions(context); + + return ( + + + {/* Та же, что выбрана в «Организации» сотрудника, — второй раз не вводим. */} + undefined} readOnly /> + + + { + set('WorkplaceChangeCurrentProject', name); + set('WorkplaceChangeCurrentProjectId', id); + }} + placeholder={PROJECT_PLACEHOLDER} + /> + + + set('WorkplaceChangeReason', v)} + options={WORKPLACE_CHANGE_REASONS} + placeholder="— выберите —" + /> + + +