feat(автосогласование): колонка AutoApprove во всех списках согласования #20
@@ -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" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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): пустая строка
|
||||
|
||||
Reference in New Issue
Block a user