добавлен новый тип менеджера и условие новое
Tests / frontend-test (pull_request) Successful in 43s
Tests / backend-test (pull_request) Successful in 4m0s

This commit is contained in:
gorbenko.pavel
2026-08-13 12:16:22 +05:00
parent d076f4e98d
commit f4542c7b39
7 changed files with 74 additions and 13 deletions
@@ -76,6 +76,18 @@ const OPERATION_OPTIONS = Object.values(ComparisonOperator)
.filter(v => v !== ComparisonOperator.None)
.map(v => ({ value: v, label: comparisonOperatorLabels[v] }));
/**
* Служебное поле условия, не привязанное к списку SharePoint — вычисляется на бэкенде
* из должности инициатора (см. TemplateRouteService.ResolveAsync), а не из данных заявки.
* Поэтому оно доступно всегда, даже если у типа заявки не настроен list_name.
*/
const IS_MANAGER_FIELD = 'IsManager';
const SYNTHETIC_FIELD_OPTIONS = [{ value: IS_MANAGER_FIELD, label: texts.routeSteps.conditionFieldIsManagerLabel }];
const IS_MANAGER_VALUE_OPTIONS = [
{ value: 'true', label: texts.common.yes },
{ value: 'false', label: texts.common.no }
];
function toFormState(editing: TemplateRouteStep | undefined, nextOrderNo: number): IFormState {
if (!editing) {
return {
@@ -132,18 +144,23 @@ export const RouteStepFormModal: React.FC<IRouteStepFormModalProps> = ({
const sharePointFields = useSharePointFields(sharePoint, requestType?.siteUrl ?? undefined, requestType?.listId ?? undefined);
const fieldNameOptions = React.useMemo(() => {
const options = sharePointFields.items.map(f => ({ value: f.internalName, label: f.title }));
const listOptions = sharePointFields.items.map(f => ({ value: f.internalName, label: f.title }));
const options = [...SYNTHETIC_FIELD_OPTIONS, ...listOptions];
if (form.fieldName && !options.some(o => o.value === form.fieldName)) {
options.unshift({ value: form.fieldName, label: form.fieldName });
options.push({ value: form.fieldName, label: form.fieldName });
}
return options;
}, [sharePointFields.items, form.fieldName]);
const isManagerField = form.fieldName === IS_MANAGER_FIELD;
// Тип столбца известен, только если он есть среди подгруженных полей списка —
// для значения, введённого до появления поля в списке (или после его удаления),
// ограничения ниже не действуют, чтобы не блокировать уже сохранённые данные.
// IsManager — служебное булево поле, а не колонка списка, поэтому для него
// числовые ограничения не действуют в любом случае.
const selectedField = sharePointFields.items.find(f => f.internalName === form.fieldName);
const isNumericField = selectedField ? isNumericFieldType(selectedField.typeAsString) : true;
const isNumericField = isManagerField ? false : selectedField ? isNumericFieldType(selectedField.typeAsString) : true;
const operationOptions = isNumericField
? OPERATION_OPTIONS
@@ -158,6 +175,15 @@ export const RouteStepFormModal: React.FC<IRouteStepFormModalProps> = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isNumericField]);
// IsManager — булево поле: значение должно быть одним из IS_MANAGER_VALUE_OPTIONS,
// а не произвольным текстом, который пользователь мог набрать для другого field_name.
React.useEffect(() => {
if (isManagerField && form.value !== 'true' && form.value !== 'false') {
setForm(f => ({ ...f, value: 'true' }));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isManagerField]);
const submitForm = (): void => {
const orderNo = Number(form.orderNo);
if (!orderNo || orderNo < 1) {
@@ -326,12 +352,11 @@ export const RouteStepFormModal: React.FC<IRouteStepFormModalProps> = ({
<Checkbox
label={texts.routeSteps.conditionCheckbox}
checked={form.hasCondition}
disabled={!hasListId}
onChange={v => setForm({ ...form, hasCondition: v })}
/>
</Field>
{form.hasCondition && hasListId && (
{form.hasCondition && (
<>
<Field
label={texts.routeSteps.conditionFieldNameLabel}
@@ -361,12 +386,20 @@ export const RouteStepFormModal: React.FC<IRouteStepFormModalProps> = ({
/>
</Field>
<Field label={texts.routeSteps.conditionValueLabel} required>
<TextInput
type={isNumericField ? 'number' : 'text'}
value={form.value}
onChange={e => setForm({ ...form, value: e.target.value })}
placeholder={texts.routeSteps.conditionValuePlaceholder}
/>
{isManagerField ? (
<Select
options={IS_MANAGER_VALUE_OPTIONS}
value={form.value}
onChange={e => setForm({ ...form, value: e.target.value })}
/>
) : (
<TextInput
type={isNumericField ? 'number' : 'text'}
value={form.value}
onChange={e => setForm({ ...form, value: e.target.value })}
placeholder={texts.routeSteps.conditionValuePlaceholder}
/>
)}
</Field>
</>
)}
@@ -22,6 +22,7 @@ export enum HeadType {
Chief = 'Chief',
DirectionHead = 'DirectionHead',
DirectorTansu = 'DirectorTansu',
ManagingDirectorTansu = 'ManagingDirectorTansu',
// Резолвится по departmentId из запроса на resolve (см. ResolveTemplateRouteQuery),
// а не по цепочке подчинённости инициатора, как остальные типы, — поэтому на
// экране «Проверка маршрута» появляется отдельное поле выбора отдела.
+4 -1
View File
@@ -31,6 +31,7 @@ export const headTypeLabels: Record<HeadType, string> = {
[HeadType.Chief]: 'Непосредственный руководитель',
[HeadType.DirectionHead]: 'Руководитель направления',
[HeadType.DirectorTansu]: 'Директор Tansu',
[HeadType.ManagingDirectorTansu]: 'Управляющий директор Tansu',
[HeadType.DepartmentHead]: 'Руководитель указанного отдела'
};
@@ -233,7 +234,9 @@ export const texts = {
askOverdueReasonHint: '«Не задано» — берётся значение типа заявки.',
conditionLabel: 'Условие видимости шага',
conditionCheckbox: 'Показывать шаг только при выполнении условия',
conditionDisabledHint: 'Недоступно: у типа заявки не указан список SharePoint (list_name).',
conditionDisabledHint:
'У типа заявки не указан список SharePoint (list_name) — доступно только служебное поле «Инициатор — руководитель».',
conditionFieldIsManagerLabel: 'Инициатор — руководитель (IsManager)',
conditionFieldNameLabel: 'Поле списка',
conditionFieldNamePlaceholder: 'Выберите столбец списка',
conditionFieldNameLoading: 'Загрузка столбцов списка...',