Перейти к основному содержимому

Шаг 4: Валидация занятости

Валидация полей занятости и дохода с условными правилами основанными на статусе занятости.

Что мы валидируем

Шаг 4 содержит поля, связанные с занятостью, с условными требованиями:

ПолеПравила валидации
employmentStatusОбязательно
monthlyIncomeОбязательно, min 10 000
additionalIncomeОпционально, min 0
Для работающих
companyNameОбязательно при работе
companyAddressОбязательно при работе
positionОбязательно при работе
workExperienceTotalОпционально, min 0
workExperienceCurrentОбязательно при работе, min 3 месяца
Для самозанятых
businessTypeОбязательно при самозанятости
businessInnОбязательно при самозанятости, 10 или 12 цифр
businessAddressОбязательно при самозанятости
businessExperienceОбязательно при самозанятости, min 6 месяцев

Создание файла валидатора

Создайте файл валидатора для Шага 4:

touch src/schemas/validators/employment.ts

Реализация

Базовые поля занятости

Начните с требуемых полей, которые применяются ко всем статусам занятости:

src/schemas/validators/employment.ts
import { required, min, pattern, applyWhen } from '@reformer/core/validators';
import type { ValidationSchemaFn, FieldPath } from '@reformer/core';
import type { CreditApplicationForm } from '@/types';

/**
* Валидация для Шага 4: Занятость
*
* Валидирует:
* - Статус занятости (требуется для всех)
* - Поля дохода (требуются для всех)
* - Поля занятости (условно требуются)
* - Поля самозанятости (условно требуются)
*/
export const employmentValidation: ValidationSchemaFn<CreditApplicationForm> = (
path: FieldPath<CreditApplicationForm>
) => {
// ==========================================
// Базовые поля занятости
// ==========================================

// Статус занятости (всегда требуется)
required(path.employmentStatus, { message: 'Статус занятости обязателен' });

// Ежемесячный доход (всегда требуется, минимальный порог)
required(path.monthlyIncome, { message: 'Ежемесячный доход обязателен' });
min(path.monthlyIncome, 10000, {
message: 'Минимальный ежемесячный доход: 10 000',
});

// Дополнительный доход (опционален, но не может быть отрицательным если указан)
min(path.additionalIncome, 0, {
message: 'Дополнительный доход не может быть отрицательным',
});
};

Условная валидация: Работающие

Добавьте валидацию для работающих людей:

src/schemas/validators/employment.ts
export const employmentValidation: ValidationSchemaFn<CreditApplicationForm> = (path) => {
// ... предыдущая валидация ...

// ==========================================
// Условно: Поля работающих
// ==========================================

applyWhen(
path.employmentStatus,
(status) => status === 'employed',
(p) => {
required(p.companyName, { message: 'Название компании обязательно' });
required(p.companyAddress, { message: 'Адрес компании обязателен' });
required(p.position, { message: 'Должность обязательна' });

required(p.workExperienceCurrent, { message: 'Стаж работы на текущем месте обязателен' });
min(p.workExperienceCurrent, 3, {
message: 'Минимум 3 месяца опыта на текущем месте требуется',
});

min(p.workExperienceTotal, 0, {
message: 'Общий стаж не может быть отрицательным',
});
}
);
};

Условная валидация: Самозанятые

Добавьте валидацию для самозанятых людей:

src/schemas/validators/employment.ts
export const employmentValidation: ValidationSchemaFn<CreditApplicationForm> = (path) => {
// ... предыдущая валидация ...

// ==========================================
// Условно: Поля самозанятых
// ==========================================

applyWhen(
path.employmentStatus,
(status) => status === 'selfEmployed',
(p) => {
required(p.businessType, { message: 'Тип бизнеса обязателен' });
required(p.businessInn, { message: 'ИНН бизнеса обязателен' });
}
);

// Валидация паттерна для ИНН бизнеса
pattern(path.businessInn, /^\d{10}$|^\d{12}$/, {
message: 'ИНН бизнеса должен быть 10 или 12 цифр',
});
};

