Auto approve (#21)
Tests / frontend-test (push) Successful in 46s
Tests / backend-test (push) Successful in 3m39s

Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz>
Reviewed-on: http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/pulls/21
This commit was merged in pull request #21.
This commit is contained in:
2026-09-14 09:01:56 +00:00
parent e2b6227015
commit 2b8fe77fac
24 changed files with 1443 additions and 29 deletions
@@ -47,7 +47,8 @@ function toStepInput(step: TemplateRouteStep): CreateTemplateRouteStepInput {
conditions: step.conditions,
conditionsLogic: step.conditionsLogic,
slaHours: step.slaHours,
askOverdueReason: step.askOverdueReason
askOverdueReason: step.askOverdueReason,
autoApprove: step.autoApprove
};
}
@@ -77,6 +77,7 @@ interface IFormState {
/** Пусто — наследовать SLA от типа заявки; «0» — напоминать сразу. Это разные вещи. */
slaHours: string;
askOverdueReason: TriState;
autoApprove: boolean;
}
const EMPTY_CONDITION: RouteStepCondition = { fieldName: '', operation: ComparisonOperator.GreaterThan, value: '' };
@@ -133,7 +134,8 @@ function toFormState(editing: TemplateRouteStep | undefined, nextOrderNo: number
conditions: [EMPTY_CONDITION],
conditionsLogic: ConditionsLogic.And,
slaHours: '',
askOverdueReason: ''
askOverdueReason: '',
autoApprove: false
};
}
return {
@@ -149,7 +151,8 @@ function toFormState(editing: TemplateRouteStep | undefined, nextOrderNo: number
conditions: editing.conditions.length > 0 ? editing.conditions : [EMPTY_CONDITION],
conditionsLogic: editing.conditionsLogic,
slaHours: slaHoursToInput(editing.slaHours),
askOverdueReason: boolToTriState(editing.askOverdueReason)
askOverdueReason: boolToTriState(editing.askOverdueReason),
autoApprove: editing.autoApprove
};
}
@@ -365,6 +368,14 @@ export const RouteStepFormModal: React.FC<IRouteStepFormModalProps> = ({
submit.fail(texts.routeSteps.validationSlaHours);
return;
}
// Автосогласование отсчитывается от SLA — без него фоновая задача шаг просто
// пропустит (см. AutoApproveService.DeadlineOf), и настройка тихо не сработала
// бы. Пустое поле здесь не значит «нет SLA»: оно значит «взять у типа заявки»,
// поэтому ошибка только когда SLA нет и там.
if (form.autoApprove && parseSlaHours(form.slaHours) === null && requestType?.slaHours === null) {
submit.fail(texts.routeSteps.validationAutoApproveWithoutSla);
return;
}
const payload = {
orderNo,
@@ -379,7 +390,8 @@ export const RouteStepFormModal: React.FC<IRouteStepFormModalProps> = ({
: null,
conditionsLogic: form.conditionsLogic,
slaHours: parseSlaHours(form.slaHours),
askOverdueReason: triStateToBool(form.askOverdueReason)
askOverdueReason: triStateToBool(form.askOverdueReason),
autoApprove: form.autoApprove
};
const request = editing
@@ -505,6 +517,14 @@ export const RouteStepFormModal: React.FC<IRouteStepFormModalProps> = ({
/>
</Field>
<Field label={texts.routeSteps.autoApproveLabel} full hint={texts.routeSteps.autoApproveHint}>
<Checkbox
label={texts.routeSteps.autoApproveCheckbox}
checked={form.autoApprove}
onChange={v => setForm({ ...form, autoApprove: v })}
/>
</Field>
<Field label={texts.routeSteps.conditionLabel} full hint={!hasListId ? texts.routeSteps.conditionDisabledHint : undefined}>
<Checkbox
label={texts.routeSteps.conditionCheckbox}
@@ -34,6 +34,15 @@ export interface TemplateRouteStep {
slaHours: number | null;
/** Спрашивать ли причину просрочки на этом шаге. null — наследовать от типа. */
askOverdueReason: boolean | null;
/**
* Согласовывать шаг автоматически, когда истечёт его SLA (фоновая задача бэкенда,
* см. AutoApproveService). Обычный boolean, а не «три состояния», как у полей выше:
* наследовать его неоткуда — у типа заявки такой настройки нет.
*
* Работает только вместе с SLA: без действующего SLA (ни здесь, ни у типа заявки)
* отсчитывать нечего, и задача такой шаг пропускает.
*/
autoApprove: boolean;
/** Предупреждение бэкенда о сохранённом шаге (например, должность не найдена). */
warning?: string | null;
}
@@ -50,6 +59,7 @@ export interface CreateTemplateRouteStepInput {
conditionsLogic: ConditionsLogic;
slaHours: number | null;
askOverdueReason: boolean | null;
autoApprove: boolean;
}
export type UpdateTemplateRouteStepInput = CreateTemplateRouteStepInput;
@@ -66,6 +76,7 @@ export interface PatchTemplateRouteStepInput {
conditionsLogic?: ConditionsLogic;
slaHours?: number | null;
askOverdueReason?: boolean | null;
autoApprove?: boolean;
}
// Способ задать исполнителя шага: ровно одно из полей выше должно быть заполнено
@@ -329,6 +329,12 @@ export const texts = {
slaHoursPlaceholder: 'Как у типа заявки',
askOverdueReasonLabel: 'Спрашивать причину просрочки',
askOverdueReasonHint: '«Не задано» — берётся значение типа заявки.',
autoApproveLabel: 'Автосогласование',
autoApproveCheckbox: 'Согласовать шаг автоматически, когда истечёт SLA',
autoApproveHint:
'Если согласующий не примет решение в срок, шаг будет согласован без него и заявка пойдёт дальше. Проверка идёт раз в час, поэтому согласование произойдёт в течение часа после истечения срока.',
validationAutoApproveWithoutSla:
'Автосогласование считается от SLA — задайте SLA шага или SLA типа заявки.',
conditionLabel: 'Условие видимости шага',
conditionCheckbox: 'Показывать шаг только при выполнении условия',
conditionDisabledHint:
@@ -16,6 +16,7 @@ function step(overrides: Partial<TemplateRouteStep>): TemplateRouteStep {
conditionsLogic: ConditionsLogic.And,
slaHours: null,
askOverdueReason: null,
autoApprove: false,
...overrides
};
}