fix: черновик без маршрута планирует маршрут заново при открытии
У части черновиков не оказалось строк маршрута, и форма показывала пустую матрицу: отправить такую заявку было некому. Теперь при открытии черновика без строк маршрут планируется заново (loadRoutePhase), во всех формах; поданные заявки по-прежнему читают записанные строки. Почему строки пропали у первоначального случая — пока не выяснено. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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<IMatrixRow[]>
|
||||
): Promise<IMatrixRow[]> {
|
||||
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);
|
||||
}
|
||||
@@ -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<typeof useMatrixEditor>;
|
||||
|
||||
@@ -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<string> => approval.getJobTitle(login);
|
||||
|
||||
const approvers = useMatrixEditor(loadApprovers, reloadKey, editable, canLoad, jobTitle, resolveApproverOrgInfo(context));
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user