feat: поля вида «Временный перевод» в изменении условий ТД
Tests / frontend-test (push) Successful in 1m31s
Tests / frontend-test (push) Successful in 1m31s
Последний из разобранных видов: откуда и куда — проект из справочника и работа текстом, период не дольше 3 месяцев включительно (календарь не даёт выбрать лишнее, при отправке — проверка), основание (производственная необходимость / простой), обоснование и пометка из шаблона ТЗ о сохранении оплаты. Поля разобраны по всем видам, кроме продления срока ТД. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -51,7 +51,7 @@
|
|||||||
| [`hr-skud-explanation`](../src/webparts/requestPortal/forms/SkudExplanation/README.md) | Объяснительная об отсутствии отметок в СКУД | Отдел кадров | да | нет | да | только «на себя»; дни выбираются кликом в календаре «Мой СКУД» (проходы Hikvision текущего пользователя), сохраняются снимком для согласующего; текст записки собирается автоматически, печать — бланк объяснительной | ⏳ код готов, списки/тип/маршрут не заведены |
|
| [`hr-skud-explanation`](../src/webparts/requestPortal/forms/SkudExplanation/README.md) | Объяснительная об отсутствии отметок в СКУД | Отдел кадров | да | нет | да | только «на себя»; дни выбираются кликом в календаре «Мой СКУД» (проходы Hikvision текущего пользователя), сохраняются снимком для согласующего; текст записки собирается автоматически, печать — бланк объяснительной | ⏳ код готов, списки/тип/маршрут не заведены |
|
||||||
| [`hr-business-trip`](../src/webparts/requestPortal/forms/BusinessTrip/README.md) | Заявка на командировку | Отдел кадров | да | да | да | можно за другого сотрудника; командирующая организация — выбор из организаций сотрудника; маршрут лентой (точки по Казахстану — город из справочника КАТО на русском и казахском + объект из справочника, за рубежом — страна/город/объект вручную; даты не задним числом, поездка ≤ 40 дней; участки с видом транспорта и временем выезда); суточные (≤ 6 МРП КЗ / ≤ 8 за рубежом) и проживание (ночи сами, ≤ 15 000 ₸ за ночь КЗ) — по точкам; комментарий после итога; задание — список задач; смета: статьи с позициями, привязанными к точкам/участкам (суммой или кол-во × цена — всё вручную), итог с долями и по объектам; обязательство по авансовому отчёту — флажок; печать без шапки адресата | ⏳ код готов, списки/тип/маршрут не заведены |
|
| [`hr-business-trip`](../src/webparts/requestPortal/forms/BusinessTrip/README.md) | Заявка на командировку | Отдел кадров | да | да | да | можно за другого сотрудника; командирующая организация — выбор из организаций сотрудника; маршрут лентой (точки по Казахстану — город из справочника КАТО на русском и казахском + объект из справочника, за рубежом — страна/город/объект вручную; даты не задним числом, поездка ≤ 40 дней; участки с видом транспорта и временем выезда); суточные (≤ 6 МРП КЗ / ≤ 8 за рубежом) и проживание (ночи сами, ≤ 15 000 ₸ за ночь КЗ) — по точкам; комментарий после итога; задание — список задач; смета: статьи с позициями, привязанными к точкам/участкам (суммой или кол-во × цена — всё вручную), итог с долями и по объектам; обязательство по авансовому отчёту — флажок; печать без шапки адресата | ⏳ код готов, списки/тип/маршрут не заведены |
|
||||||
| `hr-transfer` | Перемещение сотрудников | Отдел кадров | — | — | — | — | ⏳ заглушка (плитка есть, форма не сделана) |
|
| `hr-transfer` | Перемещение сотрудников | Отдел кадров | — | — | — | — | ⏳ заглушка (плитка есть, форма не сделана) |
|
||||||
| [`hr-contract-change`](../src/webparts/requestPortal/forms/ContractChange/README.md) | Изменение условий трудового договора | Отдел кадров | да | — | да | сотрудник из `Employee/FullBase` (и без учётки M365), снимок в текстовых колонках; маршрут по сотруднику, без учётки — по заявителю; 10 видов изменения (один, данные под несколько); поля по видам — в работе | ⏳ каркас, списки/тип/маршрут не заведены |
|
| [`hr-contract-change`](../src/webparts/requestPortal/forms/ContractChange/README.md) | Изменение условий трудового договора | Отдел кадров | да | — | да | сотрудник из `Employee/FullBase` (и без учётки M365), снимок в текстовых колонках; маршрут по сотруднику, без учётки — по заявителю; 10 видов изменения (один, данные под несколько), поля по каждому виду по ТЗ кадров (кроме «Продления срока ТД»); инициатор работник/работодатель; подписанные вложения по «Что приложить» | ⏳ код готов (кроме вида 6), списки/тип/маршрут не заведены |
|
||||||
| [`wh-service-request`](../src/webparts/requestPortal/forms/ServiceRequest/README.md) | Заявка на услуги | Склад / Снабжение | да | нет | да | маршрут по проекту (руководитель проекта/ПТО); табличные части «Приложения» и «Смета» (JSON-колонки); сумма по смете считается на лету (qty × цена), итог дублируется числовой колонкой для отчётов | ✅ |
|
| [`wh-service-request`](../src/webparts/requestPortal/forms/ServiceRequest/README.md) | Заявка на услуги | Склад / Снабжение | да | нет | да | маршрут по проекту (руководитель проекта/ПТО); табличные части «Приложения» и «Смета» (JSON-колонки); сумма по смете считается на лету (qty × цена), итог дублируется числовой колонкой для отчётов | ✅ |
|
||||||
|
|
||||||
Легенда: **Согл.** — есть фаза согласования; **Исп.** — есть фаза исполнения;
|
Легенда: **Согл.** — есть фаза согласования; **Исп.** — есть фаза исполнения;
|
||||||
|
|||||||
@@ -8,10 +8,12 @@
|
|||||||
`pageUrl` («hr-contract-change») через `useRequestTypeId` (см. «Формы больше не хардкодят
|
`pageUrl` («hr-contract-change») через `useRequestTypeId` (см. «Формы больше не хардкодят
|
||||||
RequestTypeId» в `docs/forms.md`). Тип и маршрут в админке approval-route ещё не заведены.
|
RequestTypeId» в `docs/forms.md`). Тип и маршрут в админке approval-route ещё не заведены.
|
||||||
|
|
||||||
> **Статус: каркас.** Есть сотрудник и вид изменения; поля под конкретные виды
|
> **Статус (2026-10-29):** поля разобраны по всем видам, кроме 6 «Продление срока ТД»
|
||||||
> добавляются по мере разбора ТЗ кадров. Таблица ТЗ (пример, кто инициирует, что
|
> (это автоматический запрос, не обычная заявка — отдельно). ТЗ кадров — файл
|
||||||
> приложить, сроки, маршрут А/Б/В/Г…) — общая, в форму из неё попадает только то,
|
> «ТЗ_ст33_маршруты_v2» (листы «Виды изменений ст.33» и «Маршрут»); оно общее, в форму
|
||||||
> что явно согласовано. Печатного бланка пока нет.
|
> из него попадает только то, что явно согласовано. Ещё не сделано: передача вида и
|
||||||
|
> инициатора в маршрут (`fieldValues`: фин. блок — виды 1, 2, 3, 5, 9; маршрут А/Б —
|
||||||
|
> по инициатору), печатный бланк, заведение типа и маршрутов в админке.
|
||||||
|
|
||||||
## Маршрут
|
## Маршрут
|
||||||
- Фазы: согласование (+ исполнение, если его заведут в админке — форма поддерживает).
|
- Фазы: согласование (+ исполнение, если его заведут в админке — форма поддерживает).
|
||||||
@@ -66,7 +68,7 @@ RequestTypeId» в `docs/forms.md`). Тип и маршрут в админке
|
|||||||
| Изменение режима / графика работы | `ScheduleChange` | по ролям строго по ТЗ. Общее: текущий график + текущее время работы (списки как в подборе/приёме, «Другое» → текст; `OptionWithOtherField`, наборы — `byType/workSchedule.ts`), дата начала, вложение «Новый график» (необязательно). **Работник:** неполный рабочий день (часов) / неполная рабочая неделя (дней) — одно или оба, текст заявления. **Работодатель:** новый режим (вахтовый / сменный / иной + описание), новые график и время работы, причина (специфика объекта / смещение сроков проекта — только из ТЗ), обоснование. Поля другой роли при сохранении очищаются |
|
| Изменение режима / графика работы | `ScheduleChange` | по ролям строго по ТЗ. Общее: текущий график + текущее время работы (списки как в подборе/приёме, «Другое» → текст; `OptionWithOtherField`, наборы — `byType/workSchedule.ts`), дата начала, вложение «Новый график» (необязательно). **Работник:** неполный рабочий день (часов) / неполная рабочая неделя (дней) — одно или оба, текст заявления. **Работодатель:** новый режим (вахтовый / сменный / иной + описание), новые график и время работы, причина (специфика объекта / смещение сроков проекта — только из ТЗ), обоснование. Поля другой роли при сохранении очищаются |
|
||||||
| Изменение трудовой функции (без смены должности) | `FunctionChange` | ровно по шаблону ТЗ: дата изменения, «В связи с» (свободный текст — списка причин в ТЗ нет), вложение **«Новая должностная инструкция»** — обязательно. Инициатор — только руководитель |
|
| Изменение трудовой функции (без смены должности) | `FunctionChange` | ровно по шаблону ТЗ: дата изменения, «В связи с» (свободный текст — списка причин в ТЗ нет), вложение **«Новая должностная инструкция»** — обязательно. Инициатор — только руководитель |
|
||||||
| Изменение условий труда по инициативе работодателя | `EmployerInitiative` | причина (из названия вида в ТЗ: экономические / технологические / изменения в организации / сокращение объёма работ + «Другое» со своим текстом); «Что меняется» — флажки а) график работы, б) должностной оклад (можно оба, при отправке — хотя бы один); а) текущий → новый график + дата; б) **один новый оклад + доля постоянной части в %** (по умолчанию 75 из ТЗ, 0–100, можно дробную), переменная доля = остаток до 100 %, суммы обеих частей в ₸ считаются сами (`salaryParts.ts`, превысить 100 % нельзя; в SP пишутся при сохранении) + дата; даты **не раньше +15 дней** (уведомление по ТЗ: блок в календаре + проверка при отправке); обоснование — **текстом ИЛИ документом**, одно из двух обязательно при отправке |
|
| Изменение условий труда по инициативе работодателя | `EmployerInitiative` | причина (из названия вида в ТЗ: экономические / технологические / изменения в организации / сокращение объёма работ + «Другое» со своим текстом); «Что меняется» — флажки а) график работы, б) должностной оклад (можно оба, при отправке — хотя бы один); а) текущий → новый график + дата; б) **один новый оклад + доля постоянной части в %** (по умолчанию 75 из ТЗ, 0–100, можно дробную), переменная доля = остаток до 100 %, суммы обеих частей в ₸ считаются сами (`salaryParts.ts`, превысить 100 % нельзя; в SP пишутся при сохранении) + дата; даты **не раньше +15 дней** (уведомление по ТЗ: блок в календаре + проверка при отправке); обоснование — **текстом ИЛИ документом**, одно из двух обязательно при отправке |
|
||||||
| Остальные 1 | — | ещё не разобран с кадрами |
|
| Временный перевод при производственной необходимости / простое | `TemporaryTransfer` | откуда и куда — проект из справочника + работа текстом; дата начала / окончания — **не дольше 3 месяцев** (по ТЗ «до 3 мес. в календарном году»; включительно: с 01.01 по 31.03, `period.ts`; сумму за год проверяет HR-директор); основание (производственная необходимость / простой), обоснование; пометка из шаблона ТЗ «Оплата — не ниже средней заработной платы по прежней работе». Инициатор — только руководитель |
|
||||||
|
|
||||||
Обязательное по виду при отправке (инициатор, документы, принимающий руководитель,
|
Обязательное по виду при отправке (инициатор, документы, принимающий руководитель,
|
||||||
новая организация) — одна функция `byType/submitChecks.ts`; черновик сохраняется без них.
|
новая организация) — одна функция `byType/submitChecks.ts`; черновик сохраняется без них.
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { WorkplaceChangeSection } from './WorkplaceChange/WorkplaceChangeSection
|
|||||||
import { ScheduleChangeSection } from './ScheduleChange/ScheduleChangeSection';
|
import { ScheduleChangeSection } from './ScheduleChange/ScheduleChangeSection';
|
||||||
import { FunctionChangeSection } from './FunctionChange/FunctionChangeSection';
|
import { FunctionChangeSection } from './FunctionChange/FunctionChangeSection';
|
||||||
import { EmployerInitiativeSection } from './EmployerInitiative/EmployerInitiativeSection';
|
import { EmployerInitiativeSection } from './EmployerInitiative/EmployerInitiativeSection';
|
||||||
|
import { TemporaryTransferSection } from './TemporaryTransfer/TemporaryTransferSection';
|
||||||
import styles from './ChangeTypeSections.module.scss';
|
import styles from './ChangeTypeSections.module.scss';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,7 +25,8 @@ const SECTION_BY_TYPE: Partial<Record<ChangeType, React.FC<ISectionProps>>> = {
|
|||||||
'workplace-change': WorkplaceChangeSection,
|
'workplace-change': WorkplaceChangeSection,
|
||||||
'schedule-change': ScheduleChangeSection,
|
'schedule-change': ScheduleChangeSection,
|
||||||
'function-change': FunctionChangeSection,
|
'function-change': FunctionChangeSection,
|
||||||
'employer-initiative': EmployerInitiativeSection
|
'employer-initiative': EmployerInitiativeSection,
|
||||||
|
'temporary-transfer': TemporaryTransferSection
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
@import '../../../../../../styles/design-tokens';
|
||||||
|
|
||||||
|
// Пояснение под полем (ограничение периода).
|
||||||
|
.hint {
|
||||||
|
margin-top: $sp-1;
|
||||||
|
font-size: $fs-label;
|
||||||
|
color: $gray-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Гарантия по оплате из шаблона ТЗ — на всю ширину сетки, приглушённо, как пометка.
|
||||||
|
.payNote {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
font-size: $fs-sm;
|
||||||
|
color: $gray-500;
|
||||||
|
}
|
||||||
+99
@@ -0,0 +1,99 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { FormGrid, FormField, TextInput, TextArea, Select } from '../../../../shared/ui';
|
||||||
|
import { ProjectPicker } from '../../../../shared/dictionary';
|
||||||
|
import { ISectionProps } from '../../model';
|
||||||
|
import { TEMPORARY_TRANSFER_REASONS, TEMPORARY_TRANSFER_PAY_NOTE, TEMPORARY_TRANSFER_PERIOD_HINT } from './options';
|
||||||
|
import { maxTransferEndDate } from './period';
|
||||||
|
import styles from './TemporaryTransferSection.module.scss';
|
||||||
|
|
||||||
|
const PROJECT_PLACEHOLDER = 'Начните вводить название или код проекта…';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Поля вида «Временный перевод при производственной необходимости / простое» — по
|
||||||
|
* шаблону ТЗ: «прошу временно перевести работника с ___ (объект / работа) на ___
|
||||||
|
* с … по … в связи с ___ (производственная необходимость / простой). Оплата — не
|
||||||
|
* ниже средней заработной платы по прежней работе». Откуда/куда — проект из
|
||||||
|
* справочника + работа текстом; период не длиннее 3 месяцев (period.ts).
|
||||||
|
* Инициатор — только руководитель.
|
||||||
|
*
|
||||||
|
* Поля — в FormGrid без заголовка: вид уже выбран выше, заголовок его только дублировал.
|
||||||
|
*/
|
||||||
|
export const TemporaryTransferSection: React.FC<ISectionProps> = ({ data, set, context }) => (
|
||||||
|
<FormGrid>
|
||||||
|
<FormField label="Откуда: проект / объект" htmlFor="TemporaryTransferFromProject">
|
||||||
|
<ProjectPicker
|
||||||
|
context={context}
|
||||||
|
id="TemporaryTransferFromProject"
|
||||||
|
value={data.TemporaryTransferFromProject}
|
||||||
|
onChange={(name, id) => {
|
||||||
|
set('TemporaryTransferFromProject', name);
|
||||||
|
set('TemporaryTransferFromProjectId', id);
|
||||||
|
}}
|
||||||
|
placeholder={PROJECT_PLACEHOLDER}
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
<FormField label="Откуда: работа" htmlFor="TemporaryTransferFromWork">
|
||||||
|
<TextInput
|
||||||
|
id="TemporaryTransferFromWork"
|
||||||
|
value={data.TemporaryTransferFromWork}
|
||||||
|
onChange={(v) => set('TemporaryTransferFromWork', v)}
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
<FormField label="Куда: проект / объект" htmlFor="TemporaryTransferToProject">
|
||||||
|
<ProjectPicker
|
||||||
|
context={context}
|
||||||
|
id="TemporaryTransferToProject"
|
||||||
|
value={data.TemporaryTransferToProject}
|
||||||
|
onChange={(name, id) => {
|
||||||
|
set('TemporaryTransferToProject', name);
|
||||||
|
set('TemporaryTransferToProjectId', id);
|
||||||
|
}}
|
||||||
|
placeholder={PROJECT_PLACEHOLDER}
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
<FormField label="Куда: работа" htmlFor="TemporaryTransferToWork">
|
||||||
|
<TextInput
|
||||||
|
id="TemporaryTransferToWork"
|
||||||
|
value={data.TemporaryTransferToWork}
|
||||||
|
onChange={(v) => set('TemporaryTransferToWork', v)}
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
<FormField label="Дата начала" htmlFor="TemporaryTransferStartDate">
|
||||||
|
<TextInput
|
||||||
|
id="TemporaryTransferStartDate"
|
||||||
|
type="date"
|
||||||
|
value={data.TemporaryTransferStartDate}
|
||||||
|
onChange={(v) => set('TemporaryTransferStartDate', v)}
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
<FormField label="Дата окончания" htmlFor="TemporaryTransferEndDate">
|
||||||
|
{/* Не раньше начала и не дольше 3 месяцев — календарь не даст выбрать лишнее. */}
|
||||||
|
<TextInput
|
||||||
|
id="TemporaryTransferEndDate"
|
||||||
|
type="date"
|
||||||
|
value={data.TemporaryTransferEndDate}
|
||||||
|
onChange={(v) => set('TemporaryTransferEndDate', v)}
|
||||||
|
min={data.TemporaryTransferStartDate || undefined}
|
||||||
|
max={maxTransferEndDate(data.TemporaryTransferStartDate)}
|
||||||
|
/>
|
||||||
|
<div className={styles.hint}>{TEMPORARY_TRANSFER_PERIOD_HINT}</div>
|
||||||
|
</FormField>
|
||||||
|
<FormField label="Основание" htmlFor="TemporaryTransferReason" full>
|
||||||
|
<Select
|
||||||
|
id="TemporaryTransferReason"
|
||||||
|
value={data.TemporaryTransferReason}
|
||||||
|
onChange={(v) => set('TemporaryTransferReason', v)}
|
||||||
|
options={TEMPORARY_TRANSFER_REASONS}
|
||||||
|
placeholder="— выберите —"
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
<FormField label="Обоснование" htmlFor="TemporaryTransferJustification" full>
|
||||||
|
<TextArea
|
||||||
|
id="TemporaryTransferJustification"
|
||||||
|
value={data.TemporaryTransferJustification}
|
||||||
|
onChange={(v) => set('TemporaryTransferJustification', v)}
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
<div className={styles.payNote}>{TEMPORARY_TRANSFER_PAY_NOTE}</div>
|
||||||
|
</FormGrid>
|
||||||
|
);
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { ISelectOption } from '../../../../shared/ui';
|
||||||
|
import { MAX_TRANSFER_MONTHS } from './period';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Основания временного перевода — из шаблона ТЗ («в связи с ___ (производственная
|
||||||
|
* необходимость / простой)»). Значения = choices колонки TemporaryTransferReason.
|
||||||
|
*/
|
||||||
|
export const TEMPORARY_TRANSFER_REASONS: ISelectOption[] = ['Производственная необходимость', 'Простой'].map(
|
||||||
|
(reason) => ({ value: reason, label: reason })
|
||||||
|
);
|
||||||
|
|
||||||
|
/** Гарантия работнику из шаблона ТЗ — показывается на форме пометкой. */
|
||||||
|
export const TEMPORARY_TRANSFER_PAY_NOTE = 'Оплата — не ниже средней заработной платы по прежней работе.';
|
||||||
|
|
||||||
|
/** Пояснение под датой окончания. */
|
||||||
|
export const TEMPORARY_TRANSFER_PERIOD_HINT = `Не дольше ${MAX_TRANSFER_MONTHS} месяцев.`;
|
||||||
|
export const TEMPORARY_TRANSFER_PERIOD_ERROR = `Временный перевод — не дольше ${MAX_TRANSFER_MONTHS} месяцев, и дата окончания не раньше даты начала.`;
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
import { maxTransferEndDate } from './period';
|
||||||
|
|
||||||
|
describe('maxTransferEndDate', () => {
|
||||||
|
it('3 месяца включительно: с 01.01 — по 31.03', () => {
|
||||||
|
expect(maxTransferEndDate('2026-01-01')).toBe('2026-03-31');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('с середины месяца', () => {
|
||||||
|
expect(maxTransferEndDate('2026-02-15')).toBe('2026-05-14');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('через новый год', () => {
|
||||||
|
expect(maxTransferEndDate('2026-11-01')).toBe('2027-01-31');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('конец месяца не уезжает в следующий (30.11 + 3 мес. = 28.02)', () => {
|
||||||
|
expect(maxTransferEndDate('2026-11-30')).toBe('2027-02-27');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('пустая дата — нет ограничения', () => {
|
||||||
|
expect(maxTransferEndDate('')).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Временный перевод — не дольше 3 месяцев (ТЗ: «До 3 месяцев в течение
|
||||||
|
* календарного года»; сумму за год форма не видит — её проверяет HR-директор).
|
||||||
|
*/
|
||||||
|
export const MAX_TRANSFER_MONTHS = 3;
|
||||||
|
|
||||||
|
function pad2(n: number): string {
|
||||||
|
return n < 10 ? `0${n}` : `${n}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Последний допустимый день периода, начатого start (YYYY-MM-DD): start + 3 месяца
|
||||||
|
* − 1 день, оба дня включительно (с 01.01 — по 31.03). К концу месяца прижимаем:
|
||||||
|
* 30.11 + 3 мес. — это 28.02, а не 02.03. Некорректная дата — undefined.
|
||||||
|
*/
|
||||||
|
export function maxTransferEndDate(startIso: string): string | undefined {
|
||||||
|
const [year, month, day] = startIso.split('-').map(Number);
|
||||||
|
if (!year || !month || !day) return undefined;
|
||||||
|
|
||||||
|
const targetMonthIndex = month - 1 + MAX_TRANSFER_MONTHS;
|
||||||
|
const lastDayOfTargetMonth = new Date(year, targetMonthIndex + 1, 0).getDate();
|
||||||
|
const end = new Date(year, targetMonthIndex, Math.min(day, lastDayOfTargetMonth));
|
||||||
|
end.setDate(end.getDate() - 1);
|
||||||
|
return `${end.getFullYear()}-${pad2(end.getMonth() + 1)}-${pad2(end.getDate())}`;
|
||||||
|
}
|
||||||
@@ -15,6 +15,8 @@ import {
|
|||||||
EMPLOYER_JUSTIFICATION_ATTACHMENT,
|
EMPLOYER_JUSTIFICATION_ATTACHMENT,
|
||||||
minEmployerChangeDate
|
minEmployerChangeDate
|
||||||
} from './EmployerInitiative/options';
|
} from './EmployerInitiative/options';
|
||||||
|
import { TEMPORARY_TRANSFER_PERIOD_ERROR } from './TemporaryTransfer/options';
|
||||||
|
import { maxTransferEndDate } from './TemporaryTransfer/period';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Что обязательно по выбранному виду изменения — проверяется только при ОТПРАВКЕ:
|
* Что обязательно по выбранному виду изменения — проверяется только при ОТПРАВКЕ:
|
||||||
@@ -36,6 +38,23 @@ export function changeTypeSubmitError(data: IFormData, attachments: IAttachmentE
|
|||||||
if (data.ChangeTypes.includes('employer-initiative')) {
|
if (data.ChangeTypes.includes('employer-initiative')) {
|
||||||
return employerInitiativeError(data, attachments);
|
return employerInitiativeError(data, attachments);
|
||||||
}
|
}
|
||||||
|
if (data.ChangeTypes.includes('temporary-transfer')) {
|
||||||
|
return temporaryTransferError(data);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Временный перевод: если обе даты заданы — окончание не раньше начала и не дольше
|
||||||
|
* 3 месяцев. Календарь это уже ограничивает; здесь — на случай, когда дату начала
|
||||||
|
* сдвинули после выбора окончания.
|
||||||
|
*/
|
||||||
|
function temporaryTransferError(data: IFormData): string | undefined {
|
||||||
|
const start = data.TemporaryTransferStartDate;
|
||||||
|
const end = data.TemporaryTransferEndDate;
|
||||||
|
if (!start || !end) return undefined;
|
||||||
|
const maxEnd = maxTransferEndDate(start);
|
||||||
|
if (end < start || (maxEnd && end > maxEnd)) return TEMPORARY_TRANSFER_PERIOD_ERROR;
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -155,7 +155,17 @@ export async function loadContractChangeRequest(itemId: number, listId?: string)
|
|||||||
'EmployerChangeNewSalary',
|
'EmployerChangeNewSalary',
|
||||||
'EmployerChangeFixedPercent',
|
'EmployerChangeFixedPercent',
|
||||||
'EmployerChangeSalaryDate',
|
'EmployerChangeSalaryDate',
|
||||||
'EmployerChangeJustification'
|
'EmployerChangeJustification',
|
||||||
|
'TemporaryTransferFromProject',
|
||||||
|
'TemporaryTransferFromProjectId',
|
||||||
|
'TemporaryTransferFromWork',
|
||||||
|
'TemporaryTransferToProject',
|
||||||
|
'TemporaryTransferToProjectId',
|
||||||
|
'TemporaryTransferToWork',
|
||||||
|
'TemporaryTransferStartDate',
|
||||||
|
'TemporaryTransferEndDate',
|
||||||
|
'TemporaryTransferReason',
|
||||||
|
'TemporaryTransferJustification'
|
||||||
)
|
)
|
||||||
.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;
|
||||||
@@ -233,7 +243,17 @@ export async function loadContractChangeRequest(itemId: number, listId?: string)
|
|||||||
// Оклад не меняли (доли не сохранялись) — доля по умолчанию из ТЗ, 75 %.
|
// Оклад не меняли (доли не сохранялись) — доля по умолчанию из ТЗ, 75 %.
|
||||||
EmployerChangeFixedPercent: numberText(item.EmployerChangeFixedPercent) || DEFAULT_FIXED_PERCENT,
|
EmployerChangeFixedPercent: numberText(item.EmployerChangeFixedPercent) || DEFAULT_FIXED_PERCENT,
|
||||||
EmployerChangeSalaryDate: spDateOnlyToIso(text(item.EmployerChangeSalaryDate)),
|
EmployerChangeSalaryDate: spDateOnlyToIso(text(item.EmployerChangeSalaryDate)),
|
||||||
EmployerChangeJustification: text(item.EmployerChangeJustification)
|
EmployerChangeJustification: text(item.EmployerChangeJustification),
|
||||||
|
TemporaryTransferFromProject: text(item.TemporaryTransferFromProject),
|
||||||
|
TemporaryTransferFromProjectId: text(item.TemporaryTransferFromProjectId),
|
||||||
|
TemporaryTransferFromWork: text(item.TemporaryTransferFromWork),
|
||||||
|
TemporaryTransferToProject: text(item.TemporaryTransferToProject),
|
||||||
|
TemporaryTransferToProjectId: text(item.TemporaryTransferToProjectId),
|
||||||
|
TemporaryTransferToWork: text(item.TemporaryTransferToWork),
|
||||||
|
TemporaryTransferStartDate: spDateOnlyToIso(text(item.TemporaryTransferStartDate)),
|
||||||
|
TemporaryTransferEndDate: spDateOnlyToIso(text(item.TemporaryTransferEndDate)),
|
||||||
|
TemporaryTransferReason: text(item.TemporaryTransferReason),
|
||||||
|
TemporaryTransferJustification: text(item.TemporaryTransferJustification)
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -185,6 +185,22 @@ export interface IFormData {
|
|||||||
EmployerChangeSalaryDate: string;
|
EmployerChangeSalaryDate: string;
|
||||||
/** Обоснование текстом — или документом (одно из двух обязательно при отправке). */
|
/** Обоснование текстом — или документом (одно из двух обязательно при отправке). */
|
||||||
EmployerChangeJustification: string;
|
EmployerChangeJustification: string;
|
||||||
|
|
||||||
|
// ── Вид «Временный перевод» (byType/TemporaryTransfer). Только работодатель, поля необязательные.
|
||||||
|
/** Откуда: проект (справочник /Project) + его oid, работа — текстом. */
|
||||||
|
TemporaryTransferFromProject: string;
|
||||||
|
TemporaryTransferFromProjectId: string;
|
||||||
|
TemporaryTransferFromWork: string;
|
||||||
|
/** Куда: так же. */
|
||||||
|
TemporaryTransferToProject: string;
|
||||||
|
TemporaryTransferToProjectId: string;
|
||||||
|
TemporaryTransferToWork: string;
|
||||||
|
/** Период, ISO YYYY-MM-DD; не дольше 3 месяцев (maxTransferEndDate). */
|
||||||
|
TemporaryTransferStartDate: string;
|
||||||
|
TemporaryTransferEndDate: string;
|
||||||
|
/** Основание — TEMPORARY_TRANSFER_REASONS. */
|
||||||
|
TemporaryTransferReason: string;
|
||||||
|
TemporaryTransferJustification: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Обновление одного поля формы с сохранением типа значения. */
|
/** Обновление одного поля формы с сохранением типа значения. */
|
||||||
@@ -268,7 +284,17 @@ export const EMPTY_DATA: IFormData = {
|
|||||||
EmployerChangeNewSalary: '',
|
EmployerChangeNewSalary: '',
|
||||||
EmployerChangeFixedPercent: DEFAULT_FIXED_PERCENT,
|
EmployerChangeFixedPercent: DEFAULT_FIXED_PERCENT,
|
||||||
EmployerChangeSalaryDate: '',
|
EmployerChangeSalaryDate: '',
|
||||||
EmployerChangeJustification: ''
|
EmployerChangeJustification: '',
|
||||||
|
TemporaryTransferFromProject: '',
|
||||||
|
TemporaryTransferFromProjectId: '',
|
||||||
|
TemporaryTransferFromWork: '',
|
||||||
|
TemporaryTransferToProject: '',
|
||||||
|
TemporaryTransferToProjectId: '',
|
||||||
|
TemporaryTransferToWork: '',
|
||||||
|
TemporaryTransferStartDate: '',
|
||||||
|
TemporaryTransferEndDate: '',
|
||||||
|
TemporaryTransferReason: '',
|
||||||
|
TemporaryTransferJustification: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Плейсхолдер для поля выбора сотрудника. */
|
/** Плейсхолдер для поля выбора сотрудника. */
|
||||||
|
|||||||
@@ -132,6 +132,17 @@
|
|||||||
{ "internalName": "EmployerChangeFixedPart", "title": "Инициатива работодателя: постоянная часть, ₸", "type": "Number", "decimals": 0 },
|
{ "internalName": "EmployerChangeFixedPart", "title": "Инициатива работодателя: постоянная часть, ₸", "type": "Number", "decimals": 0 },
|
||||||
{ "internalName": "EmployerChangeVariablePart", "title": "Инициатива работодателя: переменная часть, ₸", "type": "Number", "decimals": 0 },
|
{ "internalName": "EmployerChangeVariablePart", "title": "Инициатива работодателя: переменная часть, ₸", "type": "Number", "decimals": 0 },
|
||||||
{ "internalName": "EmployerChangeSalaryDate", "title": "Инициатива работодателя: дата изменения оклада", "type": "DateTime", "dateOnly": true },
|
{ "internalName": "EmployerChangeSalaryDate", "title": "Инициатива работодателя: дата изменения оклада", "type": "DateTime", "dateOnly": true },
|
||||||
{ "internalName": "EmployerChangeJustification", "title": "Инициатива работодателя: обоснование", "type": "Note" }
|
{ "internalName": "EmployerChangeJustification", "title": "Инициатива работодателя: обоснование", "type": "Note" },
|
||||||
|
|
||||||
|
{ "internalName": "TemporaryTransferFromProject", "title": "Временный перевод: откуда — проект", "type": "Text" },
|
||||||
|
{ "internalName": "TemporaryTransferFromProjectId", "title": "Временный перевод: откуда — id проекта", "type": "Text" },
|
||||||
|
{ "internalName": "TemporaryTransferFromWork", "title": "Временный перевод: откуда — работа", "type": "Text" },
|
||||||
|
{ "internalName": "TemporaryTransferToProject", "title": "Временный перевод: куда — проект", "type": "Text" },
|
||||||
|
{ "internalName": "TemporaryTransferToProjectId", "title": "Временный перевод: куда — id проекта", "type": "Text" },
|
||||||
|
{ "internalName": "TemporaryTransferToWork", "title": "Временный перевод: куда — работа", "type": "Text" },
|
||||||
|
{ "internalName": "TemporaryTransferStartDate", "title": "Временный перевод: дата начала", "type": "DateTime", "dateOnly": true },
|
||||||
|
{ "internalName": "TemporaryTransferEndDate", "title": "Временный перевод: дата окончания", "type": "DateTime", "dateOnly": true },
|
||||||
|
{ "internalName": "TemporaryTransferReason", "title": "Временный перевод: основание", "type": "Choice", "choices": ["Производственная необходимость", "Простой"] },
|
||||||
|
{ "internalName": "TemporaryTransferJustification", "title": "Временный перевод: обоснование", "type": "Note" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -276,7 +276,19 @@ function buildItem(data: IFormData, status: string, receivingHeadId: number | un
|
|||||||
FunctionChangeDate: data.FunctionChangeDate,
|
FunctionChangeDate: data.FunctionChangeDate,
|
||||||
FunctionChangeReason: data.FunctionChangeReason
|
FunctionChangeReason: data.FunctionChangeReason
|
||||||
}),
|
}),
|
||||||
...employerInitiativeColumns(data)
|
...employerInitiativeColumns(data),
|
||||||
|
...keepOrClear(data.ChangeTypes.includes('temporary-transfer'), {
|
||||||
|
TemporaryTransferFromProject: data.TemporaryTransferFromProject.trim(),
|
||||||
|
TemporaryTransferFromProjectId: data.TemporaryTransferFromProjectId,
|
||||||
|
TemporaryTransferFromWork: data.TemporaryTransferFromWork.trim(),
|
||||||
|
TemporaryTransferToProject: data.TemporaryTransferToProject.trim(),
|
||||||
|
TemporaryTransferToProjectId: data.TemporaryTransferToProjectId,
|
||||||
|
TemporaryTransferToWork: data.TemporaryTransferToWork.trim(),
|
||||||
|
TemporaryTransferStartDate: data.TemporaryTransferStartDate,
|
||||||
|
TemporaryTransferEndDate: data.TemporaryTransferEndDate,
|
||||||
|
TemporaryTransferReason: data.TemporaryTransferReason,
|
||||||
|
TemporaryTransferJustification: data.TemporaryTransferJustification
|
||||||
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user