feat: поля вида «Изменение режима / графика работы» в изменении условий ТД
Седьмой вид, по ролям строго по шаблонам ТЗ. Работник просит неполный рабочий день / неполную рабочую неделю и пишет заявление; работодатель переводит на вахтовый, сменный или иной режим с новым графиком и временем работы и указывает причину из ТЗ. Текущий режим и дата — у обоих, документ «Новый график» необязателен. Списки графика и времени работы и поле «список + Другое» стали общими для видов формы. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -62,7 +62,9 @@ RequestTypeId» в `docs/forms.md`). Тип и маршрут в админке
|
||||
| Перевод на другую должность (постоянный) | `PositionTransfer` | новая должность (`PositionPicker`: справочник `Dictionary/Positions` + ручной ввод), дата перевода, причина (карьерный рост / вакантная должность / новый объект, перераспределение функций / иное), обоснование. Работника и работодателя не различаем — причины видны всем, работник пишет текст заявления в обоснование. Вложение **«Должностная инструкция»** — всегда, обязательно |
|
||||
| Перевод в другое подразделение | `DepartmentTransfer` | новое подразделение (`DepartmentPicker`, имя + id), дата перевода, причина (усиление подразделения / перераспределение функций / вакансия / иное), обоснование, **принимающий руководитель** (People, обязателен при отправке) — см. ниже |
|
||||
| Изменение места работы (другая местность) | `WorkplaceChange` | место работы «было → стало» парой **организация + проект**: текущая организация — из «Организации» сотрудника (только чтение, отдельно не хранится — это `EmployeeCompany`), текущий проект (`ProjectPicker`); новая организация (`Dictionary/Companies`, **обязательна при отправке**, не подставляется) и новый проект; адрес нового места, дата, причина (новый объект / переезд подразделения или работодателя / иное), условия переезда и компенсации; переключатель **«Вахта»** → «График работы» (тот же набор, что в подборе и приёме, «Другое» → ручной ввод; без вахты график очищается); обоснование |
|
||||
| Остальные 5 | — | ещё не разобраны с кадрами |
|
||||
| Продление срока ТД | — | **пропущен** (2026-10-29): по ТЗ это автоматический запрос ОК за месяц до окончания срочного ТД (маршрут В), а не обычная заявка — решать отдельно |
|
||||
| Изменение режима / графика работы | `ScheduleChange` | по ролям строго по ТЗ. Общее: текущий график + текущее время работы (списки как в подборе/приёме, «Другое» → текст; `OptionWithOtherField`, наборы — `byType/workSchedule.ts`), дата начала, вложение «Новый график» (необязательно). **Работник:** неполный рабочий день (часов) / неполная рабочая неделя (дней) — одно или оба, текст заявления. **Работодатель:** новый режим (вахтовый / сменный / иной + описание), новые график и время работы, причина (специфика объекта / смещение сроков проекта — только из ТЗ), обоснование. Поля другой роли при сохранении очищаются |
|
||||
| Остальные 3 | — | ещё не разобраны с кадрами |
|
||||
|
||||
Обязательное по виду при отправке (инициатор, документы, принимающий руководитель,
|
||||
новая организация) — одна функция `byType/submitChecks.ts`; черновик сохраняется без них.
|
||||
|
||||
@@ -6,6 +6,7 @@ import { AllowanceSection } from './Allowance/AllowanceSection';
|
||||
import { PositionTransferSection } from './PositionTransfer/PositionTransferSection';
|
||||
import { DepartmentTransferSection } from './DepartmentTransfer/DepartmentTransferSection';
|
||||
import { WorkplaceChangeSection } from './WorkplaceChange/WorkplaceChangeSection';
|
||||
import { ScheduleChangeSection } from './ScheduleChange/ScheduleChangeSection';
|
||||
|
||||
/**
|
||||
* Вид изменения → секция с его полями. Partial: поля видов добавляются по мере
|
||||
@@ -17,7 +18,8 @@ const SECTION_BY_TYPE: Partial<Record<ChangeType, React.FC<ISectionProps>>> = {
|
||||
allowance: AllowanceSection,
|
||||
'position-transfer': PositionTransferSection,
|
||||
'department-transfer': DepartmentTransferSection,
|
||||
'workplace-change': WorkplaceChangeSection
|
||||
'workplace-change': WorkplaceChangeSection,
|
||||
'schedule-change': ScheduleChangeSection
|
||||
};
|
||||
|
||||
/** Секции всех выбранных видов, в порядке выбора (сейчас вид один — см. MAX_CHANGE_TYPES). */
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import * as React from 'react';
|
||||
import { FormField, Select, TextInput, ISelectOption } from '../../../shared/ui';
|
||||
import { OPTION_OTHER } from './workSchedule';
|
||||
|
||||
interface Props {
|
||||
label: string;
|
||||
id: string;
|
||||
value: string;
|
||||
/** Текст при «Другое». */
|
||||
otherValue: string;
|
||||
options: ISelectOption[];
|
||||
onChange: (value: string) => void;
|
||||
onOtherChange: (text: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Выбор из списка + ручной ввод при «Другое» (как график/время работы в подборе
|
||||
* и приёме). Выбрали не «Другое» — ручной текст очищается, чтобы не сохранить
|
||||
* скрытое значение. Поля кладутся прямо в сетку CollapseSection.
|
||||
*/
|
||||
export const OptionWithOtherField: React.FC<Props> = ({
|
||||
label,
|
||||
id,
|
||||
value,
|
||||
otherValue,
|
||||
options,
|
||||
onChange,
|
||||
onOtherChange
|
||||
}) => (
|
||||
<>
|
||||
<FormField label={label} htmlFor={id}>
|
||||
<Select
|
||||
id={id}
|
||||
value={value}
|
||||
onChange={(v) => {
|
||||
onChange(v);
|
||||
if (v !== OPTION_OTHER) onOtherChange('');
|
||||
}}
|
||||
options={options}
|
||||
placeholder="— выберите —"
|
||||
/>
|
||||
</FormField>
|
||||
{value === OPTION_OTHER && (
|
||||
<FormField label={`${label} (другое)`} htmlFor={`${id}Other`}>
|
||||
<TextInput id={`${id}Other`} value={otherValue} onChange={onOtherChange} />
|
||||
</FormField>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
import * as React from 'react';
|
||||
import { CollapseSection, FormField, TextInput, Select } from '../../../../shared/ui';
|
||||
import { useFormReadOnly } from '@tnsu/ui-kit';
|
||||
import { AttachmentSlotField } from '../../../../shared/attachments/AttachmentSlotField';
|
||||
import { CHANGE_TYPE_LABELS, INITIATOR_EMPLOYEE } from '../../changeTypes';
|
||||
import { ISectionProps } from '../../model';
|
||||
import { InitiatorReasonFields } from '../InitiatorReasonFields';
|
||||
import { OptionWithOtherField } from '../OptionWithOtherField';
|
||||
import { WORK_SCHEDULE_OPTIONS, WORK_HOURS_OPTIONS } from '../workSchedule';
|
||||
import {
|
||||
MODE_OTHER,
|
||||
NEW_SCHEDULE_ATTACHMENT,
|
||||
SCHEDULE_CHANGE_NEW_MODES,
|
||||
SCHEDULE_CHANGE_REASONS
|
||||
} from './options';
|
||||
|
||||
/**
|
||||
* Поля вида «Изменение режима / графика работы» — по ролям, строго по шаблонам ТЗ
|
||||
* (см. options.ts). Общее: текущий режим (график + время работы), дата начала,
|
||||
* необязательный документ «Новый график». Работник — неполное рабочее время
|
||||
* (часов в день / дней в неделю) и текст заявления; работодатель — новый режим,
|
||||
* новые график и время работы, причина и обоснование. Все поля необязательные.
|
||||
*
|
||||
* Поля — прямо в CollapseSection, без FormGrid (тело секции само сетка).
|
||||
*/
|
||||
export const ScheduleChangeSection: React.FC<ISectionProps> = ({ data, set, attachments }) => {
|
||||
const readOnly = useFormReadOnly();
|
||||
const isEmployee = data.ChangeInitiator === INITIATOR_EMPLOYEE;
|
||||
|
||||
return (
|
||||
<CollapseSection title={CHANGE_TYPE_LABELS['schedule-change']} icon="calendar">
|
||||
<OptionWithOtherField
|
||||
label="Текущий график работы"
|
||||
id="ScheduleChangeCurrentSchedule"
|
||||
value={data.ScheduleChangeCurrentSchedule}
|
||||
otherValue={data.ScheduleChangeCurrentScheduleOther}
|
||||
options={WORK_SCHEDULE_OPTIONS}
|
||||
onChange={(v) => set('ScheduleChangeCurrentSchedule', v)}
|
||||
onOtherChange={(v) => set('ScheduleChangeCurrentScheduleOther', v)}
|
||||
/>
|
||||
<OptionWithOtherField
|
||||
label="Текущее время работы"
|
||||
id="ScheduleChangeCurrentHours"
|
||||
value={data.ScheduleChangeCurrentHours}
|
||||
otherValue={data.ScheduleChangeCurrentHoursOther}
|
||||
options={WORK_HOURS_OPTIONS}
|
||||
onChange={(v) => set('ScheduleChangeCurrentHours', v)}
|
||||
onOtherChange={(v) => set('ScheduleChangeCurrentHoursOther', v)}
|
||||
/>
|
||||
<FormField label="Дата начала" htmlFor="ScheduleChangeDate" full>
|
||||
<TextInput
|
||||
id="ScheduleChangeDate"
|
||||
type="date"
|
||||
value={data.ScheduleChangeDate}
|
||||
onChange={(v) => set('ScheduleChangeDate', v)}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
{isEmployee && (
|
||||
<>
|
||||
{/* Термины ТК: неполный рабочий день / неполная рабочая неделя — без них
|
||||
«часов в день» читалось непонятно к чему. Заполнить можно одно или оба. */}
|
||||
<FormField label="Неполный рабочий день, часов" htmlFor="ScheduleChangeHoursPerDay">
|
||||
<TextInput
|
||||
id="ScheduleChangeHoursPerDay"
|
||||
type="number"
|
||||
value={data.ScheduleChangeHoursPerDay}
|
||||
onChange={(v) => set('ScheduleChangeHoursPerDay', v)}
|
||||
placeholder="например, 6"
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="Неполная рабочая неделя, дней" htmlFor="ScheduleChangeDaysPerWeek">
|
||||
<TextInput
|
||||
id="ScheduleChangeDaysPerWeek"
|
||||
type="number"
|
||||
value={data.ScheduleChangeDaysPerWeek}
|
||||
onChange={(v) => set('ScheduleChangeDaysPerWeek', v)}
|
||||
placeholder="например, 4"
|
||||
/>
|
||||
</FormField>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!isEmployee && (
|
||||
<>
|
||||
<FormField label="Новый режим" htmlFor="ScheduleChangeNewMode" full>
|
||||
<Select
|
||||
id="ScheduleChangeNewMode"
|
||||
value={data.ScheduleChangeNewMode}
|
||||
onChange={(v) => {
|
||||
set('ScheduleChangeNewMode', v);
|
||||
if (v !== MODE_OTHER) set('ScheduleChangeNewModeOther', '');
|
||||
}}
|
||||
options={SCHEDULE_CHANGE_NEW_MODES}
|
||||
placeholder="— выберите —"
|
||||
/>
|
||||
</FormField>
|
||||
{data.ScheduleChangeNewMode === MODE_OTHER && (
|
||||
<FormField label="Иной режим (описание)" htmlFor="ScheduleChangeNewModeOther" full>
|
||||
<TextInput
|
||||
id="ScheduleChangeNewModeOther"
|
||||
value={data.ScheduleChangeNewModeOther}
|
||||
onChange={(v) => set('ScheduleChangeNewModeOther', v)}
|
||||
/>
|
||||
</FormField>
|
||||
)}
|
||||
<OptionWithOtherField
|
||||
label="Новый график работы"
|
||||
id="ScheduleChangeNewSchedule"
|
||||
value={data.ScheduleChangeNewSchedule}
|
||||
otherValue={data.ScheduleChangeNewScheduleOther}
|
||||
options={WORK_SCHEDULE_OPTIONS}
|
||||
onChange={(v) => set('ScheduleChangeNewSchedule', v)}
|
||||
onOtherChange={(v) => set('ScheduleChangeNewScheduleOther', v)}
|
||||
/>
|
||||
<OptionWithOtherField
|
||||
label="Новое время работы"
|
||||
id="ScheduleChangeNewHours"
|
||||
value={data.ScheduleChangeNewHours}
|
||||
otherValue={data.ScheduleChangeNewHoursOther}
|
||||
options={WORK_HOURS_OPTIONS}
|
||||
onChange={(v) => set('ScheduleChangeNewHours', v)}
|
||||
onOtherChange={(v) => set('ScheduleChangeNewHoursOther', v)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<InitiatorReasonFields
|
||||
initiator={data.ChangeInitiator}
|
||||
reason={{
|
||||
id: 'ScheduleChangeReason',
|
||||
value: data.ScheduleChangeReason,
|
||||
options: SCHEDULE_CHANGE_REASONS,
|
||||
onChange: (v) => set('ScheduleChangeReason', v)
|
||||
}}
|
||||
justification={{
|
||||
id: 'ScheduleChangeJustification',
|
||||
value: data.ScheduleChangeJustification,
|
||||
onChange: (v) => set('ScheduleChangeJustification', v)
|
||||
}}
|
||||
/>
|
||||
{attachments && (
|
||||
<AttachmentSlotField
|
||||
label={NEW_SCHEDULE_ATTACHMENT}
|
||||
editor={attachments}
|
||||
editable={!readOnly}
|
||||
groupKey={NEW_SCHEDULE_ATTACHMENT}
|
||||
category={NEW_SCHEDULE_ATTACHMENT}
|
||||
/>
|
||||
)}
|
||||
</CollapseSection>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ISelectOption } from '../../../../shared/ui';
|
||||
|
||||
/**
|
||||
* Вид «Изменение режима / графика работы». По ТЗ шаблон зависит от инициатора:
|
||||
* - работник: «прошу установить неполное рабочее время (___ ч в день / ___ дней
|
||||
* в неделю) с __.__.20__ (текст)»;
|
||||
* - работодатель: «прошу перевести работника на а) вахтовый метод; б) сменный
|
||||
* график; в) иной режим (текст) с __.__.20__ в связи со спецификой объекта /
|
||||
* смещением сроков проекта».
|
||||
* Значения = choices колонок ScheduleChangeNewMode / ScheduleChangeReason.
|
||||
*/
|
||||
export const MODE_OTHER = 'Иной режим';
|
||||
export const SCHEDULE_CHANGE_NEW_MODES: ISelectOption[] = ['Вахтовый метод', 'Сменный график', MODE_OTHER].map(
|
||||
(mode) => ({ value: mode, label: mode })
|
||||
);
|
||||
|
||||
/** Причины работодателя — ровно те, что в шаблоне ТЗ («Иное» в ТЗ нет — не добавляем). */
|
||||
export const SCHEDULE_CHANGE_REASONS: ISelectOption[] = ['Специфика объекта', 'Смещение сроков проекта'].map(
|
||||
(reason) => ({ value: reason, label: reason })
|
||||
);
|
||||
|
||||
/** Вложение «Новый график» — по ТЗ «можно приложить документ», необязательно. */
|
||||
export const NEW_SCHEDULE_ATTACHMENT = 'Новый график';
|
||||
+8
-18
@@ -3,7 +3,9 @@ import { CollapseSection, FormField, TextInput, TextArea, Select, Toggle } from
|
||||
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 { WORKPLACE_CHANGE_REASONS } from './options';
|
||||
import { WORK_SCHEDULE_OPTIONS } from '../workSchedule';
|
||||
import { OptionWithOtherField } from '../OptionWithOtherField';
|
||||
import { useCompanyOptions } from './useCompanyOptions';
|
||||
import { InitiatorReasonFields } from '../InitiatorReasonFields';
|
||||
import styles from './WorkplaceChangeSection.module.scss';
|
||||
@@ -102,27 +104,15 @@ export const WorkplaceChangeSection: React.FC<ISectionProps> = ({ data, set, con
|
||||
/>
|
||||
</FormField>
|
||||
{data.WorkplaceChangeRotation && (
|
||||
<FormField label="График работы" htmlFor="WorkplaceChangeSchedule">
|
||||
<Select
|
||||
<OptionWithOtherField
|
||||
label="График работы"
|
||||
id="WorkplaceChangeSchedule"
|
||||
value={data.WorkplaceChangeSchedule}
|
||||
onChange={(v) => {
|
||||
set('WorkplaceChangeSchedule', v);
|
||||
if (v !== SCHEDULE_OTHER) set('WorkplaceChangeScheduleOther', '');
|
||||
}}
|
||||
otherValue={data.WorkplaceChangeScheduleOther}
|
||||
options={WORK_SCHEDULE_OPTIONS}
|
||||
placeholder="— выберите —"
|
||||
onChange={(v) => set('WorkplaceChangeSchedule', v)}
|
||||
onOtherChange={(v) => set('WorkplaceChangeScheduleOther', v)}
|
||||
/>
|
||||
</FormField>
|
||||
)}
|
||||
{data.WorkplaceChangeRotation && data.WorkplaceChangeSchedule === SCHEDULE_OTHER && (
|
||||
<FormField label="График работы (другое)" htmlFor="WorkplaceChangeScheduleOther">
|
||||
<TextInput
|
||||
id="WorkplaceChangeScheduleOther"
|
||||
value={data.WorkplaceChangeScheduleOther}
|
||||
onChange={(v) => set('WorkplaceChangeScheduleOther', v)}
|
||||
/>
|
||||
</FormField>
|
||||
)}
|
||||
<InitiatorReasonFields
|
||||
initiator={data.ChangeInitiator}
|
||||
|
||||
@@ -10,24 +10,3 @@ export const WORKPLACE_CHANGE_REASONS: ISelectOption[] = [
|
||||
'Переезд подразделения / работодателя в другую местность',
|
||||
'Иное'
|
||||
].map((reason) => ({ value: reason, label: reason }));
|
||||
|
||||
/** «Другое» в графике работы — открывает ручной ввод. */
|
||||
export const SCHEDULE_OTHER = 'Другое';
|
||||
|
||||
/**
|
||||
* Варианты «График работы» при вахте — тот же набор, что в «Заявке на подбор» и
|
||||
* «Заявке на приём» (WORK_SCHEDULE_OPTIONS там): формы независимы, значения
|
||||
* продублированы намеренно, как и между ними.
|
||||
*/
|
||||
export const WORK_SCHEDULE_OPTIONS: ISelectOption[] = [
|
||||
'5/2',
|
||||
'Вахтовый 30/30',
|
||||
'Вахтовый 20/10',
|
||||
'Вахтовый 30/15',
|
||||
'Вахтовый 22/8',
|
||||
'Вахтовый 15/15',
|
||||
'6/1',
|
||||
'1/2',
|
||||
'2/2',
|
||||
SCHEDULE_OTHER
|
||||
].map((schedule) => ({ value: schedule, label: schedule }));
|
||||
|
||||
@@ -3,6 +3,7 @@ import { attachmentsOfCategory, prefixFiles } from '../../../shared/attachments/
|
||||
import { IFormData } from '../model';
|
||||
import { ATTESTATION_ATTACHMENT, requiresAttestation } from './Allowance/options';
|
||||
import { JOB_DESCRIPTION_ATTACHMENT } from './PositionTransfer/options';
|
||||
import { NEW_SCHEDULE_ATTACHMENT } from './ScheduleChange/options';
|
||||
|
||||
/**
|
||||
* Подписанный слот вложений по колонке ТЗ «Что приложить». Подпись (category)
|
||||
@@ -23,6 +24,10 @@ export function activeAttachmentSlots(data: IFormData): IAttachmentSlot[] {
|
||||
if (data.ChangeTypes.includes('position-transfer')) {
|
||||
slots.push({ category: JOB_DESCRIPTION_ATTACHMENT, required: true });
|
||||
}
|
||||
if (data.ChangeTypes.includes('schedule-change')) {
|
||||
// «Можно приложить документ» — необязательно.
|
||||
slots.push({ category: NEW_SCHEDULE_ATTACHMENT, required: false });
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { ISelectOption } from '../../../shared/ui';
|
||||
|
||||
/**
|
||||
* Режим работы: «График работы» и «Время работы» — те же наборы, что в «Заявке
|
||||
* на подбор» и «Заявке на приём» (WORK_SCHEDULE_OPTIONS / WORK_HOURS_OPTIONS там):
|
||||
* формы независимы, значения продублированы намеренно, как и между ними.
|
||||
* Общие для видов этой формы (место работы — вахта, режим/график работы).
|
||||
*/
|
||||
|
||||
/** «Другое» в списке — открывает ручной ввод (см. OptionWithOtherField). */
|
||||
export const OPTION_OTHER = 'Другое';
|
||||
|
||||
function toOptions(values: string[]): ISelectOption[] {
|
||||
return values.map((value) => ({ value, label: value }));
|
||||
}
|
||||
|
||||
export const WORK_SCHEDULE_OPTIONS: ISelectOption[] = toOptions([
|
||||
'5/2',
|
||||
'Вахтовый 30/30',
|
||||
'Вахтовый 20/10',
|
||||
'Вахтовый 30/15',
|
||||
'Вахтовый 22/8',
|
||||
'Вахтовый 15/15',
|
||||
'6/1',
|
||||
'1/2',
|
||||
'2/2',
|
||||
OPTION_OTHER
|
||||
]);
|
||||
|
||||
/** 24-часовой формат, как в подборе и приёме. */
|
||||
export const WORK_HOURS_OPTIONS: ISelectOption[] = toOptions([
|
||||
'08:00-17:00',
|
||||
'09:00-18:00',
|
||||
'07:00-19:00',
|
||||
'08:00-20:00',
|
||||
'09:00-21:00',
|
||||
OPTION_OTHER
|
||||
]);
|
||||
@@ -124,7 +124,22 @@ export async function loadContractChangeRequest(itemId: number, listId?: string)
|
||||
'WorkplaceChangeRotation',
|
||||
'WorkplaceChangeSchedule',
|
||||
'WorkplaceChangeScheduleOther',
|
||||
'WorkplaceChangeJustification'
|
||||
'WorkplaceChangeJustification',
|
||||
'ScheduleChangeCurrentSchedule',
|
||||
'ScheduleChangeCurrentScheduleOther',
|
||||
'ScheduleChangeCurrentHours',
|
||||
'ScheduleChangeCurrentHoursOther',
|
||||
'ScheduleChangeDate',
|
||||
'ScheduleChangeHoursPerDay',
|
||||
'ScheduleChangeDaysPerWeek',
|
||||
'ScheduleChangeNewMode',
|
||||
'ScheduleChangeNewModeOther',
|
||||
'ScheduleChangeNewSchedule',
|
||||
'ScheduleChangeNewScheduleOther',
|
||||
'ScheduleChangeNewHours',
|
||||
'ScheduleChangeNewHoursOther',
|
||||
'ScheduleChangeReason',
|
||||
'ScheduleChangeJustification'
|
||||
)
|
||||
.expand('DepartmentTransferReceivingHead')()) as Record<string, unknown>;
|
||||
const receivingHead = item.DepartmentTransferReceivingHead as { EMail?: string } | undefined;
|
||||
@@ -170,7 +185,22 @@ export async function loadContractChangeRequest(itemId: number, listId?: string)
|
||||
WorkplaceChangeRotation: item.WorkplaceChangeRotation === true,
|
||||
WorkplaceChangeSchedule: text(item.WorkplaceChangeSchedule),
|
||||
WorkplaceChangeScheduleOther: text(item.WorkplaceChangeScheduleOther),
|
||||
WorkplaceChangeJustification: text(item.WorkplaceChangeJustification)
|
||||
WorkplaceChangeJustification: text(item.WorkplaceChangeJustification),
|
||||
ScheduleChangeCurrentSchedule: text(item.ScheduleChangeCurrentSchedule),
|
||||
ScheduleChangeCurrentScheduleOther: text(item.ScheduleChangeCurrentScheduleOther),
|
||||
ScheduleChangeCurrentHours: text(item.ScheduleChangeCurrentHours),
|
||||
ScheduleChangeCurrentHoursOther: text(item.ScheduleChangeCurrentHoursOther),
|
||||
ScheduleChangeDate: spDateOnlyToIso(text(item.ScheduleChangeDate)),
|
||||
ScheduleChangeHoursPerDay: numberText(item.ScheduleChangeHoursPerDay),
|
||||
ScheduleChangeDaysPerWeek: numberText(item.ScheduleChangeDaysPerWeek),
|
||||
ScheduleChangeNewMode: text(item.ScheduleChangeNewMode),
|
||||
ScheduleChangeNewModeOther: text(item.ScheduleChangeNewModeOther),
|
||||
ScheduleChangeNewSchedule: text(item.ScheduleChangeNewSchedule),
|
||||
ScheduleChangeNewScheduleOther: text(item.ScheduleChangeNewScheduleOther),
|
||||
ScheduleChangeNewHours: text(item.ScheduleChangeNewHours),
|
||||
ScheduleChangeNewHoursOther: text(item.ScheduleChangeNewHoursOther),
|
||||
ScheduleChangeReason: text(item.ScheduleChangeReason),
|
||||
ScheduleChangeJustification: text(item.ScheduleChangeJustification)
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -126,6 +126,31 @@ export interface IFormData {
|
||||
WorkplaceChangeScheduleOther: string;
|
||||
/** Обоснование; при «Иное» — его текст, у работника — текст заявления. */
|
||||
WorkplaceChangeJustification: string;
|
||||
|
||||
// ── Вид «Изменение режима / графика работы» (byType/ScheduleChange). Поля по ролям
|
||||
// строго по ТЗ (см. ScheduleChange/options.ts). Все необязательные; «Другое» → *Other.
|
||||
/** Общее: текущий график и время работы (WORK_SCHEDULE_OPTIONS / WORK_HOURS_OPTIONS). */
|
||||
ScheduleChangeCurrentSchedule: string;
|
||||
ScheduleChangeCurrentScheduleOther: string;
|
||||
ScheduleChangeCurrentHours: string;
|
||||
ScheduleChangeCurrentHoursOther: string;
|
||||
/** Общее: с какой даты, ISO YYYY-MM-DD. */
|
||||
ScheduleChangeDate: string;
|
||||
/** Работник: неполное рабочее время — часов в день и дней в неделю (строка из input type=number). */
|
||||
ScheduleChangeHoursPerDay: string;
|
||||
ScheduleChangeDaysPerWeek: string;
|
||||
/** Работодатель: новый режим (SCHEDULE_CHANGE_NEW_MODES); при «Иной режим» — описание. */
|
||||
ScheduleChangeNewMode: string;
|
||||
ScheduleChangeNewModeOther: string;
|
||||
/** Работодатель: новые график и время работы. */
|
||||
ScheduleChangeNewSchedule: string;
|
||||
ScheduleChangeNewScheduleOther: string;
|
||||
ScheduleChangeNewHours: string;
|
||||
ScheduleChangeNewHoursOther: string;
|
||||
/** Работодатель: причина (SCHEDULE_CHANGE_REASONS). */
|
||||
ScheduleChangeReason: string;
|
||||
/** Работодатель — обоснование, работник — текст заявления. */
|
||||
ScheduleChangeJustification: string;
|
||||
}
|
||||
|
||||
/** Обновление одного поля формы с сохранением типа значения. */
|
||||
@@ -179,7 +204,22 @@ export const EMPTY_DATA: IFormData = {
|
||||
WorkplaceChangeRotation: false,
|
||||
WorkplaceChangeSchedule: '',
|
||||
WorkplaceChangeScheduleOther: '',
|
||||
WorkplaceChangeJustification: ''
|
||||
WorkplaceChangeJustification: '',
|
||||
ScheduleChangeCurrentSchedule: '',
|
||||
ScheduleChangeCurrentScheduleOther: '',
|
||||
ScheduleChangeCurrentHours: '',
|
||||
ScheduleChangeCurrentHoursOther: '',
|
||||
ScheduleChangeDate: '',
|
||||
ScheduleChangeHoursPerDay: '',
|
||||
ScheduleChangeDaysPerWeek: '',
|
||||
ScheduleChangeNewMode: '',
|
||||
ScheduleChangeNewModeOther: '',
|
||||
ScheduleChangeNewSchedule: '',
|
||||
ScheduleChangeNewScheduleOther: '',
|
||||
ScheduleChangeNewHours: '',
|
||||
ScheduleChangeNewHoursOther: '',
|
||||
ScheduleChangeReason: '',
|
||||
ScheduleChangeJustification: ''
|
||||
};
|
||||
|
||||
/** Плейсхолдер для поля выбора сотрудника. */
|
||||
|
||||
@@ -90,6 +90,22 @@
|
||||
{ "internalName": "WorkplaceChangeRotation", "title": "Место работы: вахта", "type": "Boolean" },
|
||||
{ "internalName": "WorkplaceChangeSchedule", "title": "Место работы: график работы", "type": "Text" },
|
||||
{ "internalName": "WorkplaceChangeScheduleOther", "title": "Место работы: график работы (другое)", "type": "Text" },
|
||||
{ "internalName": "WorkplaceChangeJustification", "title": "Место работы: обоснование", "type": "Note" }
|
||||
{ "internalName": "WorkplaceChangeJustification", "title": "Место работы: обоснование", "type": "Note" },
|
||||
|
||||
{ "internalName": "ScheduleChangeCurrentSchedule", "title": "Режим работы: текущий график", "type": "Text" },
|
||||
{ "internalName": "ScheduleChangeCurrentScheduleOther", "title": "Режим работы: текущий график (другое)", "type": "Text" },
|
||||
{ "internalName": "ScheduleChangeCurrentHours", "title": "Режим работы: текущее время работы", "type": "Text" },
|
||||
{ "internalName": "ScheduleChangeCurrentHoursOther", "title": "Режим работы: текущее время работы (другое)", "type": "Text" },
|
||||
{ "internalName": "ScheduleChangeDate", "title": "Режим работы: дата начала", "type": "DateTime", "dateOnly": true },
|
||||
{ "internalName": "ScheduleChangeHoursPerDay", "title": "Режим работы: неполный рабочий день, часов", "type": "Number", "decimals": 1 },
|
||||
{ "internalName": "ScheduleChangeDaysPerWeek", "title": "Режим работы: неполная рабочая неделя, дней", "type": "Number", "decimals": 0 },
|
||||
{ "internalName": "ScheduleChangeNewMode", "title": "Режим работы: новый режим", "type": "Choice", "choices": ["Вахтовый метод", "Сменный график", "Иной режим"] },
|
||||
{ "internalName": "ScheduleChangeNewModeOther", "title": "Режим работы: иной режим (описание)", "type": "Text" },
|
||||
{ "internalName": "ScheduleChangeNewSchedule", "title": "Режим работы: новый график", "type": "Text" },
|
||||
{ "internalName": "ScheduleChangeNewScheduleOther", "title": "Режим работы: новый график (другое)", "type": "Text" },
|
||||
{ "internalName": "ScheduleChangeNewHours", "title": "Режим работы: новое время работы", "type": "Text" },
|
||||
{ "internalName": "ScheduleChangeNewHoursOther", "title": "Режим работы: новое время работы (другое)", "type": "Text" },
|
||||
{ "internalName": "ScheduleChangeReason", "title": "Режим работы: причина", "type": "Choice", "choices": ["Специфика объекта", "Смещение сроков проекта"] },
|
||||
{ "internalName": "ScheduleChangeJustification", "title": "Режим работы: обоснование / текст заявления", "type": "Note" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -149,28 +149,79 @@ function departmentTransferColumns(data: IFormData, receivingHeadId: number | un
|
||||
};
|
||||
}
|
||||
|
||||
/** Колонки вида «Изменение места работы»; вид не выбран — очищаются. */
|
||||
/** Колонки вида «Изменение места работы»; вид не выбран — очищаются, график — только при вахте. */
|
||||
function workplaceChangeColumns(data: IFormData): Record<string, unknown> {
|
||||
const columns = {
|
||||
WorkplaceChangeCurrentProject: data.WorkplaceChangeCurrentProject.trim() || null,
|
||||
WorkplaceChangeCurrentProjectId: data.WorkplaceChangeCurrentProjectId || null,
|
||||
WorkplaceChangeNewCompany: data.WorkplaceChangeNewCompany || null,
|
||||
WorkplaceChangeNewCompanyId: data.WorkplaceChangeNewCompanyId || null,
|
||||
WorkplaceChangeNewProject: data.WorkplaceChangeNewProject.trim() || null,
|
||||
WorkplaceChangeNewProjectId: data.WorkplaceChangeNewProjectId || null,
|
||||
WorkplaceChangeAddress: data.WorkplaceChangeAddress.trim() || null,
|
||||
WorkplaceChangeDate: data.WorkplaceChangeDate || null,
|
||||
WorkplaceChangeReason: employerReason(data, data.WorkplaceChangeReason) ?? null,
|
||||
WorkplaceChangeRelocation: data.WorkplaceChangeRelocation || null,
|
||||
const isSelected = data.ChangeTypes.includes('workplace-change');
|
||||
const fields = keepOrClear(isSelected, {
|
||||
WorkplaceChangeCurrentProject: data.WorkplaceChangeCurrentProject.trim(),
|
||||
WorkplaceChangeCurrentProjectId: data.WorkplaceChangeCurrentProjectId,
|
||||
WorkplaceChangeNewCompany: data.WorkplaceChangeNewCompany,
|
||||
WorkplaceChangeNewCompanyId: data.WorkplaceChangeNewCompanyId,
|
||||
WorkplaceChangeNewProject: data.WorkplaceChangeNewProject.trim(),
|
||||
WorkplaceChangeNewProjectId: data.WorkplaceChangeNewProjectId,
|
||||
WorkplaceChangeAddress: data.WorkplaceChangeAddress.trim(),
|
||||
WorkplaceChangeDate: data.WorkplaceChangeDate,
|
||||
WorkplaceChangeReason: employerReason(data, data.WorkplaceChangeReason),
|
||||
WorkplaceChangeRelocation: data.WorkplaceChangeRelocation,
|
||||
WorkplaceChangeRotation: data.WorkplaceChangeRotation,
|
||||
WorkplaceChangeJustification: data.WorkplaceChangeJustification
|
||||
});
|
||||
// Без вахты графика нет, даже если он остался в состоянии формы.
|
||||
WorkplaceChangeSchedule: (data.WorkplaceChangeRotation && data.WorkplaceChangeSchedule) || null,
|
||||
WorkplaceChangeScheduleOther: (data.WorkplaceChangeRotation && data.WorkplaceChangeScheduleOther.trim()) || null,
|
||||
WorkplaceChangeJustification: data.WorkplaceChangeJustification || null
|
||||
};
|
||||
if (data.ChangeTypes.includes('workplace-change')) return columns;
|
||||
// Тот же набор ключей, все — null: колонки невыбранного вида очищаются.
|
||||
return Object.keys(columns).reduce<Record<string, unknown>>((acc, key) => ({ ...acc, [key]: null }), {});
|
||||
const rotationFields = keepOrClear(isSelected && data.WorkplaceChangeRotation, {
|
||||
WorkplaceChangeSchedule: data.WorkplaceChangeSchedule,
|
||||
WorkplaceChangeScheduleOther: data.WorkplaceChangeScheduleOther.trim()
|
||||
});
|
||||
return { ...fields, ...rotationFields };
|
||||
}
|
||||
|
||||
/**
|
||||
* Колонки вида «Изменение режима / графика работы». Поля другой роли очищаются:
|
||||
* у работника нет нового режима и причин, у работодателя — часов/дней неполного времени.
|
||||
*/
|
||||
function scheduleChangeColumns(data: IFormData): Record<string, unknown> {
|
||||
const isSelected = data.ChangeTypes.includes('schedule-change');
|
||||
const isEmployee = data.ChangeInitiator === INITIATOR_EMPLOYEE;
|
||||
|
||||
const common = keepOrClear(isSelected, {
|
||||
ScheduleChangeCurrentSchedule: data.ScheduleChangeCurrentSchedule,
|
||||
ScheduleChangeCurrentScheduleOther: data.ScheduleChangeCurrentScheduleOther.trim(),
|
||||
ScheduleChangeCurrentHours: data.ScheduleChangeCurrentHours,
|
||||
ScheduleChangeCurrentHoursOther: data.ScheduleChangeCurrentHoursOther.trim(),
|
||||
ScheduleChangeDate: data.ScheduleChangeDate,
|
||||
ScheduleChangeJustification: data.ScheduleChangeJustification
|
||||
});
|
||||
const employeeFields = keepOrClear(isSelected && isEmployee, {
|
||||
ScheduleChangeHoursPerDay: numberColumn(data.ScheduleChangeHoursPerDay),
|
||||
ScheduleChangeDaysPerWeek: numberColumn(data.ScheduleChangeDaysPerWeek)
|
||||
});
|
||||
const employerFields = keepOrClear(isSelected && !isEmployee, {
|
||||
ScheduleChangeNewMode: data.ScheduleChangeNewMode,
|
||||
ScheduleChangeNewModeOther: data.ScheduleChangeNewModeOther.trim(),
|
||||
ScheduleChangeNewSchedule: data.ScheduleChangeNewSchedule,
|
||||
ScheduleChangeNewScheduleOther: data.ScheduleChangeNewScheduleOther.trim(),
|
||||
ScheduleChangeNewHours: data.ScheduleChangeNewHours,
|
||||
ScheduleChangeNewHoursOther: data.ScheduleChangeNewHoursOther.trim(),
|
||||
ScheduleChangeReason: data.ScheduleChangeReason
|
||||
});
|
||||
return { ...common, ...employeeFields, ...employerFields };
|
||||
}
|
||||
|
||||
/**
|
||||
* Группа колонок: keep — значения как есть (пустые '' / undefined → null; false
|
||||
* остаётся false), иначе все → null, чтобы SharePoint очистил поля, которых при
|
||||
* текущем виде/роли на форме нет.
|
||||
*/
|
||||
function keepOrClear(
|
||||
keep: boolean,
|
||||
columns: Record<string, string | number | boolean | undefined>
|
||||
): Record<string, unknown> {
|
||||
const result: Record<string, unknown> = {};
|
||||
Object.keys(columns).forEach((key) => {
|
||||
const value = columns[key];
|
||||
const isEmpty = value === undefined || value === '';
|
||||
result[key] = keep && !isEmpty ? value : null;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Поля элемента списка данных из модели формы. */
|
||||
@@ -186,7 +237,8 @@ function buildItem(data: IFormData, status: string, receivingHeadId: number | un
|
||||
...allowanceColumns(data),
|
||||
...positionTransferColumns(data),
|
||||
...departmentTransferColumns(data, receivingHeadId),
|
||||
...workplaceChangeColumns(data)
|
||||
...workplaceChangeColumns(data),
|
||||
...scheduleChangeColumns(data)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user