feat(автосогласование): колонка AutoApprove во всех списках согласования (#20)
Tests / frontend-test (push) Failing after 38s

Провижининг (Provision-Lists.ps1 читает эти манифесты) теперь заводит колонку
«Автосогласование» (AutoApprove, Да/Нет) в каждом из 12 списков согласования —
рядом с уже существующими SLAhours/AskOverdueReason.

Значение приходит из resolve (approval-route API, шаг маршрута настраивается
только в админке — портал само значение не выбирает, только переносит его в
строку списка при отправке заявки/сохранении черновика, тем же путём, что и
slaHours/askOverdueReason). Фактическое автосогласование по истечении SLA
делает фоновая задача бэкенда (Hangfire), не портал.

В ApprovalMatrix добавлена пометка «(авто)» рядом со значением SLA — чисто
информативно, чтобы участник видел, что решение по шагу может принять не
только человек.

Примечание: ServiceRequest и TmcTransfer не передают ни slaHours/
askOverdueReason, ни (теперь) autoApprove из resolve в строку — это
существовавший пробел (SLA на уровне шага у этих двух форм и раньше не
работал), автосогласование для них наследует только это же ограничение.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz>
Reviewed-on: http://git.tnsu.kz/timur.gorodetskiy/request-portal/pulls/20
This commit was merged in pull request #20.
This commit is contained in:
2026-09-14 09:02:14 +00:00
parent 7712a5ced4
commit cf57b40513
31 changed files with 78 additions and 11 deletions
@@ -201,7 +201,8 @@ const ClearanceForm: React.FC<IRequestFormProps> = ({
// Транзитом из маршрута в строку списка: наследование бэкенд применил
// при resolve, форма только переносит значения (см. IMatrixRow.slaHours).
slaHours: r.slaHours,
askOverdueReason: r.askOverdueReason
askOverdueReason: r.askOverdueReason,
autoApprove: r.autoApprove
}));
const save = (status: string): (() => Promise<void>) => async (): Promise<void> => {
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
]
}
@@ -199,7 +199,8 @@ const ConcreteRequestForm: React.FC<IRequestFormProps> = ({
// Транзитом из маршрута в строку списка: наследование бэкенд применил
// при resolve, форма только переносит значения (см. IMatrixRow.slaHours).
slaHours: r.slaHours,
askOverdueReason: r.askOverdueReason
askOverdueReason: r.askOverdueReason,
autoApprove: r.autoApprove
}));
const save = (status: string): (() => Promise<void>) => async (): Promise<void> => {
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
]
}
@@ -222,7 +222,8 @@ const DismissalForm: React.FC<IRequestFormProps> = ({
// Транзитом из маршрута в строку списка: наследование бэкенд применил
// при resolve, форма только переносит значения (см. IMatrixRow.slaHours).
slaHours: r.slaHours,
askOverdueReason: r.askOverdueReason
askOverdueReason: r.askOverdueReason,
autoApprove: r.autoApprove
}));
const save = (status: string): (() => Promise<void>) => async (): Promise<void> => {
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
]
}
@@ -46,7 +46,8 @@ function toApprovers(rows: IMatrixRow[]): IApproverToWrite[] {
allowReassign: r.allowReassign,
order: r.order,
slaHours: r.slaHours,
askOverdueReason: r.askOverdueReason
askOverdueReason: r.askOverdueReason,
autoApprove: r.autoApprove
}));
}
@@ -188,7 +188,8 @@ const EmployeeTransferForm: React.FC<IRequestFormProps> = ({
// Транзитом из маршрута в строку списка: наследование бэкенд применил
// при resolve, форма только переносит значения (см. IMatrixRow.slaHours).
slaHours: r.slaHours,
askOverdueReason: r.askOverdueReason
askOverdueReason: r.askOverdueReason,
autoApprove: r.autoApprove
}));
const save = (status: string): (() => Promise<void>) => async (): Promise<void> => {
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
]
}
@@ -214,6 +214,7 @@ const RecruitingHireForm: React.FC<IRequestFormProps> = ({
// при resolve, форма только переносит значения (см. IMatrixRow.slaHours).
slaHours: r.slaHours,
askOverdueReason: r.askOverdueReason,
autoApprove: r.autoApprove
}));
// Удаление уже прикреплённого файла (в правке) — сразу из заявки.
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
]
}
@@ -224,7 +224,8 @@ const RecruitingSelectionForm: React.FC<IRequestFormProps> = ({
// Транзитом из маршрута в строку списка: наследование бэкенд применил
// при resolve, форма только переносит значения (см. IMatrixRow.slaHours).
slaHours: r.slaHours,
askOverdueReason: r.askOverdueReason
askOverdueReason: r.askOverdueReason,
autoApprove: r.autoApprove
}));
const handleRemoveAttachment = (fileName: string): void => {
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
]
}
@@ -268,7 +268,8 @@ const ReferralForm: React.FC<IRequestFormProps> = ({
// Транзитом из маршрута в строку списка: наследование бэкенд применил
// при resolve, форма только переносит значения (см. IMatrixRow.slaHours).
slaHours: r.slaHours,
askOverdueReason: r.askOverdueReason
askOverdueReason: r.askOverdueReason,
autoApprove: r.autoApprove
}));
const save = (status: string): (() => Promise<void>) => async (): Promise<void> => {
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
]
}
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
]
}
@@ -207,7 +207,8 @@ const Ticket1CForm: React.FC<IRequestFormProps> = ({
// Транзитом из маршрута в строку списка: наследование бэкенд применил
// при resolve, форма только переносит значения (см. IMatrixRow.slaHours).
slaHours: r.slaHours,
askOverdueReason: r.askOverdueReason
askOverdueReason: r.askOverdueReason,
autoApprove: r.autoApprove
}));
const handleRemoveAttachment = (fileName: string): void => {
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
]
}
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
]
}
@@ -200,7 +200,8 @@ const UnpaidLeaveForm: React.FC<IRequestFormProps> = ({
// Транзитом из маршрута в строку списка: наследование бэкенд применил
// при resolve, форма только переносит значения (см. IMatrixRow.slaHours).
slaHours: r.slaHours,
askOverdueReason: r.askOverdueReason
askOverdueReason: r.askOverdueReason,
autoApprove: r.autoApprove
}));
const save = (status: string): (() => Promise<void>) => async (): Promise<void> => {
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
]
}
@@ -198,7 +198,8 @@ const VacationForm: React.FC<IRequestFormProps> = ({
// Транзитом из маршрута в строку списка: наследование бэкенд применил
// при resolve, форма только переносит значения (см. IMatrixRow.slaHours).
slaHours: r.slaHours,
askOverdueReason: r.askOverdueReason
askOverdueReason: r.askOverdueReason,
autoApprove: r.autoApprove
}));
const save = (status: string): (() => Promise<void>) => async (): Promise<void> => {
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
]
}
@@ -232,6 +232,7 @@ const WorkplaceSetupForm: React.FC<IRequestFormProps> = ({
// при resolve, форма только переносит значения (см. IMatrixRow.slaHours).
slaHours: r.slaHours,
askOverdueReason: r.askOverdueReason,
autoApprove: r.autoApprove
}));
// Удаление уже прикреплённого файла (в правке) — сразу из заявки.
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
]
}