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
@@ -36,6 +36,7 @@ const FIELDS = {
delegatedBy: 'DelegatedBy', // «Кем переназначено» (User) → DelegatedById
slaHours: 'SLAhours', // «SLA, часов» (Number) — пусто значит «взять у типа заявки»
askOverdueReason: 'AskOverdueReason', // «Опрос причины просрочки» (Да/Нет)
autoApprove: 'AutoApprove', // «Автосогласование» (Да/Нет) — см. IApproverToWrite.autoApprove
};
// Согласующий для записи в список.
@@ -61,6 +62,14 @@ export interface IApproverToWrite {
slaHours?: number | null;
// eslint-disable-next-line @rushstack/no-new-null -- см. slaHours
askOverdueReason?: boolean | null;
/**
* Согласовать шаг автоматически по истечении SLA — так же, как SLA-поля выше,
* приходит из resolve с уже применённым решением (наследовать здесь нечего,
* см. ResolvedRouteStepDto.AutoApprove на бэкенде), портал только переносит
* значение в строку. undefined — «не задано» (старые строки без этого поля);
* фактическое решение принимает не портал, а фоновая задача бэкенда.
*/
autoApprove?: boolean;
}
// Все запросы к списку «<форма> — согласование». listTitle задаётся формой.
@@ -141,6 +150,7 @@ export class ApprovalListService {
// ?? null, а не || null: ноль часов — значимое значение.
[FIELDS.slaHours]: a.slaHours ?? null,
[FIELDS.askOverdueReason]: a.askOverdueReason ?? null,
[FIELDS.autoApprove]: a.autoApprove ?? false,
});
});
await execute();
@@ -278,6 +288,7 @@ export class ApprovalListService {
FIELDS.phase,
FIELDS.slaHours,
FIELDS.askOverdueReason,
FIELDS.autoApprove,
`${FIELDS.approver}/Title`,
`${FIELDS.approver}/EMail`,
`${FIELDS.delegatedBy}/Title`
@@ -306,6 +317,7 @@ export class ApprovalListService {
// проставленный при первой отправке, обнулился бы у черновика.
slaHours: (r[FIELDS.slaHours] as number | 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,
slaHours: s.slaHours,
askOverdueReason: s.askOverdueReason,
autoApprove: s.autoApprove,
warning: s.warning,
}))
);
@@ -108,4 +108,13 @@ export interface IResolvedRouteStep {
slaHours: number | null;
// eslint-disable-next-line @rushstack/no-new-null -- см. slaHours
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;
// eslint-disable-next-line @rushstack/no-new-null -- см. slaHours
askOverdueReason?: boolean | null;
/**
* Согласовать шаг автоматически по истечении SLA — то же транзитное поле, что и
* slaHours/askOverdueReason выше: настраивается в админке, портал только
* переносит значение в строку списка, ничего не решая сам (см.
* IResolvedRouteStep.autoApprove).
*/
autoApprove?: boolean;
/**
* Предупреждение approval-route по ЭТОМУ шагу (см. IResolvedRouteStep.warning) —
* например, роль не разрешилась в конкретного человека, и почему именно.
@@ -193,7 +193,12 @@ export const ApprovalMatrix: React.FC<IApprovalMatrixProps> = ({
)}
{hasDates && <td data-label="Дата">{r.date}</td>}
{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;
}
// Пометка «(авто)» рядом со значением SLA — шаг настроен на автосогласование
// (см. ApprovalMatrix.tsx/r.autoApprove). Нейтральным серым, не золотым и не
// красным: это не проблема и не решение человека, а просто информация о
// правиле, действующем на шаг.
.autoApproveBadge {
color: $gray-500;
}
// Причина, по которой approval-route не назначил человека на шаг (см.
// ApprovalMatrix.tsx/r.warning) — красным, как настоящая проблема маршрута
// (в отличие от нейтрального addedByInline/commentInline): пустая строка