feat(автосогласование): колонка AutoApprove во всех списках согласования #20

Merged
gorbenko.pavel merged 1 commits from auto-approve into main 2026-09-14 09:02:14 +00:00
31 changed files with 78 additions and 11 deletions
@@ -201,7 +201,8 @@ const ClearanceForm: React.FC<IRequestFormProps> = ({
// Транзитом из маршрута в строку списка: наследование бэкенд применил // Транзитом из маршрута в строку списка: наследование бэкенд применил
// при resolve, форма только переносит значения (см. IMatrixRow.slaHours). // при resolve, форма только переносит значения (см. IMatrixRow.slaHours).
slaHours: r.slaHours, slaHours: r.slaHours,
askOverdueReason: r.askOverdueReason askOverdueReason: r.askOverdueReason,
autoApprove: r.autoApprove
})); }));
const save = (status: string): (() => Promise<void>) => async (): Promise<void> => { const save = (status: string): (() => Promise<void>) => async (): Promise<void> => {
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] }, { "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" }, { "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" }, { "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" } { "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
] ]
} }
@@ -199,7 +199,8 @@ const ConcreteRequestForm: React.FC<IRequestFormProps> = ({
// Транзитом из маршрута в строку списка: наследование бэкенд применил // Транзитом из маршрута в строку списка: наследование бэкенд применил
// при resolve, форма только переносит значения (см. IMatrixRow.slaHours). // при resolve, форма только переносит значения (см. IMatrixRow.slaHours).
slaHours: r.slaHours, slaHours: r.slaHours,
askOverdueReason: r.askOverdueReason askOverdueReason: r.askOverdueReason,
autoApprove: r.autoApprove
})); }));
const save = (status: string): (() => Promise<void>) => async (): Promise<void> => { const save = (status: string): (() => Promise<void>) => async (): Promise<void> => {
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] }, { "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" }, { "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" }, { "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" } { "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
] ]
} }
@@ -222,7 +222,8 @@ const DismissalForm: React.FC<IRequestFormProps> = ({
// Транзитом из маршрута в строку списка: наследование бэкенд применил // Транзитом из маршрута в строку списка: наследование бэкенд применил
// при resolve, форма только переносит значения (см. IMatrixRow.slaHours). // при resolve, форма только переносит значения (см. IMatrixRow.slaHours).
slaHours: r.slaHours, slaHours: r.slaHours,
askOverdueReason: r.askOverdueReason askOverdueReason: r.askOverdueReason,
autoApprove: r.autoApprove
})); }));
const save = (status: string): (() => Promise<void>) => async (): Promise<void> => { const save = (status: string): (() => Promise<void>) => async (): Promise<void> => {
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] }, { "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" }, { "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" }, { "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" } { "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
] ]
} }
@@ -46,7 +46,8 @@ function toApprovers(rows: IMatrixRow[]): IApproverToWrite[] {
allowReassign: r.allowReassign, allowReassign: r.allowReassign,
order: r.order, order: r.order,
slaHours: r.slaHours, slaHours: r.slaHours,
askOverdueReason: r.askOverdueReason askOverdueReason: r.askOverdueReason,
autoApprove: r.autoApprove
})); }));
} }
@@ -188,7 +188,8 @@ const EmployeeTransferForm: React.FC<IRequestFormProps> = ({
// Транзитом из маршрута в строку списка: наследование бэкенд применил // Транзитом из маршрута в строку списка: наследование бэкенд применил
// при resolve, форма только переносит значения (см. IMatrixRow.slaHours). // при resolve, форма только переносит значения (см. IMatrixRow.slaHours).
slaHours: r.slaHours, slaHours: r.slaHours,
askOverdueReason: r.askOverdueReason askOverdueReason: r.askOverdueReason,
autoApprove: r.autoApprove
})); }));
const save = (status: string): (() => Promise<void>) => async (): Promise<void> => { const save = (status: string): (() => Promise<void>) => async (): Promise<void> => {
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] }, { "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" }, { "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" }, { "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" } { "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
] ]
} }
@@ -214,6 +214,7 @@ const RecruitingHireForm: React.FC<IRequestFormProps> = ({
// при resolve, форма только переносит значения (см. IMatrixRow.slaHours). // при resolve, форма только переносит значения (см. IMatrixRow.slaHours).
slaHours: r.slaHours, slaHours: r.slaHours,
askOverdueReason: r.askOverdueReason, askOverdueReason: r.askOverdueReason,
autoApprove: r.autoApprove
})); }));
// Удаление уже прикреплённого файла (в правке) — сразу из заявки. // Удаление уже прикреплённого файла (в правке) — сразу из заявки.
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] }, { "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" }, { "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" }, { "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" } { "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
] ]
} }
@@ -224,7 +224,8 @@ const RecruitingSelectionForm: React.FC<IRequestFormProps> = ({
// Транзитом из маршрута в строку списка: наследование бэкенд применил // Транзитом из маршрута в строку списка: наследование бэкенд применил
// при resolve, форма только переносит значения (см. IMatrixRow.slaHours). // при resolve, форма только переносит значения (см. IMatrixRow.slaHours).
slaHours: r.slaHours, slaHours: r.slaHours,
askOverdueReason: r.askOverdueReason askOverdueReason: r.askOverdueReason,
autoApprove: r.autoApprove
})); }));
const handleRemoveAttachment = (fileName: string): void => { const handleRemoveAttachment = (fileName: string): void => {
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] }, { "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" }, { "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" }, { "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" } { "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
] ]
} }
@@ -268,7 +268,8 @@ const ReferralForm: React.FC<IRequestFormProps> = ({
// Транзитом из маршрута в строку списка: наследование бэкенд применил // Транзитом из маршрута в строку списка: наследование бэкенд применил
// при resolve, форма только переносит значения (см. IMatrixRow.slaHours). // при resolve, форма только переносит значения (см. IMatrixRow.slaHours).
slaHours: r.slaHours, slaHours: r.slaHours,
askOverdueReason: r.askOverdueReason askOverdueReason: r.askOverdueReason,
autoApprove: r.autoApprove
})); }));
const save = (status: string): (() => Promise<void>) => async (): Promise<void> => { const save = (status: string): (() => Promise<void>) => async (): Promise<void> => {
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] }, { "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" }, { "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" }, { "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" } { "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
] ]
} }
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] }, { "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" }, { "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" }, { "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" } { "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
] ]
} }
@@ -207,7 +207,8 @@ const Ticket1CForm: React.FC<IRequestFormProps> = ({
// Транзитом из маршрута в строку списка: наследование бэкенд применил // Транзитом из маршрута в строку списка: наследование бэкенд применил
// при resolve, форма только переносит значения (см. IMatrixRow.slaHours). // при resolve, форма только переносит значения (см. IMatrixRow.slaHours).
slaHours: r.slaHours, slaHours: r.slaHours,
askOverdueReason: r.askOverdueReason askOverdueReason: r.askOverdueReason,
autoApprove: r.autoApprove
})); }));
const handleRemoveAttachment = (fileName: string): void => { const handleRemoveAttachment = (fileName: string): void => {
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] }, { "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" }, { "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" }, { "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" } { "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
] ]
} }
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] }, { "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" }, { "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" }, { "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" } { "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
] ]
} }
@@ -200,7 +200,8 @@ const UnpaidLeaveForm: React.FC<IRequestFormProps> = ({
// Транзитом из маршрута в строку списка: наследование бэкенд применил // Транзитом из маршрута в строку списка: наследование бэкенд применил
// при resolve, форма только переносит значения (см. IMatrixRow.slaHours). // при resolve, форма только переносит значения (см. IMatrixRow.slaHours).
slaHours: r.slaHours, slaHours: r.slaHours,
askOverdueReason: r.askOverdueReason askOverdueReason: r.askOverdueReason,
autoApprove: r.autoApprove
})); }));
const save = (status: string): (() => Promise<void>) => async (): Promise<void> => { const save = (status: string): (() => Promise<void>) => async (): Promise<void> => {
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] }, { "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" }, { "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" }, { "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" } { "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
] ]
} }
@@ -198,7 +198,8 @@ const VacationForm: React.FC<IRequestFormProps> = ({
// Транзитом из маршрута в строку списка: наследование бэкенд применил // Транзитом из маршрута в строку списка: наследование бэкенд применил
// при resolve, форма только переносит значения (см. IMatrixRow.slaHours). // при resolve, форма только переносит значения (см. IMatrixRow.slaHours).
slaHours: r.slaHours, slaHours: r.slaHours,
askOverdueReason: r.askOverdueReason askOverdueReason: r.askOverdueReason,
autoApprove: r.autoApprove
})); }));
const save = (status: string): (() => Promise<void>) => async (): Promise<void> => { const save = (status: string): (() => Promise<void>) => async (): Promise<void> => {
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] }, { "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" }, { "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" }, { "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" } { "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
] ]
} }
@@ -232,6 +232,7 @@ const WorkplaceSetupForm: React.FC<IRequestFormProps> = ({
// при resolve, форма только переносит значения (см. IMatrixRow.slaHours). // при resolve, форма только переносит значения (см. IMatrixRow.slaHours).
slaHours: r.slaHours, slaHours: r.slaHours,
askOverdueReason: r.askOverdueReason, askOverdueReason: r.askOverdueReason,
autoApprove: r.autoApprove
})); }));
// Удаление уже прикреплённого файла (в правке) — сразу из заявки. // Удаление уже прикреплённого файла (в правке) — сразу из заявки.
@@ -11,6 +11,7 @@
{ "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] }, { "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] },
{ "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" }, { "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" },
{ "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" }, { "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" },
{ "internalName": "AutoApprove", "title": "Автосогласование", "type": "Boolean" },
{ "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" } { "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" }
] ]
} }
@@ -36,6 +36,7 @@ const FIELDS = {
delegatedBy: 'DelegatedBy', // «Кем переназначено» (User) → DelegatedById delegatedBy: 'DelegatedBy', // «Кем переназначено» (User) → DelegatedById
slaHours: 'SLAhours', // «SLA, часов» (Number) — пусто значит «взять у типа заявки» slaHours: 'SLAhours', // «SLA, часов» (Number) — пусто значит «взять у типа заявки»
askOverdueReason: 'AskOverdueReason', // «Опрос причины просрочки» (Да/Нет) askOverdueReason: 'AskOverdueReason', // «Опрос причины просрочки» (Да/Нет)
autoApprove: 'AutoApprove', // «Автосогласование» (Да/Нет) — см. IApproverToWrite.autoApprove
}; };
// Согласующий для записи в список. // Согласующий для записи в список.
@@ -61,6 +62,14 @@ export interface IApproverToWrite {
slaHours?: number | null; slaHours?: number | null;
// eslint-disable-next-line @rushstack/no-new-null -- см. slaHours // eslint-disable-next-line @rushstack/no-new-null -- см. slaHours
askOverdueReason?: boolean | null; askOverdueReason?: boolean | null;
/**
* Согласовать шаг автоматически по истечении SLA — так же, как SLA-поля выше,
* приходит из resolve с уже применённым решением (наследовать здесь нечего,
* см. ResolvedRouteStepDto.AutoApprove на бэкенде), портал только переносит
* значение в строку. undefined — «не задано» (старые строки без этого поля);
* фактическое решение принимает не портал, а фоновая задача бэкенда.
*/
autoApprove?: boolean;
} }
// Все запросы к списку «<форма> — согласование». listTitle задаётся формой. // Все запросы к списку «<форма> — согласование». listTitle задаётся формой.
@@ -141,6 +150,7 @@ export class ApprovalListService {
// ?? null, а не || null: ноль часов — значимое значение. // ?? null, а не || null: ноль часов — значимое значение.
[FIELDS.slaHours]: a.slaHours ?? null, [FIELDS.slaHours]: a.slaHours ?? null,
[FIELDS.askOverdueReason]: a.askOverdueReason ?? null, [FIELDS.askOverdueReason]: a.askOverdueReason ?? null,
[FIELDS.autoApprove]: a.autoApprove ?? false,
}); });
}); });
await execute(); await execute();
@@ -278,6 +288,7 @@ export class ApprovalListService {
FIELDS.phase, FIELDS.phase,
FIELDS.slaHours, FIELDS.slaHours,
FIELDS.askOverdueReason, FIELDS.askOverdueReason,
FIELDS.autoApprove,
`${FIELDS.approver}/Title`, `${FIELDS.approver}/Title`,
`${FIELDS.approver}/EMail`, `${FIELDS.approver}/EMail`,
`${FIELDS.delegatedBy}/Title` `${FIELDS.delegatedBy}/Title`
@@ -306,6 +317,7 @@ export class ApprovalListService {
// проставленный при первой отправке, обнулился бы у черновика. // проставленный при первой отправке, обнулился бы у черновика.
slaHours: (r[FIELDS.slaHours] as number | null) ?? null, slaHours: (r[FIELDS.slaHours] as number | null) ?? null,
askOverdueReason: (r[FIELDS.askOverdueReason] as boolean | null) ?? null, askOverdueReason: (r[FIELDS.askOverdueReason] as boolean | null) ?? null,
autoApprove: (r[FIELDS.autoApprove] as boolean | null) ?? false,
}; };
}); });
} }
@@ -38,6 +38,7 @@ export async function getPlannedMatrix(
phase, phase,
slaHours: s.slaHours, slaHours: s.slaHours,
askOverdueReason: s.askOverdueReason, askOverdueReason: s.askOverdueReason,
autoApprove: s.autoApprove,
warning: s.warning, warning: s.warning,
})) }))
); );
@@ -108,4 +108,13 @@ export interface IResolvedRouteStep {
slaHours: number | null; slaHours: number | null;
// eslint-disable-next-line @rushstack/no-new-null -- см. slaHours // eslint-disable-next-line @rushstack/no-new-null -- см. slaHours
askOverdueReason: boolean | null; askOverdueReason: boolean | null;
/**
* Согласовать шаг автоматически, когда истечёт его SLA — настраивается ТОЛЬКО в
* админке маршрутов (approval-route), портал это значение только читает из
* resolve и переносит в строку списка согласования, ничего не решая сам:
* фактическое автосогласование выполняет фоновая задача бэкенда (Hangfire).
* Обычный boolean, а не nullable, как SLA-поля выше, — наследовать здесь
* нечего, см. соответствующее поле на бэкенде (TemplateRouteStep.AutoApprove).
*/
autoApprove: boolean;
} }
@@ -31,6 +31,13 @@ export interface IMatrixRow {
slaHours?: number | null; slaHours?: number | null;
// eslint-disable-next-line @rushstack/no-new-null -- см. slaHours // eslint-disable-next-line @rushstack/no-new-null -- см. slaHours
askOverdueReason?: boolean | null; askOverdueReason?: boolean | null;
/**
* Согласовать шаг автоматически по истечении SLA — то же транзитное поле, что и
* slaHours/askOverdueReason выше: настраивается в админке, портал только
* переносит значение в строку списка, ничего не решая сам (см.
* IResolvedRouteStep.autoApprove).
*/
autoApprove?: boolean;
/** /**
* Предупреждение approval-route по ЭТОМУ шагу (см. IResolvedRouteStep.warning) — * Предупреждение approval-route по ЭТОМУ шагу (см. IResolvedRouteStep.warning) —
* например, роль не разрешилась в конкретного человека, и почему именно. * например, роль не разрешилась в конкретного человека, и почему именно.
@@ -193,7 +193,12 @@ export const ApprovalMatrix: React.FC<IApprovalMatrixProps> = ({
)} )}
{hasDates && <td data-label="Дата">{r.date}</td>} {hasDates && <td data-label="Дата">{r.date}</td>}
{hasSla && ( {hasSla && (
<td data-label="SLA">{r.slaHours || r.slaHours === 0 ? `${r.slaHours} ч` : '—'}</td> <td data-label="SLA">
{r.slaHours || r.slaHours === 0 ? `${r.slaHours} ч` : '—'}
{/* Настраивается только в админке — здесь чисто информативно,
чтобы участник видел: решение по шагу примут не только вручную. */}
{r.autoApprove && <span className={styles.autoApproveBadge}> (авто)</span>}
</td>
)} )}
{/* Перемещение действует на ШАГ целиком (параллельные строки {/* Перемещение действует на ШАГ целиком (параллельные строки
переезжают вместе): одна пара стрелок на весь шаг. */} переезжают вместе): одна пара стрелок на весь шаг. */}
@@ -187,6 +187,14 @@
font-weight: 500; font-weight: 500;
} }
// Пометка «(авто)» рядом со значением SLA — шаг настроен на автосогласование
// (см. ApprovalMatrix.tsx/r.autoApprove). Нейтральным серым, не золотым и не
// красным: это не проблема и не решение человека, а просто информация о
// правиле, действующем на шаг.
.autoApproveBadge {
color: $gray-500;
}
// Причина, по которой approval-route не назначил человека на шаг (см. // Причина, по которой approval-route не назначил человека на шаг (см.
// ApprovalMatrix.tsx/r.warning) — красным, как настоящая проблема маршрута // ApprovalMatrix.tsx/r.warning) — красным, как настоящая проблема маршрута
// (в отличие от нейтрального addedByInline/commentInline): пустая строка // (в отличие от нейтрального addedByInline/commentInline): пустая строка