From c4b2cb3c3d56c013922f3e91830a956209f3ade3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A2=D0=B8=D0=BC=D1=83=D1=80=20=D0=93=D0=BE=D1=80=D0=BE?= =?UTF-8?q?=D0=B4=D0=B5=D1=86=D0=BA=D0=B8=D0=B9?= Date: Thu, 29 Oct 2026 14:33:25 +0500 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=BF=D0=BE=D0=BB=D1=8F=20=D0=B2=D0=B8?= =?UTF-8?q?=D0=B4=D0=B0=20=C2=AB=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BC=D0=B5=D1=81=D1=82=D0=B0=20=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D1=8B=C2=BB=20=D0=B2=20=D0=B8=D0=B7=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D0=B8=20=D1=83=D1=81=D0=BB=D0=BE?= =?UTF-8?q?=D0=B2=D0=B8=D0=B9=20=D0=A2=D0=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Пятый вид: место работы «было → стало» парой организация + проект (текущая организация — из шапки, новая — из справочника и обязательна), адрес, дата, причина, условия переезда, вахта с графиком работы и обоснование. Проверки «обязательно при отправке» по видам собраны в одну функцию, чтобы форма не росла с каждым новым видом. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../ContractChange/ContractChangeForm.tsx | 11 +- .../forms/ContractChange/README.md | 6 +- .../byType/ChangeTypeSections.tsx | 4 +- .../WorkplaceChangeSection.module.scss | 8 + .../WorkplaceChangeSection.tsx | 144 ++++++++++++++++++ .../byType/WorkplaceChange/options.ts | 33 ++++ .../WorkplaceChange/useCompanyOptions.ts | 48 ++++++ .../ContractChange/byType/submitChecks.ts | 21 +++ .../forms/ContractChange/load.ts | 32 +++- .../forms/ContractChange/model.ts | 45 +++++- .../forms/ContractChange/provisioning.json | 21 ++- .../forms/ContractChange/submit.ts | 27 +++- 12 files changed, 387 insertions(+), 13 deletions(-) create mode 100644 src/webparts/requestPortal/forms/ContractChange/byType/WorkplaceChange/WorkplaceChangeSection.module.scss create mode 100644 src/webparts/requestPortal/forms/ContractChange/byType/WorkplaceChange/WorkplaceChangeSection.tsx create mode 100644 src/webparts/requestPortal/forms/ContractChange/byType/WorkplaceChange/options.ts create mode 100644 src/webparts/requestPortal/forms/ContractChange/byType/WorkplaceChange/useCompanyOptions.ts create mode 100644 src/webparts/requestPortal/forms/ContractChange/byType/submitChecks.ts 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="— выберите —" + /> + + +