Полный код

Вот полный валидатор для Шага 4:

src/schemas/validators/employment.ts
import { required, min, pattern, applyWhen } from '@reformer/core/validators';
import type { ValidationSchemaFn, FieldPath } from '@reformer/core';
import type { CreditApplicationForm } from '@/types';

/**
* Валидация для Шага 4: Занятость
*
* Валидирует:
* - Статус занятости (требуется для всех)
* - Поля дохода (требуются для всех)
* - Поля занятости (условно требуются)
* - Поля самозанятости (условно требуются)
*/
export const employmentValidation: ValidationSchemaFn<CreditApplicationForm> = (
path: FieldPath<CreditApplicationForm>
) => {
// ==========================================
// Базовые поля занятости
// ==========================================

required(path.employmentStatus, { message: 'Статус занятости обязателен' });

required(path.monthlyIncome, { message: 'Ежемесячный доход обязателен' });
min(path.monthlyIncome, 10000, {
message: 'Минимальный ежемесячный доход: 10 000',
});

min(path.additionalIncome, 0, {
message: 'Дополнительный доход не может быть отрицательным',
});

// ==========================================
// Условно: Поля работающих
// ==========================================

applyWhen(
path.employmentStatus,
(status) => status === 'employed',
(p) => {
required(p.companyName, { message: 'Название компании обязательно' });
required(p.companyAddress, { message: 'Адрес компании обязателен' });
required(p.position, { message: 'Должность обязательна' });

required(p.workExperienceCurrent, { message: 'Стаж работы на текущем месте обязателен' });
min(p.workExperienceCurrent, 3, {
message: 'Минимум 3 месяца опыта на текущем месте требуется',
});

min(p.workExperienceTotal, 0, {
message: 'Общий стаж не может быть отрицательным',
});
}
);

// ==========================================
// Условно: Поля самозанятых
// ==========================================

applyWhen(
path.employmentStatus,
(status) => status === 'selfEmployed',
(p) => {
required(p.businessType, { message: 'Тип бизнеса обязателен' });
required(p.businessInn, { message: 'ИНН бизнеса обязателен' });
}
);

pattern(path.businessInn, /^\d{10}$|^\d{12}$/, {
message: 'ИНН бизнеса должен быть 10 или 12 цифр',
});
};

Как это работает

Всегда требуемые поля

Эти поля требуются независимо от статуса занятости:

required(path.employmentStatus, { message: 'Статус занятости обязателен' });
required(path.monthlyIncome, { message: 'Ежемесячный доход обязателен' });
min(path.monthlyIncome, 10000, { message: 'Минимальный ежемесячный доход: 10 000' });

Условно требуемые поля

Эти поля требуются только для определённых статусов занятости:

// Применяется только при работе
applyWhen(
path.employmentStatus,
(status) => status === 'employed',
(p) => {
required(p.companyName, { message: 'Название компании обязательно' });
min(p.workExperienceCurrent, 3, {
message: 'Минимум 3 месяца опыта на текущем месте требуется',
});
}
);

// Применяется только при самозанятости
applyWhen(
path.employmentStatus,
(status) => status === 'selfEmployed',
(p) => {
required(p.businessType, { message: 'Тип бизнеса обязателен' });
}
);

Интеграция с Behaviors

Из раздела Behaviors, у нас есть:

// Behavior: Показывать поля компании только при работе
enableWhen(path.companyName, path.employmentStatus, (status) => status === 'employed');
enableWhen(path.companyAddress, path.employmentStatus, (status) => status === 'employed');

// Валидация: Требовать поля компании только при работе
applyWhen(
path.employmentStatus,
(status) => status === 'employed',
(p) => {
required(p.companyName, { message: 'Название компании обязательно' });
}
);

Идеальное выравнивание! Поля скрываются/показываются и требуются/опциональны в синхронизации.

