feat: поля вида «Изменение трудовой функции» в изменении условий ТД
Восьмой вид, ровно по шаблону ТЗ: дата изменения, «в связи с» свободным текстом (списка причин в ТЗ нет) и обязательная новая должностная инструкция — отдельный слот вложений. Инициатор — только руководитель. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -64,7 +64,8 @@ RequestTypeId» в `docs/forms.md`). Тип и маршрут в админке
|
|||||||
| Изменение места работы (другая местность) | `WorkplaceChange` | место работы «было → стало» парой **организация + проект**: текущая организация — из «Организации» сотрудника (только чтение, отдельно не хранится — это `EmployeeCompany`), текущий проект (`ProjectPicker`); новая организация (`Dictionary/Companies`, **обязательна при отправке**, не подставляется) и новый проект; адрес нового места, дата, причина (новый объект / переезд подразделения или работодателя / иное), условия переезда и компенсации; переключатель **«Вахта»** → «График работы» (тот же набор, что в подборе и приёме, «Другое» → ручной ввод; без вахты график очищается); обоснование |
|
| Изменение места работы (другая местность) | `WorkplaceChange` | место работы «было → стало» парой **организация + проект**: текущая организация — из «Организации» сотрудника (только чтение, отдельно не хранится — это `EmployeeCompany`), текущий проект (`ProjectPicker`); новая организация (`Dictionary/Companies`, **обязательна при отправке**, не подставляется) и новый проект; адрес нового места, дата, причина (новый объект / переезд подразделения или работодателя / иное), условия переезда и компенсации; переключатель **«Вахта»** → «График работы» (тот же набор, что в подборе и приёме, «Другое» → ручной ввод; без вахты график очищается); обоснование |
|
||||||
| Продление срока ТД | — | **пропущен** (2026-10-29): по ТЗ это автоматический запрос ОК за месяц до окончания срочного ТД (маршрут В), а не обычная заявка — решать отдельно |
|
| Продление срока ТД | — | **пропущен** (2026-10-29): по ТЗ это автоматический запрос ОК за месяц до окончания срочного ТД (маршрут В), а не обычная заявка — решать отдельно |
|
||||||
| Изменение режима / графика работы | `ScheduleChange` | по ролям строго по ТЗ. Общее: текущий график + текущее время работы (списки как в подборе/приёме, «Другое» → текст; `OptionWithOtherField`, наборы — `byType/workSchedule.ts`), дата начала, вложение «Новый график» (необязательно). **Работник:** неполный рабочий день (часов) / неполная рабочая неделя (дней) — одно или оба, текст заявления. **Работодатель:** новый режим (вахтовый / сменный / иной + описание), новые график и время работы, причина (специфика объекта / смещение сроков проекта — только из ТЗ), обоснование. Поля другой роли при сохранении очищаются |
|
| Изменение режима / графика работы | `ScheduleChange` | по ролям строго по ТЗ. Общее: текущий график + текущее время работы (списки как в подборе/приёме, «Другое» → текст; `OptionWithOtherField`, наборы — `byType/workSchedule.ts`), дата начала, вложение «Новый график» (необязательно). **Работник:** неполный рабочий день (часов) / неполная рабочая неделя (дней) — одно или оба, текст заявления. **Работодатель:** новый режим (вахтовый / сменный / иной + описание), новые график и время работы, причина (специфика объекта / смещение сроков проекта — только из ТЗ), обоснование. Поля другой роли при сохранении очищаются |
|
||||||
| Остальные 3 | — | ещё не разобраны с кадрами |
|
| Изменение трудовой функции (без смены должности) | `FunctionChange` | ровно по шаблону ТЗ: дата изменения, «В связи с» (свободный текст — списка причин в ТЗ нет), вложение **«Новая должностная инструкция»** — обязательно. Инициатор — только руководитель |
|
||||||
|
| Остальные 2 | — | ещё не разобраны с кадрами |
|
||||||
|
|
||||||
Обязательное по виду при отправке (инициатор, документы, принимающий руководитель,
|
Обязательное по виду при отправке (инициатор, документы, принимающий руководитель,
|
||||||
новая организация) — одна функция `byType/submitChecks.ts`; черновик сохраняется без них.
|
новая организация) — одна функция `byType/submitChecks.ts`; черновик сохраняется без них.
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { PositionTransferSection } from './PositionTransfer/PositionTransferSect
|
|||||||
import { DepartmentTransferSection } from './DepartmentTransfer/DepartmentTransferSection';
|
import { DepartmentTransferSection } from './DepartmentTransfer/DepartmentTransferSection';
|
||||||
import { WorkplaceChangeSection } from './WorkplaceChange/WorkplaceChangeSection';
|
import { WorkplaceChangeSection } from './WorkplaceChange/WorkplaceChangeSection';
|
||||||
import { ScheduleChangeSection } from './ScheduleChange/ScheduleChangeSection';
|
import { ScheduleChangeSection } from './ScheduleChange/ScheduleChangeSection';
|
||||||
|
import { FunctionChangeSection } from './FunctionChange/FunctionChangeSection';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Вид изменения → секция с его полями. Partial: поля видов добавляются по мере
|
* Вид изменения → секция с его полями. Partial: поля видов добавляются по мере
|
||||||
@@ -19,7 +20,8 @@ const SECTION_BY_TYPE: Partial<Record<ChangeType, React.FC<ISectionProps>>> = {
|
|||||||
'position-transfer': PositionTransferSection,
|
'position-transfer': PositionTransferSection,
|
||||||
'department-transfer': DepartmentTransferSection,
|
'department-transfer': DepartmentTransferSection,
|
||||||
'workplace-change': WorkplaceChangeSection,
|
'workplace-change': WorkplaceChangeSection,
|
||||||
'schedule-change': ScheduleChangeSection
|
'schedule-change': ScheduleChangeSection,
|
||||||
|
'function-change': FunctionChangeSection
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Секции всех выбранных видов, в порядке выбора (сейчас вид один — см. MAX_CHANGE_TYPES). */
|
/** Секции всех выбранных видов, в порядке выбора (сейчас вид один — см. MAX_CHANGE_TYPES). */
|
||||||
|
|||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { CollapseSection, FormField, TextInput, TextArea } from '../../../../shared/ui';
|
||||||
|
import { useFormReadOnly } from '@tnsu/ui-kit';
|
||||||
|
import { AttachmentSlotField } from '../../../../shared/attachments/AttachmentSlotField';
|
||||||
|
import { CHANGE_TYPE_LABELS } from '../../changeTypes';
|
||||||
|
import { ISectionProps } from '../../model';
|
||||||
|
import { NEW_JOB_DESCRIPTION_ATTACHMENT } from './options';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Поля вида «Изменение трудовой функции (без смены должности)». Ровно по шаблону
|
||||||
|
* ТЗ: «прошу внести изменения в трудовую функцию работника согласно ДИ во
|
||||||
|
* вложении с __.__.20__ в связи с ___ (текст)» — дата, «в связи с» (свободный
|
||||||
|
* текст, списка причин в ТЗ нет) и обязательная новая ДИ. Инициатор — только
|
||||||
|
* руководитель.
|
||||||
|
*
|
||||||
|
* Поля — прямо в CollapseSection, без FormGrid (тело секции само сетка).
|
||||||
|
*/
|
||||||
|
export const FunctionChangeSection: React.FC<ISectionProps> = ({ data, set, attachments }) => {
|
||||||
|
const readOnly = useFormReadOnly();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CollapseSection title={CHANGE_TYPE_LABELS['function-change']} icon="document">
|
||||||
|
<FormField label="Дата изменения" htmlFor="FunctionChangeDate">
|
||||||
|
<TextInput
|
||||||
|
id="FunctionChangeDate"
|
||||||
|
type="date"
|
||||||
|
value={data.FunctionChangeDate}
|
||||||
|
onChange={(v) => set('FunctionChangeDate', v)}
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
<FormField label="В связи с" htmlFor="FunctionChangeReason" full>
|
||||||
|
<TextArea
|
||||||
|
id="FunctionChangeReason"
|
||||||
|
value={data.FunctionChangeReason}
|
||||||
|
onChange={(v) => set('FunctionChangeReason', v)}
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
{attachments && (
|
||||||
|
<AttachmentSlotField
|
||||||
|
label={NEW_JOB_DESCRIPTION_ATTACHMENT}
|
||||||
|
editor={attachments}
|
||||||
|
editable={!readOnly}
|
||||||
|
groupKey={NEW_JOB_DESCRIPTION_ATTACHMENT}
|
||||||
|
category={NEW_JOB_DESCRIPTION_ATTACHMENT}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</CollapseSection>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
/**
|
||||||
|
* Вложение «Новая должностная инструкция» — по ТЗ «приложить документ», обязательно.
|
||||||
|
* Отдельная подпись от «Должностной инструкции» перевода на должность: это разные слоты.
|
||||||
|
*/
|
||||||
|
export const NEW_JOB_DESCRIPTION_ATTACHMENT = 'Новая должностная инструкция';
|
||||||
@@ -4,6 +4,7 @@ import { IFormData } from '../model';
|
|||||||
import { ATTESTATION_ATTACHMENT, requiresAttestation } from './Allowance/options';
|
import { ATTESTATION_ATTACHMENT, requiresAttestation } from './Allowance/options';
|
||||||
import { JOB_DESCRIPTION_ATTACHMENT } from './PositionTransfer/options';
|
import { JOB_DESCRIPTION_ATTACHMENT } from './PositionTransfer/options';
|
||||||
import { NEW_SCHEDULE_ATTACHMENT } from './ScheduleChange/options';
|
import { NEW_SCHEDULE_ATTACHMENT } from './ScheduleChange/options';
|
||||||
|
import { NEW_JOB_DESCRIPTION_ATTACHMENT } from './FunctionChange/options';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Подписанный слот вложений по колонке ТЗ «Что приложить». Подпись (category)
|
* Подписанный слот вложений по колонке ТЗ «Что приложить». Подпись (category)
|
||||||
@@ -24,6 +25,9 @@ export function activeAttachmentSlots(data: IFormData): IAttachmentSlot[] {
|
|||||||
if (data.ChangeTypes.includes('position-transfer')) {
|
if (data.ChangeTypes.includes('position-transfer')) {
|
||||||
slots.push({ category: JOB_DESCRIPTION_ATTACHMENT, required: true });
|
slots.push({ category: JOB_DESCRIPTION_ATTACHMENT, required: true });
|
||||||
}
|
}
|
||||||
|
if (data.ChangeTypes.includes('function-change')) {
|
||||||
|
slots.push({ category: NEW_JOB_DESCRIPTION_ATTACHMENT, required: true });
|
||||||
|
}
|
||||||
if (data.ChangeTypes.includes('schedule-change')) {
|
if (data.ChangeTypes.includes('schedule-change')) {
|
||||||
// «Можно приложить документ» — необязательно.
|
// «Можно приложить документ» — необязательно.
|
||||||
slots.push({ category: NEW_SCHEDULE_ATTACHMENT, required: false });
|
slots.push({ category: NEW_SCHEDULE_ATTACHMENT, required: false });
|
||||||
|
|||||||
@@ -139,7 +139,9 @@ export async function loadContractChangeRequest(itemId: number, listId?: string)
|
|||||||
'ScheduleChangeNewHours',
|
'ScheduleChangeNewHours',
|
||||||
'ScheduleChangeNewHoursOther',
|
'ScheduleChangeNewHoursOther',
|
||||||
'ScheduleChangeReason',
|
'ScheduleChangeReason',
|
||||||
'ScheduleChangeJustification'
|
'ScheduleChangeJustification',
|
||||||
|
'FunctionChangeDate',
|
||||||
|
'FunctionChangeReason'
|
||||||
)
|
)
|
||||||
.expand('DepartmentTransferReceivingHead')()) as Record<string, unknown>;
|
.expand('DepartmentTransferReceivingHead')()) as Record<string, unknown>;
|
||||||
const receivingHead = item.DepartmentTransferReceivingHead as { EMail?: string } | undefined;
|
const receivingHead = item.DepartmentTransferReceivingHead as { EMail?: string } | undefined;
|
||||||
@@ -200,7 +202,9 @@ export async function loadContractChangeRequest(itemId: number, listId?: string)
|
|||||||
ScheduleChangeNewHours: text(item.ScheduleChangeNewHours),
|
ScheduleChangeNewHours: text(item.ScheduleChangeNewHours),
|
||||||
ScheduleChangeNewHoursOther: text(item.ScheduleChangeNewHoursOther),
|
ScheduleChangeNewHoursOther: text(item.ScheduleChangeNewHoursOther),
|
||||||
ScheduleChangeReason: text(item.ScheduleChangeReason),
|
ScheduleChangeReason: text(item.ScheduleChangeReason),
|
||||||
ScheduleChangeJustification: text(item.ScheduleChangeJustification)
|
ScheduleChangeJustification: text(item.ScheduleChangeJustification),
|
||||||
|
FunctionChangeDate: spDateOnlyToIso(text(item.FunctionChangeDate)),
|
||||||
|
FunctionChangeReason: text(item.FunctionChangeReason)
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -151,6 +151,13 @@ export interface IFormData {
|
|||||||
ScheduleChangeReason: string;
|
ScheduleChangeReason: string;
|
||||||
/** Работодатель — обоснование, работник — текст заявления. */
|
/** Работодатель — обоснование, работник — текст заявления. */
|
||||||
ScheduleChangeJustification: string;
|
ScheduleChangeJustification: string;
|
||||||
|
|
||||||
|
// ── Вид «Изменение трудовой функции» (byType/FunctionChange). Только работодатель.
|
||||||
|
// Поля необязательные; вложение «Новая должностная инструкция» — обязательно (attachmentSlots).
|
||||||
|
/** С какой даты, ISO YYYY-MM-DD. */
|
||||||
|
FunctionChangeDate: string;
|
||||||
|
/** «В связи с ___ (текст)» из шаблона ТЗ — списка причин нет. */
|
||||||
|
FunctionChangeReason: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Обновление одного поля формы с сохранением типа значения. */
|
/** Обновление одного поля формы с сохранением типа значения. */
|
||||||
@@ -219,7 +226,9 @@ export const EMPTY_DATA: IFormData = {
|
|||||||
ScheduleChangeNewHours: '',
|
ScheduleChangeNewHours: '',
|
||||||
ScheduleChangeNewHoursOther: '',
|
ScheduleChangeNewHoursOther: '',
|
||||||
ScheduleChangeReason: '',
|
ScheduleChangeReason: '',
|
||||||
ScheduleChangeJustification: ''
|
ScheduleChangeJustification: '',
|
||||||
|
FunctionChangeDate: '',
|
||||||
|
FunctionChangeReason: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Плейсхолдер для поля выбора сотрудника. */
|
/** Плейсхолдер для поля выбора сотрудника. */
|
||||||
|
|||||||
@@ -106,6 +106,9 @@
|
|||||||
{ "internalName": "ScheduleChangeNewHours", "title": "Режим работы: новое время работы", "type": "Text" },
|
{ "internalName": "ScheduleChangeNewHours", "title": "Режим работы: новое время работы", "type": "Text" },
|
||||||
{ "internalName": "ScheduleChangeNewHoursOther", "title": "Режим работы: новое время работы (другое)", "type": "Text" },
|
{ "internalName": "ScheduleChangeNewHoursOther", "title": "Режим работы: новое время работы (другое)", "type": "Text" },
|
||||||
{ "internalName": "ScheduleChangeReason", "title": "Режим работы: причина", "type": "Choice", "choices": ["Специфика объекта", "Смещение сроков проекта"] },
|
{ "internalName": "ScheduleChangeReason", "title": "Режим работы: причина", "type": "Choice", "choices": ["Специфика объекта", "Смещение сроков проекта"] },
|
||||||
{ "internalName": "ScheduleChangeJustification", "title": "Режим работы: обоснование / текст заявления", "type": "Note" }
|
{ "internalName": "ScheduleChangeJustification", "title": "Режим работы: обоснование / текст заявления", "type": "Note" },
|
||||||
|
|
||||||
|
{ "internalName": "FunctionChangeDate", "title": "Трудовая функция: дата изменения", "type": "DateTime", "dateOnly": true },
|
||||||
|
{ "internalName": "FunctionChangeReason", "title": "Трудовая функция: в связи с", "type": "Note" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -238,7 +238,11 @@ function buildItem(data: IFormData, status: string, receivingHeadId: number | un
|
|||||||
...positionTransferColumns(data),
|
...positionTransferColumns(data),
|
||||||
...departmentTransferColumns(data, receivingHeadId),
|
...departmentTransferColumns(data, receivingHeadId),
|
||||||
...workplaceChangeColumns(data),
|
...workplaceChangeColumns(data),
|
||||||
...scheduleChangeColumns(data)
|
...scheduleChangeColumns(data),
|
||||||
|
...keepOrClear(data.ChangeTypes.includes('function-change'), {
|
||||||
|
FunctionChangeDate: data.FunctionChangeDate,
|
||||||
|
FunctionChangeReason: data.FunctionChangeReason
|
||||||
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user