feat: трудовой отпуск — не более 24 дней в одной заявке

Требование HR: ограничение на поле «Количество дней» и проверка при
отправке с понятным сообщением.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Тимур Городецкий
2026-10-12 13:59:58 +05:00
parent 41cce93b41
commit 4b05da5469
3 changed files with 16 additions and 4 deletions
@@ -44,7 +44,7 @@ import {
dataListTitle,
dataListTitleForListId
} from '../../shared/listTitles';
import { FORM_ID, IFormData, EMPTY_DATA, SetField, MIN_NOTICE_DAYS } from './model';
import { FORM_ID, IFormData, EMPTY_DATA, SetField, MIN_NOTICE_DAYS, MAX_DAYS } from './model';
import { saveVacationRequest, vacationApprovalService } from './submit';
import { loadVacationRequest } from './load';
import { RequestSection } from './sections/RequestSection';
@@ -202,17 +202,20 @@ const VacationForm: React.FC<IRequestFormProps> = ({
// выбрать более раннюю дату, но проверяем и здесь на случай обхода UI.
const minStartDate = isoDatePlusDays(MIN_NOTICE_DAYS);
const isStartDateValid = !data.StartDate || data.StartDate >= minStartDate;
const isDaysCountTooLarge = Number.isFinite(daysNum) && daysNum > MAX_DAYS;
const isValid = !!(
data.Employee &&
data.StartDate &&
isStartDateValid &&
Number.isFinite(daysNum) &&
daysNum > 0
daysNum > 0 &&
!isDaysCountTooLarge
);
const validate = (): string | undefined => {
if (data.StartDate && !isStartDateValid) {
return `Заявку на отпуск нужно подать не позднее чем за ${MIN_NOTICE_DAYS} дней до даты начала — минимально доступная дата: ${formatIsoDate(minStartDate)}.`;
}
if (isDaysCountTooLarge) return `Отпуск — не более ${MAX_DAYS} календарных дней в одной заявке.`;
if (!isValid) return 'Заполните обязательные поля: сотрудник, дата начала, количество дней (> 0).';
if (requestTypeIdState.error) return `Не удалось определить тип заявки: ${requestTypeIdState.error}`;
if (!requestTypeId) return 'Тип заявки ещё определяется — подождите немного и повторите.';
@@ -80,5 +80,7 @@ export const EMPTY_DATA: IFormData = {
export const EMPLOYEE_PLACEHOLDER = 'Начните вводить Ф.И.О. сотрудника…';
/** Заявку нужно подавать не позднее чем за столько дней до даты начала отпуска. */
export const MIN_NOTICE_DAYS = 7;
/** Ежегодный трудовой отпуск — не более стольких календарных дней в одной заявке (требование HR). */
export const MAX_DAYS = 24;
/** Плейсхолдер для поля выбора замещающего. */
export const SUBSTITUTE_PLACEHOLDER = 'Начните вводить Ф.И.О. замещающего…';
@@ -1,7 +1,7 @@
import * as React from 'react';
import { FormGrid, FormField, TextInput, PeopleField, OrganizationTable } from '../../../shared/ui';
import { ProjectPicker } from '../../../shared/dictionary';
import { ISectionProps, EMPLOYEE_PLACEHOLDER, SUBSTITUTE_PLACEHOLDER, MIN_NOTICE_DAYS } from '../model';
import { ISectionProps, EMPLOYEE_PLACEHOLDER, SUBSTITUTE_PLACEHOLDER, MIN_NOTICE_DAYS, MAX_DAYS } from '../model';
import { isoDatePlusDays } from '../../../shared/dateUtils';
/** Единственная секция формы «Заявка на отпуск»: сотрудник (должность и подразделение подтягиваются из справочника), даты. */
@@ -38,7 +38,14 @@ export const RequestSection: React.FC<ISectionProps> = ({ data, set, context, on
/>
</FormField>
<FormField label="Количество дней" htmlFor="DaysCount" required>
<TextInput id="DaysCount" type="number" value={data.DaysCount} onChange={(v) => set('DaysCount', v)} />
<TextInput
id="DaysCount"
type="number"
value={data.DaysCount}
onChange={(v) => set('DaysCount', v)}
min={1}
max={MAX_DAYS}
/>
</FormField>
<FormField label="Лицо, замещающее на время отсутствия" htmlFor="Substitute" full>
<PeopleField