From bbef3062f42d9105debcdfdba78d36561e61b858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A2=D0=B8=D0=BC=D1=83=D1=80=20=D0=93=D0=BE=D1=80=D0=BE?= =?UTF-8?q?=D0=B4=D0=B5=D1=86=D0=BA=D0=B8=D0=B9?= Date: Fri, 9 Oct 2026 12:18:15 +0500 Subject: [PATCH] =?UTF-8?q?fix:=20=D1=87=D0=B5=D1=80=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=B8=D0=BA=20=D0=B1=D0=B5=D0=B7=20=D0=BC=D0=B0=D1=80=D1=88?= =?UTF-8?q?=D1=80=D1=83=D1=82=D0=B0=20=D0=BF=D0=BB=D0=B0=D0=BD=D0=B8=D1=80?= =?UTF-8?q?=D1=83=D0=B5=D1=82=20=D0=BC=D0=B0=D1=80=D1=88=D1=80=D1=83=D1=82?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=BD=D0=BE=D0=B2=D0=BE=20=D0=BF=D1=80=D0=B8=20?= =?UTF-8?q?=D0=BE=D1=82=D0=BA=D1=80=D1=8B=D1=82=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit У части черновиков не оказалось строк маршрута, и форма показывала пустую матрицу: отправить такую заявку было некому. Теперь при открытии черновика без строк маршрут планируется заново (loadRoutePhase), во всех формах; поданные заявки по-прежнему читают записанные строки. Почему строки пропали у первоначального случая — пока не выяснено. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../forms/Clearance/ClearanceForm.tsx | 34 +++++------ .../ConcreteRequest/ConcreteRequestForm.tsx | 16 +++-- .../forms/Dismissal/DismissalForm.tsx | 18 +++--- .../EmployeeTransfer/EmployeeTransferForm.tsx | 16 ++--- .../RecruitingHire/RecruitingHireForm.tsx | 25 ++++---- .../RecruitingSelectionForm.tsx | 27 ++++----- .../forms/Referral/ReferralForm.tsx | 29 +++++---- .../ServiceRequest/ServiceRequestForm.tsx | 15 +++-- .../SkudExplanation/SkudExplanationForm.tsx | 2 + .../forms/Ticket1C/Ticket1CForm.tsx | 26 ++++---- .../forms/TmcTransfer/TmcTransferForm.tsx | 29 +++++---- .../forms/UnpaidLeave/UnpaidLeaveForm.tsx | 16 ++--- .../forms/Vacation/VacationForm.tsx | 16 ++--- .../WorkplaceSetup/WorkplaceSetupForm.tsx | 28 ++++----- .../requestPortal/shared/approval/index.ts | 2 + .../shared/approval/loadRoutePhase.ts | 47 +++++++++++++++ .../shared/approval/usePhasedRoute.ts | 59 +++++++++++-------- .../shared/approval/useRouteExecutors.ts | 18 ++++-- 18 files changed, 247 insertions(+), 176 deletions(-) create mode 100644 src/webparts/requestPortal/shared/approval/loadRoutePhase.ts diff --git a/src/webparts/requestPortal/forms/Clearance/ClearanceForm.tsx b/src/webparts/requestPortal/forms/Clearance/ClearanceForm.tsx index 4d97841..ff883da 100644 --- a/src/webparts/requestPortal/forms/Clearance/ClearanceForm.tsx +++ b/src/webparts/requestPortal/forms/Clearance/ClearanceForm.tsx @@ -19,13 +19,14 @@ import { StatusBadge, useMatrixEditor, getPlannedMatrix, + loadRoutePhase, + isOpenedRouteReady, resolveWriteListId, notifyActiveStep, useRequestTypeId, REQUEST_STATUS, isEditableStatus, IApproverToWrite, - APPROVAL_PHASE, unassignedStepMessage } from '../../shared/approval'; import { effectiveUserEmail } from '../../shared/impersonation'; @@ -149,16 +150,15 @@ const ClearanceForm: React.FC = ({ // передана» (см. TemplateRouteService.ResolveAsync в approval-route). const loadMatrix = useCallback( () => - editId - ? approval - .getMatrixForRequest(editId) - .then((rows) => rows.filter((r) => r.phase !== APPROVAL_PHASE.execution)) - : getPlannedMatrix(context, requestTypeId as number, data.Employee, approval, 'Approval', null, [ - { fieldName: 'Position', value: data.Position } - ]), - [context, approval, editId, data.Employee, data.Position, requestTypeId] + loadRoutePhase(approval, editId, requestStatus, 'Approval', () => + getPlannedMatrix(context, requestTypeId as number, data.Employee, approval, 'Approval', null, [ + { fieldName: 'Position', value: data.Position } + ]) + ), + [context, approval, editId, requestStatus, data.Employee, data.Position, requestTypeId] ); - const matrixReady = !!editId || (!!data.Employee && !!requestTypeId); + const matrixReady = + isOpenedRouteReady(editId, requestStatus, requestTypeId) && (!!editId || (!!data.Employee && !!requestTypeId)); const matrix = useMatrixEditor( loadMatrix, `${data.Employee}-${editId ?? 'new'}-${data.Position}`, @@ -172,14 +172,12 @@ const ClearanceForm: React.FC = ({ // открытой на редактирование заявки — строки уже записаны, читаем как есть. const loadExecutors = useCallback( () => - editId - ? approval - .getMatrixForRequest(editId) - .then((rows) => rows.filter((r) => r.phase === APPROVAL_PHASE.execution)) - : getPlannedMatrix(context, requestTypeId as number, data.Employee, approval, 'Execution', null, [ - { fieldName: 'Position', value: data.Position } - ]), - [context, approval, editId, data.Employee, data.Position, requestTypeId] + loadRoutePhase(approval, editId, requestStatus, 'Execution', () => + getPlannedMatrix(context, requestTypeId as number, data.Employee, approval, 'Execution', null, [ + { fieldName: 'Position', value: data.Position } + ]) + ), + [context, approval, editId, requestStatus, data.Employee, data.Position, requestTypeId] ); const executors = useMatrixEditor( loadExecutors, diff --git a/src/webparts/requestPortal/forms/ConcreteRequest/ConcreteRequestForm.tsx b/src/webparts/requestPortal/forms/ConcreteRequest/ConcreteRequestForm.tsx index d619235..e776057 100644 --- a/src/webparts/requestPortal/forms/ConcreteRequest/ConcreteRequestForm.tsx +++ b/src/webparts/requestPortal/forms/ConcreteRequest/ConcreteRequestForm.tsx @@ -20,6 +20,8 @@ import { StatusBadge, useMatrixEditor, getPlannedMatrix, + loadRoutePhase, + isOpenedRouteReady, resolveWriteListId, notifyActiveStep, useRequestTypeId, @@ -28,7 +30,6 @@ import { IApproverToWrite, unassignedStepMessage, useFormExecutors, - approvalPhaseRows, executorsToWrite, initialSubmitStatus } from '../../shared/approval'; @@ -172,13 +173,15 @@ const ConcreteRequestForm: React.FC = ({ // проект не выбран (см. matrixReady). У заявки на редактировании маршрут // читается из уже записанных строк, поэтому проект там уже не нужен. const projectId = data.ProjectId || null; - const matrixReady = !!editId || (!!data.SiteChiefEmail && !!projectId && !!requestTypeId); + const matrixReady = + isOpenedRouteReady(editId, requestStatus, requestTypeId) && + (!!editId || (!!data.SiteChiefEmail && !!projectId && !!requestTypeId)); const loadMatrix = useCallback( () => - editId - ? approval.getMatrixForRequest(editId).then(approvalPhaseRows) - : getPlannedMatrix(context, requestTypeId as number, data.SiteChiefEmail, approval, 'Approval', null, undefined, projectId), - [context, approval, editId, data.SiteChiefEmail, requestTypeId, projectId] + loadRoutePhase(approval, editId, requestStatus, 'Approval', () => + getPlannedMatrix(context, requestTypeId as number, data.SiteChiefEmail, approval, 'Approval', null, undefined, projectId) + ), + [context, approval, editId, requestStatus, data.SiteChiefEmail, requestTypeId, projectId] ); const matrix = useMatrixEditor( loadMatrix, @@ -197,6 +200,7 @@ const ConcreteRequestForm: React.FC = ({ requestTypeId, userEmail: data.SiteChiefEmail, editId, + requestStatus, projectId }); diff --git a/src/webparts/requestPortal/forms/Dismissal/DismissalForm.tsx b/src/webparts/requestPortal/forms/Dismissal/DismissalForm.tsx index 0600f05..20f2f68 100644 --- a/src/webparts/requestPortal/forms/Dismissal/DismissalForm.tsx +++ b/src/webparts/requestPortal/forms/Dismissal/DismissalForm.tsx @@ -20,6 +20,8 @@ import { StatusBadge, useMatrixEditor, getPlannedMatrix, + loadRoutePhase, + isOpenedRouteReady, notifyActiveStep, useRequestTypeId, REQUEST_STATUS, @@ -27,7 +29,6 @@ import { IApproverToWrite, unassignedStepMessage, resolveWriteListId, - approvalPhaseRows, executorsToWrite, initialSubmitStatus, useFormExecutors @@ -163,18 +164,18 @@ const DismissalForm: React.FC = ({ // затереть правки инициатора. const loadMatrix = useCallback( () => - editId - ? approval.getMatrixForRequest(editId).then(approvalPhaseRows) - : getPlannedMatrix(context, requestTypeId as number, data.Employee, approval, 'Approval', null, [ - { fieldName: 'Position', value: primaryPosition(data.Organization) } - ]), - [context, approval, editId, data.Employee, data.Organization, requestTypeId] + loadRoutePhase(approval, editId, requestStatus, 'Approval', () => + getPlannedMatrix(context, requestTypeId as number, data.Employee, approval, 'Approval', null, [ + { fieldName: 'Position', value: primaryPosition(data.Organization) } + ]) + ), + [context, approval, editId, requestStatus, data.Employee, data.Organization, requestTypeId] ); const matrix = useMatrixEditor( loadMatrix, `${data.Employee}-${editId ?? 'new'}-${primaryPosition(data.Organization)}`, !readOnly, - !!editId || (!!data.Employee && !!requestTypeId), + isOpenedRouteReady(editId, requestStatus, requestTypeId) && (!!editId || (!!data.Employee && !!requestTypeId)), (login) => approval.getJobTitle(login) ); @@ -187,6 +188,7 @@ const DismissalForm: React.FC = ({ requestTypeId, userEmail: data.Employee, editId, + requestStatus, fieldValues: [{ fieldName: 'Position', value: primaryPosition(data.Organization) }] }); diff --git a/src/webparts/requestPortal/forms/EmployeeTransfer/EmployeeTransferForm.tsx b/src/webparts/requestPortal/forms/EmployeeTransfer/EmployeeTransferForm.tsx index 07f4471..7e17a59 100644 --- a/src/webparts/requestPortal/forms/EmployeeTransfer/EmployeeTransferForm.tsx +++ b/src/webparts/requestPortal/forms/EmployeeTransfer/EmployeeTransferForm.tsx @@ -20,10 +20,11 @@ import { useMatrixEditor, useFormExecutors, ExecutorsMatrix, - approvalPhaseRows, executorsToWrite, initialSubmitStatus, getPlannedMatrix, + loadRoutePhase, + isOpenedRouteReady, resolveWriteListId, notifyActiveStep, useRequestTypeId, @@ -159,16 +160,16 @@ const EmployeeTransferForm: React.FC = ({ // маршрут читается из уже записанных строк, чтобы не затереть правки инициатора. const loadMatrix = useCallback( () => - editId - ? approval.getMatrixForRequest(editId).then(approvalPhaseRows) - : getPlannedMatrix(context, requestTypeId as number, data.Employee, approval, 'Approval'), - [context, approval, editId, data.Employee, requestTypeId] + loadRoutePhase(approval, editId, requestStatus, 'Approval', () => + getPlannedMatrix(context, requestTypeId as number, data.Employee, approval, 'Approval') + ), + [context, approval, editId, requestStatus, data.Employee, requestTypeId] ); const matrix = useMatrixEditor( loadMatrix, `${data.Employee}-${editId ?? 'new'}`, !readOnly, - !!editId || (!!data.Employee && !!requestTypeId), + isOpenedRouteReady(editId, requestStatus, requestTypeId) && (!!editId || (!!data.Employee && !!requestTypeId)), (login) => approval.getJobTitle(login) ); @@ -180,7 +181,8 @@ const EmployeeTransferForm: React.FC = ({ approval, requestTypeId, userEmail: data.Employee, - editId + editId, + requestStatus }); const isValid = !!( diff --git a/src/webparts/requestPortal/forms/RecruitingHire/RecruitingHireForm.tsx b/src/webparts/requestPortal/forms/RecruitingHire/RecruitingHireForm.tsx index be66d48..33267fc 100644 --- a/src/webparts/requestPortal/forms/RecruitingHire/RecruitingHireForm.tsx +++ b/src/webparts/requestPortal/forms/RecruitingHire/RecruitingHireForm.tsx @@ -28,13 +28,14 @@ import { useMatrixEditor, canEditExecutors, getPlannedMatrix, + loadRoutePhase, + isOpenedRouteReady, resolveWriteListId, notifyActiveStep, useRequestTypeId, REQUEST_STATUS, isEditableStatus, IApproverToWrite, - APPROVAL_PHASE, unassignedStepMessage, resolveApproverOrgInfo, canEditRoute @@ -161,7 +162,7 @@ const RecruitingHireForm: React.FC = ({ // (поле — мультивыбор) и ждём его выбора, как раньше. // Пустой Id — проект старой заявки, введённый вручную (см. projectsFromItem). const projectId = (data.Projects.length > 0 && data.Projects[0].Id) || null; - const matrixReady = !!editId || (!!projectId && !!requestTypeId); + const matrixReady = isOpenedRouteReady(editId, requestStatus, requestTypeId) &&(!!editId || (!!projectId && !!requestTypeId)); // Несколько шагов маршрута условны по компании (см. панель «Маршруты»: // «CompaniesJson = TOO QAZGEOMAX», «CompaniesJson = TOO CT Global», шаг с // сразу ДВУМЯ условиями одновременно). Companies — мультивыбор → ОДНА запись @@ -180,12 +181,10 @@ const RecruitingHireForm: React.FC = ({ }, [companiesFieldValue, projectsFieldValue]); const loadMatrix = useCallback( () => - editId - ? approval - .getMatrixForRequest(editId) - .then((rows) => rows.filter((r) => r.phase !== APPROVAL_PHASE.execution)) - : getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Approval', null, fieldValues, projectId), - [context, userEmail, approval, editId, requestTypeId, fieldValues, projectId] + loadRoutePhase(approval, editId, requestStatus, 'Approval', () => + getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Approval', null, fieldValues, projectId) + ), + [context, userEmail, approval, editId, requestStatus, requestTypeId, fieldValues, projectId] ); const matrix = useMatrixEditor( loadMatrix, @@ -200,12 +199,10 @@ const RecruitingHireForm: React.FC = ({ // Фаза исполнения: исполнители из маршрута (status='Execution'), только для чтения. const loadExecutors = useCallback( () => - editId - ? approval - .getMatrixForRequest(editId) - .then((rows) => rows.filter((r) => r.phase === APPROVAL_PHASE.execution)) - : getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Execution', null, fieldValues, projectId), - [context, userEmail, approval, editId, requestTypeId, fieldValues, projectId] + loadRoutePhase(approval, editId, requestStatus, 'Execution', () => + getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Execution', null, fieldValues, projectId) + ), + [context, userEmail, approval, editId, requestStatus, requestTypeId, fieldValues, projectId] ); const executors = useMatrixEditor( loadExecutors, diff --git a/src/webparts/requestPortal/forms/RecruitingSelection/RecruitingSelectionForm.tsx b/src/webparts/requestPortal/forms/RecruitingSelection/RecruitingSelectionForm.tsx index 11efda5..088f978 100644 --- a/src/webparts/requestPortal/forms/RecruitingSelection/RecruitingSelectionForm.tsx +++ b/src/webparts/requestPortal/forms/RecruitingSelection/RecruitingSelectionForm.tsx @@ -28,6 +28,8 @@ import { useMatrixEditor, canEditExecutors, getPlannedMatrix, + loadRoutePhase, + isOpenedRouteReady, resolveWriteListId, notifyActiveStep, useRequestTypeId, @@ -36,8 +38,7 @@ import { IApproverToWrite, unassignedStepMessage, resolveApproverOrgInfo, - canEditRoute, - APPROVAL_PHASE + canEditRoute } from '../../shared/approval'; import { uploadAttachments, @@ -192,7 +193,7 @@ const RecruitingSelectionForm: React.FC = ({ const projectId = data.ObjectProjectId || null; // Новая заявка: матрицу резолвим только когда есть и тип, и проект — иначе // 404 «маршрут не найден». При редактировании читаем уже записанные строки. - const matrixReady = !!editId || (!!projectId && !!requestTypeId); + const matrixReady = isOpenedRouteReady(editId, requestStatus, requestTypeId) &&(!!editId || (!!projectId && !!requestTypeId)); // Условные шаги маршрута (см. [[request-portal-fieldvalues-routing]] — тот же // разбор на RecruitingHire): fieldName = internalName колонки. Companies — // мультивыбор → ОДНА запись fieldValues, value — имена выбранных компаний @@ -207,12 +208,10 @@ const RecruitingSelectionForm: React.FC = ({ }, [companiesFieldValue, data.ObjectProject]); const loadMatrix = useCallback( () => - editId - ? approval - .getMatrixForRequest(editId) - .then((rows) => rows.filter((r) => r.phase !== APPROVAL_PHASE.execution)) - : getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Approval', null, fieldValues, projectId), - [context, userEmail, approval, editId, requestTypeId, fieldValues, projectId] + loadRoutePhase(approval, editId, requestStatus, 'Approval', () => + getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Approval', null, fieldValues, projectId) + ), + [context, userEmail, approval, editId, requestStatus, requestTypeId, fieldValues, projectId] ); const matrix = useMatrixEditor( loadMatrix, @@ -227,12 +226,10 @@ const RecruitingSelectionForm: React.FC = ({ // Фаза исполнения: исполнители из маршрута (status='Execution'), только для чтения. const loadExecutors = useCallback( () => - editId - ? approval - .getMatrixForRequest(editId) - .then((rows) => rows.filter((r) => r.phase === APPROVAL_PHASE.execution)) - : getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Execution', null, fieldValues, projectId), - [context, userEmail, approval, editId, requestTypeId, fieldValues, projectId] + loadRoutePhase(approval, editId, requestStatus, 'Execution', () => + getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Execution', null, fieldValues, projectId) + ), + [context, userEmail, approval, editId, requestStatus, requestTypeId, fieldValues, projectId] ); const executors = useMatrixEditor( loadExecutors, diff --git a/src/webparts/requestPortal/forms/Referral/ReferralForm.tsx b/src/webparts/requestPortal/forms/Referral/ReferralForm.tsx index 531d410..109336f 100644 --- a/src/webparts/requestPortal/forms/Referral/ReferralForm.tsx +++ b/src/webparts/requestPortal/forms/Referral/ReferralForm.tsx @@ -23,6 +23,8 @@ import { useMatrixEditor, canEditExecutors, getPlannedMatrix, + loadRoutePhase, + isOpenedRouteReady, notifyActiveStep, useRequestTypeId, REQUEST_STATUS, @@ -30,7 +32,6 @@ import { IApproverToWrite, unassignedStepMessage, resolveWriteListId, - APPROVAL_PHASE, initialSubmitStatus, resolveApproverOrgInfo, canEditRoute @@ -210,18 +211,18 @@ const ReferralForm: React.FC = ({ // правки инициатора. const loadMatrix = useCallback( () => - editId - ? approval - .getMatrixForRequest(editId) - .then((rows) => rows.filter((r) => r.phase !== APPROVAL_PHASE.execution)) - : getPlannedMatrix(context, requestTypeId as number, data.Referrer, approval, 'Approval'), - [context, approval, editId, data.Referrer, requestTypeId] + loadRoutePhase(approval, editId, requestStatus, 'Approval', () => + getPlannedMatrix(context, requestTypeId as number, data.Referrer, approval, 'Approval') + ), + [context, approval, editId, requestStatus, data.Referrer, requestTypeId] ); + const matrixReady = + isOpenedRouteReady(editId, requestStatus, requestTypeId) && (!!editId || (!!data.Referrer && !!requestTypeId)); const matrix = useMatrixEditor( loadMatrix, `${data.Referrer}-${editId ?? 'new'}`, !readOnly, - !!editId || (!!data.Referrer && !!requestTypeId), + matrixReady, (login) => approval.getJobTitle(login), resolveApproverOrgInfo(context) ); @@ -232,18 +233,16 @@ const ReferralForm: React.FC = ({ // 'Execution' getPlannedMatrix её просто не резолвит. const loadExecutors = useCallback( () => - editId - ? approval - .getMatrixForRequest(editId) - .then((rows) => rows.filter((r) => r.phase === APPROVAL_PHASE.execution)) - : getPlannedMatrix(context, requestTypeId as number, data.Referrer, approval, 'Execution'), - [context, approval, editId, data.Referrer, requestTypeId] + loadRoutePhase(approval, editId, requestStatus, 'Execution', () => + getPlannedMatrix(context, requestTypeId as number, data.Referrer, approval, 'Execution') + ), + [context, approval, editId, requestStatus, data.Referrer, requestTypeId] ); const executors = useMatrixEditor( loadExecutors, `${data.Referrer}-${editId ?? 'new'}`, canEditExecutors(context, readOnly), - !!editId || (!!data.Referrer && !!requestTypeId), + matrixReady, (login) => approval.getJobTitle(login), resolveApproverOrgInfo(context) ); diff --git a/src/webparts/requestPortal/forms/ServiceRequest/ServiceRequestForm.tsx b/src/webparts/requestPortal/forms/ServiceRequest/ServiceRequestForm.tsx index 9a5e95d..14a4800 100644 --- a/src/webparts/requestPortal/forms/ServiceRequest/ServiceRequestForm.tsx +++ b/src/webparts/requestPortal/forms/ServiceRequest/ServiceRequestForm.tsx @@ -19,6 +19,8 @@ import { StatusBadge, useMatrixEditor, getPlannedMatrix, + loadRoutePhase, + isOpenedRouteReady, notifyActiveStep, useRequestTypeId, REQUEST_STATUS, @@ -28,7 +30,6 @@ import { resolveWriteListId, useFormExecutors, ExecutorsMatrix, - approvalPhaseRows, executorsToWrite, initialSubmitStatus } from '../../shared/approval'; @@ -183,13 +184,14 @@ const ServiceRequestForm: React.FC = ({ // Маршрут зависит от проекта (см. approval-route: согласующие — руководитель // проекта, ПТО объекта) — пока проект не выбран, resolve вернёт 404. const projectId = data.ProjectId || null; - const matrixReady = !!editId || (!!userEmail && !!projectId && !!requestTypeId); + const matrixReady = + isOpenedRouteReady(editId, requestStatus, requestTypeId) && (!!editId || (!!userEmail && !!projectId && !!requestTypeId)); const loadMatrix = useCallback( () => - editId - ? approval.getMatrixForRequest(editId).then(approvalPhaseRows) - : getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Approval', null, undefined, projectId), - [context, userEmail, approval, editId, projectId, requestTypeId] + loadRoutePhase(approval, editId, requestStatus, 'Approval', () => + getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Approval', null, undefined, projectId) + ), + [context, userEmail, approval, editId, requestStatus, projectId, requestTypeId] ); const matrix = useMatrixEditor(loadMatrix, `${userEmail}-${editId ?? 'new'}-${projectId}`, !readOnly, matrixReady, (login) => approval.getJobTitle(login) @@ -203,6 +205,7 @@ const ServiceRequestForm: React.FC = ({ requestTypeId, userEmail: matrixReady ? userEmail : '', editId, + requestStatus, projectId }); diff --git a/src/webparts/requestPortal/forms/SkudExplanation/SkudExplanationForm.tsx b/src/webparts/requestPortal/forms/SkudExplanation/SkudExplanationForm.tsx index 7f69916..92523e4 100644 --- a/src/webparts/requestPortal/forms/SkudExplanation/SkudExplanationForm.tsx +++ b/src/webparts/requestPortal/forms/SkudExplanation/SkudExplanationForm.tsx @@ -133,6 +133,8 @@ const SkudExplanationForm: React.FC = ({ context, approval, requestId: editId, + replanEmptyDraft: true, + requestStatus, employeeEmail: data.Employee, requestTypeId, editable: !readOnly, diff --git a/src/webparts/requestPortal/forms/Ticket1C/Ticket1CForm.tsx b/src/webparts/requestPortal/forms/Ticket1C/Ticket1CForm.tsx index f166573..39d8871 100644 --- a/src/webparts/requestPortal/forms/Ticket1C/Ticket1CForm.tsx +++ b/src/webparts/requestPortal/forms/Ticket1C/Ticket1CForm.tsx @@ -22,6 +22,8 @@ import { StatusBadge, useMatrixEditor, getPlannedMatrix, + loadRoutePhase, + isOpenedRouteReady, resolveWriteListId, notifyActiveStep, useRequestTypeId, @@ -29,7 +31,6 @@ import { isEditableStatus, IApproverToWrite, unassignedStepMessage, - APPROVAL_PHASE, executorsToWrite } from '../../shared/approval'; import { @@ -181,15 +182,14 @@ const Ticket1CForm: React.FC = ({ [data.WorkType, data.Department] ); const matrixKey = `${userEmail}-${editId ?? 'new'}-${data.WorkType}-${data.Department}`; - const matrixReady = !!editId || (!!data.WorkType && !!requestTypeId); + const matrixReady = + isOpenedRouteReady(editId, requestStatus, requestTypeId) && (!!editId || (!!data.WorkType && !!requestTypeId)); const loadMatrix = useCallback( () => - editId - ? approval - .getMatrixForRequest(editId) - .then((rows) => rows.filter((r) => r.phase !== APPROVAL_PHASE.execution)) - : getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Approval', null, fieldValues), - [context, userEmail, approval, editId, fieldValues, requestTypeId] + loadRoutePhase(approval, editId, requestStatus, 'Approval', () => + getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Approval', null, fieldValues) + ), + [context, userEmail, approval, editId, requestStatus, fieldValues, requestTypeId] ); const matrix = useMatrixEditor(loadMatrix, matrixKey, false, matrixReady, (login) => approval.getJobTitle(login)); @@ -197,12 +197,10 @@ const Ticket1CForm: React.FC = ({ // в карточке Planner, в портале отмечает «Выполнено» (см. plannerHandoff.ts). const loadExecutors = useCallback( () => - editId - ? approval - .getMatrixForRequest(editId) - .then((rows) => rows.filter((r) => r.phase === APPROVAL_PHASE.execution)) - : getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Execution', null, fieldValues), - [context, userEmail, approval, editId, fieldValues, requestTypeId] + loadRoutePhase(approval, editId, requestStatus, 'Execution', () => + getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Execution', null, fieldValues) + ), + [context, userEmail, approval, editId, requestStatus, fieldValues, requestTypeId] ); const executors = useMatrixEditor(loadExecutors, matrixKey, false, matrixReady, (login) => approval.getJobTitle(login)); // Должность нового «Руководителя» — approval-route её не знает (резолвил шаг diff --git a/src/webparts/requestPortal/forms/TmcTransfer/TmcTransferForm.tsx b/src/webparts/requestPortal/forms/TmcTransfer/TmcTransferForm.tsx index 23bfaba..c00d078 100644 --- a/src/webparts/requestPortal/forms/TmcTransfer/TmcTransferForm.tsx +++ b/src/webparts/requestPortal/forms/TmcTransfer/TmcTransferForm.tsx @@ -24,12 +24,13 @@ import { useMatrixEditor, canEditExecutors, getPlannedMatrix, + loadRoutePhase, + isOpenedRouteReady, notifyActiveStep, useRequestTypeId, REQUEST_STATUS, isEditableStatus, IApproverToWrite, - APPROVAL_PHASE, unassignedStepMessage, resolveWriteListId } from '../../shared/approval'; @@ -301,18 +302,18 @@ const TmcTransferForm: React.FC = ({ // Маршрут согласования зависит от заявителя и проекта-источника («Откуда»): // пока не выбраны, resolve вернёт 404 — не грузим, показываем заглушку. У // открытой на редактирование заявки маршрут уже записан, ни то ни другое не нужно. - const matrixReady = !!editId || (!!userEmail && !!projectId && !!requestTypeId); + const openedReady = isOpenedRouteReady(editId, requestStatus, requestTypeId); + const matrixReady = openedReady && (!!editId || (!!userEmail && !!projectId && !!requestTypeId)); // Исполнитель определяется проектом-получателем («Куда»): показываем блок с // заглушкой, пока «Куда» не выбрано. Источник и заявитель тоже нужны, иначе resolve 404. - const executorsReady = !!editId || (!!userEmail && !!projectId && !!targetProjectId && !!requestTypeId); + const executorsReady = + openedReady && (!!editId || (!!userEmail && !!projectId && !!targetProjectId && !!requestTypeId)); const loadMatrix = useCallback( () => - editId - ? approval - .getMatrixForRequest(editId) - .then((rows) => rows.filter((r) => r.phase !== APPROVAL_PHASE.execution)) - : getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Approval', null, undefined, projectId), - [context, userEmail, approval, editId, projectId, requestTypeId] + loadRoutePhase(approval, editId, requestStatus, 'Approval', () => + getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Approval', null, undefined, projectId) + ), + [context, userEmail, approval, editId, requestStatus, projectId, requestTypeId] ); const matrix = useMatrixEditor(loadMatrix, `${userEmail}-${editId ?? 'new'}-${projectId}`, !readOnly, matrixReady, (login) => approval.getJobTitle(login) @@ -321,12 +322,10 @@ const TmcTransferForm: React.FC = ({ // Фаза исполнения: исполнители из маршрута (status='Execution'), только для чтения. const loadExecutors = useCallback( () => - editId - ? approval - .getMatrixForRequest(editId) - .then((rows) => rows.filter((r) => r.phase === APPROVAL_PHASE.execution)) - : getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Execution', null, undefined, projectId), - [context, userEmail, approval, editId, projectId, requestTypeId] + loadRoutePhase(approval, editId, requestStatus, 'Execution', () => + getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Execution', null, undefined, projectId) + ), + [context, userEmail, approval, editId, requestStatus, projectId, requestTypeId] ); const executors = useMatrixEditor( loadExecutors, diff --git a/src/webparts/requestPortal/forms/UnpaidLeave/UnpaidLeaveForm.tsx b/src/webparts/requestPortal/forms/UnpaidLeave/UnpaidLeaveForm.tsx index 87a8264..21c0869 100644 --- a/src/webparts/requestPortal/forms/UnpaidLeave/UnpaidLeaveForm.tsx +++ b/src/webparts/requestPortal/forms/UnpaidLeave/UnpaidLeaveForm.tsx @@ -22,10 +22,11 @@ import { StatusBadge, useMatrixEditor, useFormExecutors, - approvalPhaseRows, executorsToWrite, initialSubmitStatus, getPlannedMatrix, + loadRoutePhase, + isOpenedRouteReady, notifyActiveStep, useRequestTypeId, REQUEST_STATUS, @@ -162,16 +163,16 @@ const UnpaidLeaveForm: React.FC = ({ // маршрут читается из уже записанных строк, чтобы не затереть правки инициатора. const loadMatrix = useCallback( () => - editId - ? approval.getMatrixForRequest(editId).then(approvalPhaseRows) - : getPlannedMatrix(context, requestTypeId as number, data.Employee, approval, 'Approval'), - [context, approval, editId, data.Employee, requestTypeId] + loadRoutePhase(approval, editId, requestStatus, 'Approval', () => + getPlannedMatrix(context, requestTypeId as number, data.Employee, approval, 'Approval') + ), + [context, approval, editId, requestStatus, data.Employee, requestTypeId] ); const matrix = useMatrixEditor( loadMatrix, `${data.Employee}-${editId ?? 'new'}`, !readOnly, - !!editId || (!!data.Employee && !!requestTypeId), + isOpenedRouteReady(editId, requestStatus, requestTypeId) && (!!editId || (!!data.Employee && !!requestTypeId)), (login) => approval.getJobTitle(login) ); @@ -182,7 +183,8 @@ const UnpaidLeaveForm: React.FC = ({ approval, requestTypeId, userEmail: data.Employee, - editId + editId, + requestStatus }); // Проблемы полей (см. validation.ts) подсвечиваются под полями через FieldIssuesProvider. diff --git a/src/webparts/requestPortal/forms/Vacation/VacationForm.tsx b/src/webparts/requestPortal/forms/Vacation/VacationForm.tsx index d987e62..e45441a 100644 --- a/src/webparts/requestPortal/forms/Vacation/VacationForm.tsx +++ b/src/webparts/requestPortal/forms/Vacation/VacationForm.tsx @@ -20,10 +20,11 @@ import { useMatrixEditor, useFormExecutors, ExecutorsMatrix, - approvalPhaseRows, executorsToWrite, initialSubmitStatus, getPlannedMatrix, + loadRoutePhase, + isOpenedRouteReady, resolveWriteListId, notifyActiveStep, useRequestTypeId, @@ -170,16 +171,16 @@ const VacationForm: React.FC = ({ // маршрут читается из уже записанных строк, чтобы не затереть правки инициатора. const loadMatrix = useCallback( () => - editId - ? approval.getMatrixForRequest(editId).then(approvalPhaseRows) - : getPlannedMatrix(context, requestTypeId as number, data.Employee, approval, 'Approval'), - [context, approval, editId, data.Employee, requestTypeId] + loadRoutePhase(approval, editId, requestStatus, 'Approval', () => + getPlannedMatrix(context, requestTypeId as number, data.Employee, approval, 'Approval') + ), + [context, approval, editId, requestStatus, data.Employee, requestTypeId] ); const matrix = useMatrixEditor( loadMatrix, `${data.Employee}-${editId ?? 'new'}`, !readOnly, - !!editId || (!!data.Employee && !!requestTypeId), + isOpenedRouteReady(editId, requestStatus, requestTypeId) && (!!editId || (!!data.Employee && !!requestTypeId)), (login) => approval.getJobTitle(login) ); @@ -191,7 +192,8 @@ const VacationForm: React.FC = ({ approval, requestTypeId, userEmail: data.Employee, - editId + editId, + requestStatus }); const daysNum = Number(data.DaysCount); diff --git a/src/webparts/requestPortal/forms/WorkplaceSetup/WorkplaceSetupForm.tsx b/src/webparts/requestPortal/forms/WorkplaceSetup/WorkplaceSetupForm.tsx index b46aec6..4d18eff 100644 --- a/src/webparts/requestPortal/forms/WorkplaceSetup/WorkplaceSetupForm.tsx +++ b/src/webparts/requestPortal/forms/WorkplaceSetup/WorkplaceSetupForm.tsx @@ -27,13 +27,14 @@ import { useMatrixEditor, canEditExecutors, getPlannedMatrix, + loadRoutePhase, + isOpenedRouteReady, resolveWriteListId, notifyActiveStep, useRequestTypeId, REQUEST_STATUS, isEditableStatus, IApproverToWrite, - APPROVAL_PHASE, IResolveFieldValue, unassignedStepMessage } from '../../shared/approval'; @@ -182,36 +183,33 @@ const WorkplaceSetupForm: React.FC = ({ // вручную при первом сохранении, и планирование затёрло бы эти правки. const loadMatrix = useCallback( () => - editId - ? approval - .getMatrixForRequest(editId) - .then((rows) => rows.filter((r) => r.phase !== APPROVAL_PHASE.execution)) - : getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Approval', data.DepartmentId, fieldValues), - [context, userEmail, approval, editId, data.DepartmentId, requestTypeId, fieldValues] + loadRoutePhase(approval, editId, requestStatus, 'Approval', () => + getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Approval', data.DepartmentId, fieldValues) + ), + [context, userEmail, approval, editId, requestStatus, data.DepartmentId, requestTypeId, fieldValues] ); + const matrixReady = isOpenedRouteReady(editId, requestStatus, requestTypeId) && (!!editId || !!requestTypeId); const matrix = useMatrixEditor( loadMatrix, matrixKey, !readOnly, - !!editId || !!requestTypeId, + matrixReady, (login) => approval.getJobTitle(login) ); // Фаза исполнения: исполнители из маршрута (status='Execution'), только для чтения. const loadExecutors = useCallback( () => - editId - ? approval - .getMatrixForRequest(editId) - .then((rows) => rows.filter((r) => r.phase === APPROVAL_PHASE.execution)) - : getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Execution', data.DepartmentId, fieldValues), - [context, userEmail, approval, editId, data.DepartmentId, requestTypeId, fieldValues] + loadRoutePhase(approval, editId, requestStatus, 'Execution', () => + getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Execution', data.DepartmentId, fieldValues) + ), + [context, userEmail, approval, editId, requestStatus, data.DepartmentId, requestTypeId, fieldValues] ); const executors = useMatrixEditor( loadExecutors, matrixKey, canEditExecutors(context, readOnly), - !!editId || !!requestTypeId, + matrixReady, (login) => approval.getJobTitle(login) ); diff --git a/src/webparts/requestPortal/shared/approval/index.ts b/src/webparts/requestPortal/shared/approval/index.ts index 9d0dbf2..95aa876 100644 --- a/src/webparts/requestPortal/shared/approval/index.ts +++ b/src/webparts/requestPortal/shared/approval/index.ts @@ -70,6 +70,8 @@ export { useFormExecutors, useViewExecutors } from './useRouteExecutors'; +export { loadRoutePhase, isOpenedRouteReady } from './loadRoutePhase'; +export type { RoutePhase } from './loadRoutePhase'; export { usePhasedRoute } from './usePhasedRoute'; export type { IPhasedRoute } from './usePhasedRoute'; export { resolveApproverOrgInfo } from './approverOrgInfo'; diff --git a/src/webparts/requestPortal/shared/approval/loadRoutePhase.ts b/src/webparts/requestPortal/shared/approval/loadRoutePhase.ts new file mode 100644 index 0000000..cab43ef --- /dev/null +++ b/src/webparts/requestPortal/shared/approval/loadRoutePhase.ts @@ -0,0 +1,47 @@ +import { ApprovalListService } from './approvalList.service'; +import { IMatrixRow } from './models/IMatrixRow'; +import { APPROVAL_PHASE, REQUEST_STATUS } from './models/statuses'; + +/** Фаза маршрута, которую грузит форма заполнения. */ +export type RoutePhase = 'Approval' | 'Execution'; + +/** + * Маршрут одной фазы для формы заполнения: + * - новая заявка (requestId нет) — планируемый маршрут из админки (plan); + * - открытая заявка — уже записанные строки: в них правки инициатора, их не затираем; + * - исключение — ЧЕРНОВИК без единой записанной строки маршрута (ни в одной + * фазе): при его сохранении маршрут не пришёл (ошибка resolve, ещё не выбран + * проект и т.п.). Такой черновик запрашивает маршрут заново, как новая заявка, — + * иначе он навсегда остался бы без согласующих. Черновик с записанной (в том + * числе поправленной инициатором) матрицей не трогаем. + * + * У открытой заявки requestStatus должен быть уже загружен — форма держит + * матрицу неготовой, пока он undefined (см. isOpenedRouteReady). + */ +export async function loadRoutePhase( + approval: ApprovalListService, + requestId: number | undefined, + requestStatus: string | undefined, + phase: RoutePhase, + plan: () => Promise +): Promise { + if (!requestId) return plan(); + const saved = await approval.getMatrixForRequest(requestId); + if (saved.length === 0 && requestStatus === REQUEST_STATUS.draft) return plan(); + const execution = phase === 'Execution'; + return saved.filter((r) => (r.phase === APPROVAL_PHASE.execution) === execution); +} + +/** + * Можно ли грузить маршрут ОТКРЫТОЙ заявки через loadRoutePhase: нужен статус + * (черновик ли это) и тип заявки (по нему черновик без строк планирует маршрут + * заново). Иначе загрузка ушла бы раньше времени и не повторилась. У новой + * заявки — всегда true: её готовность решает сама форма. + */ +export function isOpenedRouteReady( + requestId: number | undefined, + requestStatus: string | undefined, + requestTypeId: number | undefined +): boolean { + return !requestId || (requestStatus !== undefined && !!requestTypeId); +} diff --git a/src/webparts/requestPortal/shared/approval/usePhasedRoute.ts b/src/webparts/requestPortal/shared/approval/usePhasedRoute.ts index 857e935..f7d37b0 100644 --- a/src/webparts/requestPortal/shared/approval/usePhasedRoute.ts +++ b/src/webparts/requestPortal/shared/approval/usePhasedRoute.ts @@ -1,11 +1,10 @@ import { useCallback } from 'react'; import { WebPartContext } from '@microsoft/sp-webpart-base'; -import { APPROVAL_PHASE } from './models/statuses'; -import { IMatrixRow } from './models/IMatrixRow'; -import { ApprovalListService } from './approvalList.service'; import { getPlannedMatrix } from './approvalSubmission.service'; +import { ApprovalListService } from './approvalList.service'; import { resolveApproverOrgInfo } from './approverOrgInfo'; import { useMatrixEditor } from './useMatrixEditor'; +import { isOpenedRouteReady, loadRoutePhase, RoutePhase } from './loadRoutePhase'; type MatrixEditor = ReturnType; @@ -21,6 +20,14 @@ interface IRouteSource { approval: ApprovalListService; /** Заявка уже записана — маршрут читаем из её строк, а не планируем заново. */ requestId: number | undefined; + /** + * true — форма заполнения: черновик без записанных строк маршрута запрашивает + * маршрут заново (см. loadRoutePhase), а пока requestStatus не загружен, + * маршрут не грузится. Просмотр не включает — всегда записанные строки. + */ + replanEmptyDraft?: boolean; + /** Статус открытой заявки — нужен только при replanEmptyDraft. */ + requestStatus?: string; /** E-mail сотрудника, по которому резолвится планируемый маршрут. */ employeeEmail: string; requestTypeId: number | undefined; @@ -32,11 +39,6 @@ interface IRouteSource { reloadSeq?: number; } -const byPhase = - (execution: boolean): ((rows: IMatrixRow[]) => IMatrixRow[]) => - (rows: IMatrixRow[]): IMatrixRow[] => - rows.filter((r) => (r.phase === APPROVAL_PHASE.execution) === execution); - /** * Маршрут заявки по двум фазам: согласование + исполнение. Без отдельного * запроса фазы 'Execution' getPlannedMatrix исполнителей не резолвит — шаг @@ -44,25 +46,36 @@ const byPhase = * approval-route, форма его только получает. */ export function usePhasedRoute(source: IRouteSource): IPhasedRoute { - const { context, approval, requestId, employeeEmail, requestTypeId, editable, executorsEditable = false, reloadSeq = 0 } = source; - const canLoad = !!requestId || (!!employeeEmail && !!requestTypeId); + const { + context, + approval, + requestId, + replanEmptyDraft = false, + requestStatus, + employeeEmail, + requestTypeId, + editable, + executorsEditable = false, + reloadSeq = 0 + } = source; + const statusReady = !replanEmptyDraft || isOpenedRouteReady(requestId, requestStatus, requestTypeId); + const canLoad = statusReady && (!!requestId || (!!employeeEmail && !!requestTypeId)); const reloadKey = `${employeeEmail}-${requestId ?? 'new'}-${reloadSeq}`; const loadPhase = useCallback( - (execution: boolean) => - requestId - ? approval.getMatrixForRequest(requestId).then(byPhase(execution)) - : getPlannedMatrix( - context, - requestTypeId as number, - employeeEmail, - approval, - execution ? 'Execution' : 'Approval' - ), - [context, approval, requestId, employeeEmail, requestTypeId] + (phase: RoutePhase) => + loadRoutePhase( + approval, + requestId, + // Без replanEmptyDraft (просмотр) черновик не перепланируется — только записанные строки. + replanEmptyDraft ? requestStatus : undefined, + phase, + () => getPlannedMatrix(context, requestTypeId as number, employeeEmail, approval, phase) + ), + [context, approval, requestId, replanEmptyDraft, requestStatus, employeeEmail, requestTypeId] ); - const loadApprovers = useCallback(() => loadPhase(false), [loadPhase]); - const loadExecutors = useCallback(() => loadPhase(true), [loadPhase]); + const loadApprovers = useCallback(() => loadPhase('Approval'), [loadPhase]); + const loadExecutors = useCallback(() => loadPhase('Execution'), [loadPhase]); const jobTitle = (login: string): Promise => approval.getJobTitle(login); const approvers = useMatrixEditor(loadApprovers, reloadKey, editable, canLoad, jobTitle, resolveApproverOrgInfo(context)); diff --git a/src/webparts/requestPortal/shared/approval/useRouteExecutors.ts b/src/webparts/requestPortal/shared/approval/useRouteExecutors.ts index 6e87107..00d81fc 100644 --- a/src/webparts/requestPortal/shared/approval/useRouteExecutors.ts +++ b/src/webparts/requestPortal/shared/approval/useRouteExecutors.ts @@ -8,6 +8,7 @@ import { IMatrixRow } from './models/IMatrixRow'; import { APPROVAL_PHASE } from './models/statuses'; import { canEditExecutors } from './routeEditRights'; import { IUseMatrixEditor, useMatrixEditor } from './useMatrixEditor'; +import { isOpenedRouteReady, loadRoutePhase } from './loadRoutePhase'; /** Строки фазы согласования (всё, что не «Исполнение») — для таблицы согласующих. */ export const approvalPhaseRows = (rows: IMatrixRow[]): IMatrixRow[] => @@ -43,6 +44,11 @@ interface IFormExecutorsSource { userEmail: string; /** Редактируемая заявка — исполнители читаются из её строк, а не планируются заново. */ editId: number | undefined; + /** + * Статус открытой заявки: черновик без записанных строк маршрута планирует + * исполнителей заново (см. loadRoutePhase); пока не загружен — не грузим. + */ + requestStatus: string | undefined; /** Те же параметры, что форма отдаёт getPlannedMatrix для согласующих. */ departmentId?: string | null; // eslint-disable-line @rushstack/no-new-null -- транзитом в resolveRoute fieldValues?: IResolveFieldValue[]; @@ -60,22 +66,22 @@ interface IFormExecutorsSource { * (см. canEditExecutors) может добавлять и удалять исполнителей. */ export function useFormExecutors(source: IFormExecutorsSource): IUseMatrixEditor { - const { context, approval, requestTypeId, userEmail, editId, departmentId, fieldValues, projectId, reloadSuffix = '', readOnly = false } = source; + const { context, approval, requestTypeId, userEmail, editId, requestStatus, departmentId, fieldValues, projectId, reloadSuffix = '', readOnly = false } = source; const fieldValuesKey = JSON.stringify(fieldValues ?? []); const load = useCallback( () => - editId - ? approval.getMatrixForRequest(editId).then(executionPhaseRows) - : getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Execution', departmentId, fieldValues, projectId), + loadRoutePhase(approval, editId, requestStatus, 'Execution', () => + getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Execution', departmentId, fieldValues, projectId) + ), // fieldValues сравниваем по содержимому: форма пересоздаёт массив на каждый рендер. // eslint-disable-next-line react-hooks/exhaustive-deps - [context, approval, editId, requestTypeId, userEmail, departmentId, fieldValuesKey, projectId] + [context, approval, editId, requestStatus, requestTypeId, userEmail, departmentId, fieldValuesKey, projectId] ); return useMatrixEditor( load, `x-${userEmail}-${editId ?? 'new'}-${departmentId ?? ''}-${projectId ?? ''}-${fieldValuesKey}-${reloadSuffix}`, canEditExecutors(context, readOnly), - !!editId || (!!userEmail && !!requestTypeId), + isOpenedRouteReady(editId, requestStatus, requestTypeId) && (!!editId || (!!userEmail && !!requestTypeId)), (login) => approval.getJobTitle(login), resolveApproverOrgInfo(context) );