Тестирование валидации

Протестируйте эти сценарии:

Базовые поля (все статусы)

  • Оставьте статус занятости пусто → Ошибка показана
  • Оставьте ежемесячный доход пусто → Ошибка показана
  • Введите ежемесячный доход < 10 000 → Ошибка показана
  • Введите ежемесячный доход >= 10 000 → Ошибки нет
  • Введите отрицательный дополнительный доход → Ошибка показана
  • Оставьте дополнительный доход пусто → Ошибки нет (опционально)

Статус работающих

  • Выберите "работающих" → Поля компании становятся требуемыми
  • Оставьте название компании пусто → Ошибка показана
  • Оставьте адрес компании пусто → Ошибка показана
  • Оставьте должность пусто → Ошибка показана
  • Оставьте стаж работы пусто → Ошибка показана
  • Введите стаж < 3 месяцев → Ошибка показана
  • Введите стаж >= 3 месяцев → Ошибки нет

Статус самозанятых

  • Выберите "самозанятых" → Поля бизнеса становятся требуемыми
  • Оставьте тип бизнеса пусто → Ошибка показана
  • Оставьте ИНН бизнеса пусто → Ошибка показана
  • Введите ИНН бизнеса с 9 цифрами → Ошибка показана
  • Введите ИНН бизнеса с 10 цифрами → Ошибки нет
  • Введите ИНН бизнеса с 12 цифрами → Ошибки нет
  • Оставьте адрес бизнеса пусто → Ошибка показана
  • Оставьте опыт бизнеса пусто → Ошибка показана
  • Введите опыт бизнеса < 6 месяцев → Ошибка показана
  • Введите опыт бизнеса >= 6 месяцев → Ошибки нет

Статус без работы/другой

  • Выберите "без работы" → Только базовые поля требуются
  • Поля компании не требуются
  • Поля бизнеса не требуются
  • Ежемесячный доход все ещё требуется

Переключение статуса занятости

  • Заполните поля работающих → Переключитесь на "самозанятых" → Ошибки работающих исчезают
  • Заполните поля бизнеса → Переключитесь на "работающих" → Ошибки бизнеса исчезают
  • Переключитесь на "без работы" → Все условные ошибки исчезают

Значения статуса занятости

Типичные значения статуса занятости:

type EmploymentStatus =
| 'employed' // Полная занятость
| 'selfEmployed' // Самозанятый / предприниматель
| 'unemployed' // Без работы
| 'retired' // На пенсии
| 'student'; // Студент

Каждый статус может иметь различные требования валидации.

Ключевые выводы

  1. Всегда требуется - Некоторые поля требуются независимо от статуса
  2. Условно требуется - Используйте requiredWhen() для полей, специфических для статуса
  3. Условный min - Используйте minWhen() для условных пороговых значений
  4. Работает с Behaviors - Скрытые поля пропускают валидацию
  5. Бизнес-правила - Различные минимальные пороги (3 месяца работа, 6 месяцев бизнес)

Распространённые паттерны

Условная валидация с applyWhen

applyWhen(
path.employmentStatus,
(status) => status === 'employed',
(p) => {
required(p.field, { message: 'Поле требуется' });
min(p.field, minimumValue, { message: 'Минимальное значение не достигнуто' });
}
);

Неотрицательное опциональное поле

// Нет required(), просто min(0) чтобы предотвратить отрицательные значения
min(path.additionalIncome, 0, {
message: 'Не может быть отрицательным',
});

Что дальше?

В следующем разделе мы добавим валидацию для Шага 5: Дополнительная информация, включая:

  • Валидация массивов (имущество, существующие кредиты, созаёмщики)
  • Ограничения длины массива (min/max)
  • Валидация отдельных элементов массива
  • Валидация вложенных объектов в массивах
  • Условные требования массивов

Это продемонстрирует мощные возможности валидации массивов в ReFormer!