refactor: общий хук маршрута по фазам (usePhasedRoute)

Хук, который грузит маршрут согласования отдельно по фазам «Согласование»
и «Исполнение», был в папке «Объяснительной по СКУД». Он понадобился и
заявке на командировку, поэтому перенесён в shared/approval под общим
именем. Поведение СКУД не меняется — поменялись только импорты.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Тимур Городецкий
2026-10-05 10:19:57 +05:00
parent 4c8410bdcc
commit e85c01fca3
5 changed files with 26 additions and 22 deletions
@@ -63,6 +63,8 @@ export { notifyActiveStep } from './notify.service';
export type { INotifyTarget } from './notify.service';
export { useMatrixEditor, findUnassignedStep, unassignedStepMessage } from './useMatrixEditor';
export type { IUseMatrixEditor } from './useMatrixEditor';
export { usePhasedRoute } from './usePhasedRoute';
export type { IPhasedRoute } from './usePhasedRoute';
export { resolveApproverOrgInfo } from './approverOrgInfo';
export type { IApproverOrgInfo } from './approverOrgInfo';
export { canEditRoute } from './routeEditRights';
@@ -0,0 +1,69 @@
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 { resolveApproverOrgInfo } from './approverOrgInfo';
import { useMatrixEditor } from './useMatrixEditor';
type MatrixEditor = ReturnType<typeof useMatrixEditor>;
export interface IPhasedRoute {
/** Согласующие — редактируемы заявителем в новой заявке / черновике / доработке. */
approvers: MatrixEditor;
/** Исполнители по маршруту (фаза «Исполнение») — только чтение. */
executors: MatrixEditor;
}
interface IRouteSource {
context: WebPartContext;
approval: ApprovalListService;
/** Заявка уже записана — маршрут читаем из её строк, а не планируем заново. */
requestId: number | undefined;
/** E-mail сотрудника, по которому резолвится планируемый маршрут. */
employeeEmail: string;
requestTypeId: number | undefined;
/** Можно ли править согласующих. */
editable: boolean;
/** Меняется после решения согласующего — перечитать строки. */
reloadSeq?: number;
}
const byPhase =
(execution: boolean): ((rows: IMatrixRow[]) => IMatrixRow[]) =>
(rows: IMatrixRow[]): IMatrixRow[] =>
rows.filter((r) => (r.phase === APPROVAL_PHASE.execution) === execution);
/**
* Маршрут заявки по двум фазам: согласование + исполнение. Без отдельного
* запроса фазы 'Execution' getPlannedMatrix исполнителей не резолвит — шаг
* исполнения из маршрута «пропадал» бы. Маршрут настраивают в админке
* approval-route, форма его только получает.
*/
export function usePhasedRoute(source: IRouteSource): IPhasedRoute {
const { context, approval, requestId, employeeEmail, requestTypeId, editable, reloadSeq = 0 } = source;
const canLoad = !!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]
);
const loadApprovers = useCallback(() => loadPhase(false), [loadPhase]);
const loadExecutors = useCallback(() => loadPhase(true), [loadPhase]);
const jobTitle = (login: string): Promise<string> => approval.getJobTitle(login);
const approvers = useMatrixEditor(loadApprovers, reloadKey, editable, canLoad, jobTitle, resolveApproverOrgInfo(context));
const executors = useMatrixEditor(loadExecutors, reloadKey, false, canLoad, jobTitle, resolveApproverOrgInfo(context));
return { approvers, executors };
}