Sla (#11)
Tests / backend-test (push) Successful in 1m50s
Tests / frontend-test (push) Successful in 49s

Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz>
Reviewed-on: http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/pulls/11
This commit was merged in pull request #11.
This commit is contained in:
2026-08-13 04:44:08 +00:00
parent 7f2aabbf29
commit d076f4e98d
103 changed files with 2606 additions and 1265 deletions
@@ -18,6 +18,15 @@ import { useSharePointFields } from '../../hooks/useSharePointFields';
import { ApprovalRouteApi } from '../../services/api';
import { isNumericFieldType, SharePointListsClient } from '../../services/sharePointLists';
import { Checkbox, Field, FormGrid, FormModal, RadioGroup, SearchableSelect, Select, TextInput } from '../ui';
import {
boolToTriState,
isValidSlaHours,
parseSlaHours,
slaHoursToInput,
triStateToBool,
TriState,
TRI_STATE_OPTIONS
} from '../../utils/sla';
export interface IRouteStepFormModalProps {
api: ApprovalRouteApi;
@@ -47,6 +56,9 @@ interface IFormState {
fieldName: string;
operation: ComparisonOperator;
value: string;
/** Пусто — наследовать SLA от типа заявки; «0» — напоминать сразу. Это разные вещи. */
slaHours: string;
askOverdueReason: TriState;
}
const STATUS_OPTIONS = [RouteStepStatus.Approval, RouteStepStatus.Execution]
@@ -78,7 +90,9 @@ function toFormState(editing: TemplateRouteStep | undefined, nextOrderNo: number
hasCondition: false,
fieldName: '',
operation: ComparisonOperator.GreaterThan,
value: ''
value: '',
slaHours: '',
askOverdueReason: ''
};
}
return {
@@ -93,7 +107,9 @@ function toFormState(editing: TemplateRouteStep | undefined, nextOrderNo: number
hasCondition: Boolean(editing.fieldName),
fieldName: editing.fieldName ?? '',
operation: editing.operation && editing.operation !== ComparisonOperator.None ? editing.operation : ComparisonOperator.GreaterThan,
value: editing.value ?? ''
value: editing.value ?? '',
slaHours: slaHoursToInput(editing.slaHours),
askOverdueReason: boolToTriState(editing.askOverdueReason)
};
}
@@ -164,6 +180,10 @@ export const RouteStepFormModal: React.FC<IRouteStepFormModalProps> = ({
submit.fail(texts.routeSteps.validationConditionNumber);
return;
}
if (!isValidSlaHours(form.slaHours)) {
submit.fail(texts.routeSteps.validationSlaHours);
return;
}
const payload = {
orderNo,
@@ -175,7 +195,9 @@ export const RouteStepFormModal: React.FC<IRouteStepFormModalProps> = ({
headType: form.kind === 'headType' ? form.headType : null,
fieldName: form.hasCondition ? form.fieldName.trim() : null,
operation: form.hasCondition ? form.operation : null,
value: form.hasCondition ? form.value.trim() : null
value: form.hasCondition ? form.value.trim() : null,
slaHours: parseSlaHours(form.slaHours),
askOverdueReason: triStateToBool(form.askOverdueReason)
};
const request = editing
@@ -283,6 +305,23 @@ export const RouteStepFormModal: React.FC<IRouteStepFormModalProps> = ({
/>
</Field>
<Field label={texts.routeSteps.slaHoursLabel} hint={texts.routeSteps.slaHoursHint}>
<TextInput
type="number"
min={0}
value={form.slaHours}
onChange={e => setForm({ ...form, slaHours: e.target.value })}
placeholder={texts.routeSteps.slaHoursPlaceholder}
/>
</Field>
<Field label={texts.routeSteps.askOverdueReasonLabel} hint={texts.routeSteps.askOverdueReasonHint}>
<Select
options={TRI_STATE_OPTIONS}
value={form.askOverdueReason}
onChange={e => setForm({ ...form, askOverdueReason: e.target.value as TriState })}
/>
</Field>
<Field label={texts.routeSteps.conditionLabel} full hint={!hasListId ? texts.routeSteps.conditionDisabledHint : undefined}>
<Checkbox
label={texts.routeSteps.conditionCheckbox}