From 0e6030f024efbae5a99339c8d26ef662e55bc59d Mon Sep 17 00:00:00 2001 From: "gorbenko.pavel" Date: Wed, 12 Aug 2026 15:43:49 +0500 Subject: [PATCH] =?UTF-8?q?sla=20=D0=B2=20=D0=BE=D0=B1=D1=89=D0=B5=D0=BC?= =?UTF-8?q?=20=D0=B3=D0=BE=D1=82=D0=BE=D0=B2=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecruitingHire/RecruitingHireForm.tsx | 4 ++++ .../RecruitingHire/approval/provisioning.json | 2 ++ .../RecruitingSelectionForm.tsx | 6 ++++- .../approval/provisioning.json | 2 ++ .../forms/Ticket1C/Ticket1CForm.tsx | 6 ++++- .../forms/Ticket1C/approval/provisioning.json | 2 ++ .../forms/Vacation/VacationForm.tsx | 6 ++++- .../forms/Vacation/approval/provisioning.json | 2 ++ .../WorkplaceSetup/WorkplaceSetupForm.tsx | 4 ++++ .../WorkplaceSetup/approval/provisioning.json | 2 ++ .../shared/approval/approvalList.service.ts | 24 +++++++++++++++++++ .../approval/approvalSubmission.service.ts | 2 ++ .../approval/models/IApprovalRouteApi.ts | 8 +++++++ .../shared/approval/models/IMatrixRow.ts | 12 ++++++++++ 14 files changed, 79 insertions(+), 3 deletions(-) diff --git a/src/webparts/requestPortal/forms/RecruitingHire/RecruitingHireForm.tsx b/src/webparts/requestPortal/forms/RecruitingHire/RecruitingHireForm.tsx index 54e022f..5980aa5 100644 --- a/src/webparts/requestPortal/forms/RecruitingHire/RecruitingHireForm.tsx +++ b/src/webparts/requestPortal/forms/RecruitingHire/RecruitingHireForm.tsx @@ -141,6 +141,10 @@ const RecruitingHireForm: React.FC = ({ requestType, context, position: r.position, allowReassign: r.allowReassign, order: r.order, + // Транзитом из маршрута в строку списка: наследование бэкенд применил + // при resolve, форма только переносит значения (см. IMatrixRow.slaHours). + slaHours: r.slaHours, + askOverdueReason: r.askOverdueReason, })); // Удаление уже прикреплённого файла (в правке) — сразу из заявки. diff --git a/src/webparts/requestPortal/forms/RecruitingHire/approval/provisioning.json b/src/webparts/requestPortal/forms/RecruitingHire/approval/provisioning.json index 5473480..26dd879 100644 --- a/src/webparts/requestPortal/forms/RecruitingHire/approval/provisioning.json +++ b/src/webparts/requestPortal/forms/RecruitingHire/approval/provisioning.json @@ -9,6 +9,8 @@ { "internalName": "StepOrder", "title": "Порядок", "type": "Number" }, { "internalName": "AllowReassignToOtherApprovers", "title": "Можно перепоручать", "type": "Boolean" }, { "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] }, + { "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" }, + { "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" }, { "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" } ] } diff --git a/src/webparts/requestPortal/forms/RecruitingSelection/RecruitingSelectionForm.tsx b/src/webparts/requestPortal/forms/RecruitingSelection/RecruitingSelectionForm.tsx index 859e050..07b14d2 100644 --- a/src/webparts/requestPortal/forms/RecruitingSelection/RecruitingSelectionForm.tsx +++ b/src/webparts/requestPortal/forms/RecruitingSelection/RecruitingSelectionForm.tsx @@ -117,7 +117,11 @@ const RecruitingSelectionForm: React.FC = ({ requestType, con email: r.email, position: r.position, allowReassign: r.allowReassign, - order: r.order + order: r.order, + // Транзитом из маршрута в строку списка: наследование бэкенд применил + // при resolve, форма только переносит значения (см. IMatrixRow.slaHours). + slaHours: r.slaHours, + askOverdueReason: r.askOverdueReason })); const handleRemoveAttachment = (fileName: string): void => { diff --git a/src/webparts/requestPortal/forms/RecruitingSelection/approval/provisioning.json b/src/webparts/requestPortal/forms/RecruitingSelection/approval/provisioning.json index 0c56450..9707088 100644 --- a/src/webparts/requestPortal/forms/RecruitingSelection/approval/provisioning.json +++ b/src/webparts/requestPortal/forms/RecruitingSelection/approval/provisioning.json @@ -9,6 +9,8 @@ { "internalName": "StepOrder", "title": "Порядок", "type": "Number" }, { "internalName": "AllowReassignToOtherApprovers", "title": "Можно перепоручать", "type": "Boolean" }, { "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] }, + { "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" }, + { "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" }, { "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" } ] } diff --git a/src/webparts/requestPortal/forms/Ticket1C/Ticket1CForm.tsx b/src/webparts/requestPortal/forms/Ticket1C/Ticket1CForm.tsx index 31484ad..a474731 100644 --- a/src/webparts/requestPortal/forms/Ticket1C/Ticket1CForm.tsx +++ b/src/webparts/requestPortal/forms/Ticket1C/Ticket1CForm.tsx @@ -172,7 +172,11 @@ const Ticket1CForm: React.FC = ({ requestType, context, onCan email: r.email, position: r.position, allowReassign: r.allowReassign, - order: r.order + order: r.order, + // Транзитом из маршрута в строку списка: наследование бэкенд применил + // при resolve, форма только переносит значения (см. IMatrixRow.slaHours). + slaHours: r.slaHours, + askOverdueReason: r.askOverdueReason })); const handleRemoveAttachment = (fileName: string): void => { diff --git a/src/webparts/requestPortal/forms/Ticket1C/approval/provisioning.json b/src/webparts/requestPortal/forms/Ticket1C/approval/provisioning.json index 3da3e45..d144c07 100644 --- a/src/webparts/requestPortal/forms/Ticket1C/approval/provisioning.json +++ b/src/webparts/requestPortal/forms/Ticket1C/approval/provisioning.json @@ -9,6 +9,8 @@ { "internalName": "StepOrder", "title": "Порядок", "type": "Number" }, { "internalName": "AllowReassignToOtherApprovers", "title": "Можно перепоручать", "type": "Boolean" }, { "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] }, + { "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" }, + { "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" }, { "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" } ] } diff --git a/src/webparts/requestPortal/forms/Vacation/VacationForm.tsx b/src/webparts/requestPortal/forms/Vacation/VacationForm.tsx index 51c4cf0..ba9fd0e 100644 --- a/src/webparts/requestPortal/forms/Vacation/VacationForm.tsx +++ b/src/webparts/requestPortal/forms/Vacation/VacationForm.tsx @@ -138,7 +138,11 @@ const VacationForm: React.FC = ({ requestType, context, onCan email: r.email, position: r.position, allowReassign: r.allowReassign, - order: r.order + order: r.order, + // Транзитом из маршрута в строку списка: наследование бэкенд применил + // при resolve, форма только переносит значения (см. IMatrixRow.slaHours). + slaHours: r.slaHours, + askOverdueReason: r.askOverdueReason })); const save = (status: string): (() => Promise) => async (): Promise => { diff --git a/src/webparts/requestPortal/forms/Vacation/approval/provisioning.json b/src/webparts/requestPortal/forms/Vacation/approval/provisioning.json index 557a7d6..6558228 100644 --- a/src/webparts/requestPortal/forms/Vacation/approval/provisioning.json +++ b/src/webparts/requestPortal/forms/Vacation/approval/provisioning.json @@ -9,6 +9,8 @@ { "internalName": "StepOrder", "title": "Порядок", "type": "Number" }, { "internalName": "AllowReassignToOtherApprovers", "title": "Можно перепоручать", "type": "Boolean" }, { "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] }, + { "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" }, + { "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" }, { "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" } ] } diff --git a/src/webparts/requestPortal/forms/WorkplaceSetup/WorkplaceSetupForm.tsx b/src/webparts/requestPortal/forms/WorkplaceSetup/WorkplaceSetupForm.tsx index 58271d1..e765f6d 100644 --- a/src/webparts/requestPortal/forms/WorkplaceSetup/WorkplaceSetupForm.tsx +++ b/src/webparts/requestPortal/forms/WorkplaceSetup/WorkplaceSetupForm.tsx @@ -164,6 +164,10 @@ const WorkplaceSetupForm: React.FC = ({ requestType, context, position: r.position, allowReassign: r.allowReassign, order: r.order, + // Транзитом из маршрута в строку списка: наследование бэкенд применил + // при resolve, форма только переносит значения (см. IMatrixRow.slaHours). + slaHours: r.slaHours, + askOverdueReason: r.askOverdueReason, })); // Удаление уже прикреплённого файла (в правке) — сразу из заявки. diff --git a/src/webparts/requestPortal/forms/WorkplaceSetup/approval/provisioning.json b/src/webparts/requestPortal/forms/WorkplaceSetup/approval/provisioning.json index 09fd21e..f6b867d 100644 --- a/src/webparts/requestPortal/forms/WorkplaceSetup/approval/provisioning.json +++ b/src/webparts/requestPortal/forms/WorkplaceSetup/approval/provisioning.json @@ -9,6 +9,8 @@ { "internalName": "StepOrder", "title": "Порядок", "type": "Number" }, { "internalName": "AllowReassignToOtherApprovers", "title": "Можно перепоручать", "type": "Boolean" }, { "internalName": "Phase", "title": "Фаза", "type": "Choice", "choices": ["Согласование", "Исполнение"] }, + { "internalName": "SLAhours", "title": "SLA, часов", "type": "Number" }, + { "internalName": "AskOverdueReason", "title": "Опрос причины просрочки", "type": "Boolean" }, { "internalName": "DelegatedBy", "title": "Кем переназначено", "type": "User", "userSelectionMode": "PeopleOnly" } ] } diff --git a/src/webparts/requestPortal/shared/approval/approvalList.service.ts b/src/webparts/requestPortal/shared/approval/approvalList.service.ts index 09533ad..5535a17 100644 --- a/src/webparts/requestPortal/shared/approval/approvalList.service.ts +++ b/src/webparts/requestPortal/shared/approval/approvalList.service.ts @@ -32,6 +32,8 @@ const FIELDS = { allowReassign: 'AllowReassignToOtherApprovers', // «Можно перепоручать» (Да/Нет) phase: 'Phase', // «Фаза» (Choice): Согласование | Исполнение delegatedBy: 'DelegatedBy', // «Кем переназначено» (User) → DelegatedById + slaHours: 'SLAhours', // «SLA, часов» (Number) — пусто значит «взять у типа заявки» + askOverdueReason: 'AskOverdueReason', // «Опрос причины просрочки» (Да/Нет) }; // Согласующий для записи в список. @@ -45,6 +47,18 @@ export interface IApproverToWrite { * (ApprovalMatrix/useMatrixEditor) всегда даёт уникальный order по позиции — * это поле для параллельных шагов, приходящих из внешнего маршрута напрямую. */ order?: number; + /** + * SLA шага в часах и признак опроса причины просрочки — приходят из resolve + * с уже применённым наследованием «шаг перекрывает тип заявки» (см. бэкенд + * ResolveStepAsync). Портал их не вычисляет, только переносит в строку. + * + * undefined/null — «в строке не задано»: тогда напоминания используют SLA + * типа заявки. Ноль — это НЕ пусто, а «напоминать сразу». + */ + // eslint-disable-next-line @rushstack/no-new-null -- null = «не задано» (в SharePoint пустая колонка) + slaHours?: number | null; + // eslint-disable-next-line @rushstack/no-new-null -- см. slaHours + askOverdueReason?: boolean | null; } // Все запросы к списку «<форма> — согласование». listTitle задаётся формой. @@ -101,6 +115,9 @@ export class ApprovalListService { [FIELDS.order]: order, [FIELDS.allowReassign]: a.allowReassign, [FIELDS.phase]: phase, + // ?? null, а не || null: ноль часов — значимое значение. + [FIELDS.slaHours]: a.slaHours ?? null, + [FIELDS.askOverdueReason]: a.askOverdueReason ?? null, }); } } @@ -222,6 +239,8 @@ export class ApprovalListService { FIELDS.order, FIELDS.allowReassign, FIELDS.phase, + FIELDS.slaHours, + FIELDS.askOverdueReason, `${FIELDS.approver}/Title`, `${FIELDS.approver}/EMail` ) @@ -240,6 +259,11 @@ export class ApprovalListService { comment: (r[FIELDS.comment] as string) || '', allowReassign: (r[FIELDS.allowReassign] as boolean) ?? false, phase: (r[FIELDS.phase] as string) || APPROVAL_PHASE.approval, + // Читаем, чтобы не потерять при пересохранении: writeRoute удаляет + // строки и создаёт их заново из матрицы, и без этих полей SLA, + // проставленный при первой отправке, обнулился бы у черновика. + slaHours: (r[FIELDS.slaHours] as number | null) ?? null, + askOverdueReason: (r[FIELDS.askOverdueReason] as boolean | null) ?? null, }; }); } diff --git a/src/webparts/requestPortal/shared/approval/approvalSubmission.service.ts b/src/webparts/requestPortal/shared/approval/approvalSubmission.service.ts index df994b4..47df5c2 100644 --- a/src/webparts/requestPortal/shared/approval/approvalSubmission.service.ts +++ b/src/webparts/requestPortal/shared/approval/approvalSubmission.service.ts @@ -33,6 +33,8 @@ export async function getPlannedMatrix( comment: '', allowReassign: s.allowReassignToOtherApprovers, phase, + slaHours: s.slaHours, + askOverdueReason: s.askOverdueReason, })) ); } diff --git a/src/webparts/requestPortal/shared/approval/models/IApprovalRouteApi.ts b/src/webparts/requestPortal/shared/approval/models/IApprovalRouteApi.ts index e8d98d8..6dcd954 100644 --- a/src/webparts/requestPortal/shared/approval/models/IApprovalRouteApi.ts +++ b/src/webparts/requestPortal/shared/approval/models/IApprovalRouteApi.ts @@ -47,4 +47,12 @@ export interface IResolvedRouteStep { // Предупреждение маршрута по этому шагу (например, роль не разрешилась в // конкретного человека) — приходит от API, показываем автору при желании. warning: string | null; + // SLA шага в часах и признак опроса причины просрочки. Наследование + // «шаг перекрывает тип заявки» бэкенд уже применил — здесь итоговое значение, + // его достаточно записать в строку списка согласования. + // null — SLA не настроен, напоминаний по шагу не будет. + // eslint-disable-next-line @rushstack/no-new-null -- зеркалит nullable-контракт бэкенда + slaHours: number | null; + // eslint-disable-next-line @rushstack/no-new-null -- см. slaHours + askOverdueReason: boolean | null; } diff --git a/src/webparts/requestPortal/shared/approval/models/IMatrixRow.ts b/src/webparts/requestPortal/shared/approval/models/IMatrixRow.ts index 5ebc0c1..3cd42da 100644 --- a/src/webparts/requestPortal/shared/approval/models/IMatrixRow.ts +++ b/src/webparts/requestPortal/shared/approval/models/IMatrixRow.ts @@ -15,4 +15,16 @@ export interface IMatrixRow { // редактировании — переключается кнопкой и заново раздаёт order (см. // useMatrixEditor/withOrders). parallelWithPrev?: boolean; + /** + * SLA шага и признак опроса причины просрочки. Матрица здесь — транзитное + * звено: значения приходят из resolve (или читаются из уже записанных строк) + * и уходят обратно в список при сохранении. На форме они не показываются и + * не редактируются — правятся в админке. + * + * null/undefined — «не задано», работает SLA типа заявки. Ноль — «сразу». + */ + // eslint-disable-next-line @rushstack/no-new-null -- null приходит из SharePoint и значит «не задано» + slaHours?: number | null; + // eslint-disable-next-line @rushstack/no-new-null -- см. slaHours + askOverdueReason?: boolean | null; }