fix: заявки рекрутинга не давали отправить без выьбранного проекта, но пропускали пустую матрицу согласования
Tests / frontend-test (push) Successful in 1m23s

This commit is contained in:
Тимур Городецкий
2026-09-21 09:29:34 +05:00
parent 070c3ad752
commit 25c1188c32
11 changed files with 342 additions and 10 deletions
@@ -207,9 +207,23 @@ const RecruitingSelectionForm: React.FC<IRequestFormProps> = ({
(login) => approval.getJobTitle(login)
);
const isValid = !!(data.Responsible && data.DepartmentName.trim() && data.Position.trim() && data.Residence);
// ObjectProjectId обязателен (а не просто текст ObjectProject): маршрут
// согласования резолвится ПО ПРОЕКТУ (ByProject, см. projectId выше), и без
// выбранного из списка варианта (id) approval-route вернёт 404 — форма
// должна не пускать на отправку раньше, чем выяснится, что маршрут вообще
// некому строить (баг: раньше это не проверялось, и заявка молча уходила
// на согласование с пустой матрицей).
const isValid = !!(
data.Responsible &&
data.DepartmentName.trim() &&
data.Position.trim() &&
data.Residence &&
data.ObjectProjectId
);
const validate = (): string | undefined => {
if (!isValid) return 'Заполните обязательные поля: ответственный за подачу, название отдела, должность, проживание.';
if (!isValid) {
return 'Заполните обязательные поля: ответственный за подачу, название отдела, должность, проживание, объект/проект (выберите вариант из списка).';
}
if (requestTypeIdState.error) return `Не удалось определить тип заявки: ${requestTypeIdState.error}`;
if (!requestTypeId) return 'Тип заявки ещё определяется — подождите немного и повторите.';
return undefined;
@@ -295,6 +309,15 @@ const RecruitingSelectionForm: React.FC<IRequestFormProps> = ({
if (matrix.error) {
return `Не удалось определить маршрут согласования: ${matrix.error}. Попробуйте ещё раз или обратитесь к администратору.`;
}
if (matrix.loading) {
return 'Маршрут согласования ещё формируется — подождите немного и повторите.';
}
// Матрица пуста (не только «есть шаг без человека») — раньше это молча
// пропускалось (findUnassignedStep на пустом массиве ошибки не даёт), и
// заявка уходила на согласование без единого согласующего.
if (matrix.rows.length === 0) {
return 'Маршрут согласования не найден для выбранного объекта/проекта — обратитесь к администратору.';
}
const unassignedError = unassignedStepMessage(
matrix.rows,
(r) => `Шаг ${r.order} маршрута согласования («${r.position || 'без должности'}») без назначенного человека — выберите согласующего или удалите шаг.`
@@ -76,7 +76,7 @@ export const ApplicationSection: React.FC<ISectionProps> = ({ data, set, context
placeholder="Начните вводить название организации…"
/>
</FormField>
<FormField label="1.7 Объект/Проект" htmlFor="ObjectProject" full>
<FormField label="1.7 Объект/Проект" htmlFor="ObjectProject" required full>
<ProjectPicker
context={context}
id="ObjectProject"
@@ -1,5 +1,6 @@
import * as React from 'react';
import { CollapseSection, FormField, TextInput, TextArea, Select } from '../../../shared/ui';
import { PositionPicker } from '../../../shared/dictionary';
import { LanguagesField } from './LanguagesField';
import { MultiChoiceField } from './MultiChoiceField';
import {
@@ -12,10 +13,16 @@ import {
OTHER_VALUE
} from '../model';
export const VacancySection: React.FC<ISectionProps> = ({ data, set }) => (
export const VacancySection: React.FC<ISectionProps> = ({ data, set, context }) => (
<CollapseSection title="3. Вакансия" icon="document">
<FormField label="3.1 Должность" htmlFor="Position" required full>
<TextInput id="Position" value={data.Position} onChange={(v) => set('Position', v)} />
<PositionPicker
context={context}
id="Position"
value={data.Position}
onChange={(v) => set('Position', v)}
placeholder="Начните вводить название должности…"
/>
</FormField>
<FormField label="3.2 Характер занятости" htmlFor="EmploymentType">
<Select