refactor: общий хук маршрута по фазам (usePhasedRoute)
Хук, который грузит маршрут согласования отдельно по фазам «Согласование» и «Исполнение», был в папке «Объяснительной по СКУД». Он понадобился и заявке на командировку, поэтому перенесён в shared/approval под общим именем. Поведение СКУД не меняется — поменялись только импорты. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,7 @@
|
|||||||
## Маршрут
|
## Маршрут
|
||||||
- Фазы: согласование (напр. непосредственный руководитель) + исполнение (напр. кадры) —
|
- Фазы: согласование (напр. непосредственный руководитель) + исполнение (напр. кадры) —
|
||||||
обе фазы грузятся отдельными запросами `getPlannedMatrix(..., 'Approval' | 'Execution')`
|
обе фазы грузятся отдельными запросами `getPlannedMatrix(..., 'Approval' | 'Execution')`
|
||||||
в `useSkudRoute` (паттерн Referral). Без запроса 'Execution' шаг исполнения пропадает.
|
в общем `usePhasedRoute` (shared/approval, паттерн Referral). Без запроса 'Execution' шаг исполнения пропадает.
|
||||||
- Планируемый маршрут резолвится по e-mail сотрудника; согласующих заявитель может
|
- Планируемый маршрут резолвится по e-mail сотрудника; согласующих заявитель может
|
||||||
править (новая / черновик / возврат), исполнителей — нет (только чтение).
|
править (новая / черновик / возврат), исполнителей — нет (только чтение).
|
||||||
- Нет согласующих вовсе — `initialSubmitStatus` сразу ставит «Выполнение».
|
- Нет согласующих вовсе — `initialSubmitStatus` сразу ставит «Выполнение».
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ import {
|
|||||||
initialSubmitStatus,
|
initialSubmitStatus,
|
||||||
IApproverToWrite,
|
IApproverToWrite,
|
||||||
unassignedStepMessage,
|
unassignedStepMessage,
|
||||||
resolveWriteListId
|
resolveWriteListId,
|
||||||
|
usePhasedRoute
|
||||||
} from '../../shared/approval';
|
} from '../../shared/approval';
|
||||||
import { IRequestFormProps } from '../../models/types';
|
import { IRequestFormProps } from '../../models/types';
|
||||||
import { getEmployeeOrganizations, formatEmployeeOrganizations } from '../../shared/dictionary';
|
import { getEmployeeOrganizations, formatEmployeeOrganizations } from '../../shared/dictionary';
|
||||||
@@ -43,7 +44,6 @@ import { loadSkudExplanationRequest } from './load';
|
|||||||
import { submittedConflictMessage } from './submittedDays';
|
import { submittedConflictMessage } from './submittedDays';
|
||||||
import { getSubmittedDays } from './submittedDays.service';
|
import { getSubmittedDays } from './submittedDays.service';
|
||||||
import { useSubmittedDays } from './useSubmittedDays';
|
import { useSubmittedDays } from './useSubmittedDays';
|
||||||
import { useSkudRoute } from './useSkudRoute';
|
|
||||||
import { todayDateKey } from './skud/skudDays';
|
import { todayDateKey } from './skud/skudDays';
|
||||||
import { EmployeeSection } from './sections/EmployeeSection';
|
import { EmployeeSection } from './sections/EmployeeSection';
|
||||||
import { DaysSection } from './sections/DaysSection';
|
import { DaysSection } from './sections/DaysSection';
|
||||||
@@ -126,7 +126,7 @@ const SkudExplanationForm: React.FC<IRequestFormProps> = ({
|
|||||||
}, [editId, context]);
|
}, [editId, context]);
|
||||||
|
|
||||||
// Маршрут по сотруднику; у открытой заявки — из уже записанных строк, чтобы не затереть правки.
|
// Маршрут по сотруднику; у открытой заявки — из уже записанных строк, чтобы не затереть правки.
|
||||||
const { approvers: matrix, executors } = useSkudRoute({
|
const { approvers: matrix, executors } = usePhasedRoute({
|
||||||
context,
|
context,
|
||||||
approval,
|
approval,
|
||||||
requestId: editId,
|
requestId: editId,
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import {
|
|||||||
StatusBadge,
|
StatusBadge,
|
||||||
ApprovalHistory,
|
ApprovalHistory,
|
||||||
notifyActiveStep,
|
notifyActiveStep,
|
||||||
useRequestTypeId
|
useRequestTypeId,
|
||||||
|
usePhasedRoute
|
||||||
} from '../../shared/approval';
|
} from '../../shared/approval';
|
||||||
import type { ISiteUser } from '../../shared/approval';
|
import type { ISiteUser } from '../../shared/approval';
|
||||||
import { friendlyErrorMessage } from '../../shared/diagnostics';
|
import { friendlyErrorMessage } from '../../shared/diagnostics';
|
||||||
@@ -23,7 +24,6 @@ import { useStableCallback } from '../../shared/useStableCallback';
|
|||||||
import { FORM_ID, FORM_TITLE, IFormData, EMPTY_DATA, SetField } from './model';
|
import { FORM_ID, FORM_TITLE, IFormData, EMPTY_DATA, SetField } from './model';
|
||||||
import { loadSkudExplanationRequest } from './load';
|
import { loadSkudExplanationRequest } from './load';
|
||||||
import { skudExplanationApprovalService } from './submit';
|
import { skudExplanationApprovalService } from './submit';
|
||||||
import { useSkudRoute } from './useSkudRoute';
|
|
||||||
import { EmployeeSection } from './sections/EmployeeSection';
|
import { EmployeeSection } from './sections/EmployeeSection';
|
||||||
import { DaysSection } from './sections/DaysSection';
|
import { DaysSection } from './sections/DaysSection';
|
||||||
import { ExplanationSection } from './sections/ExplanationSection';
|
import { ExplanationSection } from './sections/ExplanationSection';
|
||||||
@@ -65,7 +65,7 @@ const SkudExplanationView: React.FC<ISkudExplanationViewProps> = ({ context, ite
|
|||||||
|
|
||||||
// decisionSeq: после решения строки маршрута меняются — перечитываем обе фазы.
|
// decisionSeq: после решения строки маршрута меняются — перечитываем обе фазы.
|
||||||
const [decisionSeq, setDecisionSeq] = useState<number>(0);
|
const [decisionSeq, setDecisionSeq] = useState<number>(0);
|
||||||
const { approvers, executors } = useSkudRoute({
|
const { approvers, executors } = usePhasedRoute({
|
||||||
context,
|
context,
|
||||||
approval,
|
approval,
|
||||||
requestId: itemId,
|
requestId: itemId,
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ export { notifyActiveStep } from './notify.service';
|
|||||||
export type { INotifyTarget } from './notify.service';
|
export type { INotifyTarget } from './notify.service';
|
||||||
export { useMatrixEditor, findUnassignedStep, unassignedStepMessage } from './useMatrixEditor';
|
export { useMatrixEditor, findUnassignedStep, unassignedStepMessage } from './useMatrixEditor';
|
||||||
export type { IUseMatrixEditor } from './useMatrixEditor';
|
export type { IUseMatrixEditor } from './useMatrixEditor';
|
||||||
|
export { usePhasedRoute } from './usePhasedRoute';
|
||||||
|
export type { IPhasedRoute } from './usePhasedRoute';
|
||||||
export { resolveApproverOrgInfo } from './approverOrgInfo';
|
export { resolveApproverOrgInfo } from './approverOrgInfo';
|
||||||
export type { IApproverOrgInfo } from './approverOrgInfo';
|
export type { IApproverOrgInfo } from './approverOrgInfo';
|
||||||
export { canEditRoute } from './routeEditRights';
|
export { canEditRoute } from './routeEditRights';
|
||||||
|
|||||||
+17
-15
@@ -1,17 +1,15 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { WebPartContext } from '@microsoft/sp-webpart-base';
|
import { WebPartContext } from '@microsoft/sp-webpart-base';
|
||||||
import {
|
import { APPROVAL_PHASE } from './models/statuses';
|
||||||
APPROVAL_PHASE,
|
import { IMatrixRow } from './models/IMatrixRow';
|
||||||
ApprovalListService,
|
import { ApprovalListService } from './approvalList.service';
|
||||||
IMatrixRow,
|
import { getPlannedMatrix } from './approvalSubmission.service';
|
||||||
getPlannedMatrix,
|
import { resolveApproverOrgInfo } from './approverOrgInfo';
|
||||||
resolveApproverOrgInfo,
|
import { useMatrixEditor } from './useMatrixEditor';
|
||||||
useMatrixEditor
|
|
||||||
} from '../../shared/approval';
|
|
||||||
|
|
||||||
type MatrixEditor = ReturnType<typeof useMatrixEditor>;
|
type MatrixEditor = ReturnType<typeof useMatrixEditor>;
|
||||||
|
|
||||||
export interface ISkudRoute {
|
export interface IPhasedRoute {
|
||||||
/** Согласующие — редактируемы заявителем в новой заявке / черновике / доработке. */
|
/** Согласующие — редактируемы заявителем в новой заявке / черновике / доработке. */
|
||||||
approvers: MatrixEditor;
|
approvers: MatrixEditor;
|
||||||
/** Исполнители по маршруту (фаза «Исполнение») — только чтение. */
|
/** Исполнители по маршруту (фаза «Исполнение») — только чтение. */
|
||||||
@@ -23,6 +21,7 @@ interface IRouteSource {
|
|||||||
approval: ApprovalListService;
|
approval: ApprovalListService;
|
||||||
/** Заявка уже записана — маршрут читаем из её строк, а не планируем заново. */
|
/** Заявка уже записана — маршрут читаем из её строк, а не планируем заново. */
|
||||||
requestId: number | undefined;
|
requestId: number | undefined;
|
||||||
|
/** E-mail сотрудника, по которому резолвится планируемый маршрут. */
|
||||||
employeeEmail: string;
|
employeeEmail: string;
|
||||||
requestTypeId: number | undefined;
|
requestTypeId: number | undefined;
|
||||||
/** Можно ли править согласующих. */
|
/** Можно ли править согласующих. */
|
||||||
@@ -31,15 +30,18 @@ interface IRouteSource {
|
|||||||
reloadSeq?: number;
|
reloadSeq?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const byPhase = (execution: boolean) => (rows: IMatrixRow[]): IMatrixRow[] =>
|
const byPhase =
|
||||||
rows.filter((r) => (r.phase === APPROVAL_PHASE.execution) === execution);
|
(execution: boolean): ((rows: IMatrixRow[]) => IMatrixRow[]) =>
|
||||||
|
(rows: IMatrixRow[]): IMatrixRow[] =>
|
||||||
|
rows.filter((r) => (r.phase === APPROVAL_PHASE.execution) === execution);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Маршрут объяснительной по двум фазам. Без отдельного запроса фазы
|
* Маршрут заявки по двум фазам: согласование + исполнение. Без отдельного
|
||||||
* 'Execution' getPlannedMatrix исполнителей не резолвит — шаг кадров из
|
* запроса фазы 'Execution' getPlannedMatrix исполнителей не резолвит — шаг
|
||||||
* маршрута «пропадал» бы. Паттерн тот же, что в Referral.
|
* исполнения из маршрута «пропадал» бы. Маршрут настраивают в админке
|
||||||
|
* approval-route, форма его только получает.
|
||||||
*/
|
*/
|
||||||
export function useSkudRoute(source: IRouteSource): ISkudRoute {
|
export function usePhasedRoute(source: IRouteSource): IPhasedRoute {
|
||||||
const { context, approval, requestId, employeeEmail, requestTypeId, editable, reloadSeq = 0 } = source;
|
const { context, approval, requestId, employeeEmail, requestTypeId, editable, reloadSeq = 0 } = source;
|
||||||
const canLoad = !!requestId || (!!employeeEmail && !!requestTypeId);
|
const canLoad = !!requestId || (!!employeeEmail && !!requestTypeId);
|
||||||
const reloadKey = `${employeeEmail}-${requestId ?? 'new'}-${reloadSeq}`;
|
const reloadKey = `${employeeEmail}-${requestId ?? 'new'}-${reloadSeq}`;
|
||||||
Reference in New Issue
Block a user