refactor: расчёт маршрута заявки в 1С вынесен в planRoute

Сбор условий маршрута (вид работ, база 1С, отдел) и запрос маршрута с
схлопыванием повторов переехали из формы в planRoute.ts. Поведение формы
не меняется; функцию сможет использовать страница «Проверка маршрутов»,
чтобы проверять ровно тот маршрут, что видит заявитель.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Тимур Городецкий
2026-10-15 12:31:59 +05:00
parent 99f3558e4a
commit ea6eb3663b
2 changed files with 55 additions and 21 deletions
@@ -21,7 +21,6 @@ import {
ApprovalHistory, ApprovalHistory,
StatusBadge, StatusBadge,
useMatrixEditor, useMatrixEditor,
getPlannedMatrix,
loadRoutePhase, loadRoutePhase,
isOpenedRouteReady, isOpenedRouteReady,
resolveWriteListId, resolveWriteListId,
@@ -57,7 +56,7 @@ import { RequestSection } from './sections/RequestSection';
import { ContactSection } from './sections/ContactSection'; import { ContactSection } from './sections/ContactSection';
import { useStableCallback } from '../../shared/useStableCallback'; import { useStableCallback } from '../../shared/useStableCallback';
import { applyManagerOverride, managerEmail } from './managerOverride'; import { applyManagerOverride, managerEmail } from './managerOverride';
import { dedupeRouteRows } from './dedupeRoute'; import { planTicket1CPhase, ticket1cRouteFieldValues } from './planRoute';
import { handoffToPlanner } from './plannerHandoff'; import { handoffToPlanner } from './plannerHandoff';
import { submitStatus } from './submitStatus'; import { submitStatus } from './submitStatus';
@@ -174,27 +173,19 @@ const Ticket1CForm: React.FC<IRequestFormProps> = ({
// см. fieldValues), а не выбирается заявителем. Поэтому матрица здесь ВСЕГДА // см. fieldValues), а не выбирается заявителем. Поэтому матрица здесь ВСЕГДА
// нередактируема (editable=false) — заявитель только видит, кто согласует, без // нередактируема (editable=false) — заявитель только видит, кто согласует, без
// возможности добавить/убрать/переставить согласующего. // возможности добавить/убрать/переставить согласующего.
// Базы 1С — мультивыбор, а approval-route принимает value только строкой: // Расчёт маршрута вынесен в planRoute.ts — им же пользуется страница
// передаём все выбранные базы одной записью через пробел (как CompaniesJson в // «Проверка маршрутов», чтобы проверять ровно то, что видит заявитель.
// RecruitingHire), условия в админке — «База 1С содержит ЗУП/АТП/ATS».
const databasesFieldValue = data.DB1C.slice().sort().join(' ');
const fieldValues = useMemo( const fieldValues = useMemo(
() => [ () => ticket1cRouteFieldValues({ WorkType: data.WorkType, DB1C: data.DB1C, Department: data.Department }),
{ fieldName: 'WorkType', value: data.WorkType }, [data.WorkType, data.DB1C, data.Department]
{ fieldName: 'DB1C', value: databasesFieldValue },
{ fieldName: 'Department', value: data.Department }
],
[data.WorkType, databasesFieldValue, data.Department]
); );
const matrixKey = `${userEmail}-${editId ?? 'new'}-${data.WorkType}-${databasesFieldValue}-${data.Department}`; const matrixKey = [userEmail, editId ?? 'new', ...fieldValues.map((f) => f.value)].join('-');
const matrixReady = const matrixReady =
isOpenedRouteReady(editId, requestStatus, requestTypeId) && (!!editId || (!!data.WorkType && !!requestTypeId)); isOpenedRouteReady(editId, requestStatus, requestTypeId) && (!!editId || (!!data.WorkType && !!requestTypeId));
const loadMatrix = useCallback( const loadMatrix = useCallback(
() => () =>
loadRoutePhase(approval, editId, requestStatus, 'Approval', async () => loadRoutePhase(approval, editId, requestStatus, 'Approval', () =>
dedupeRouteRows( planTicket1CPhase(context, requestTypeId as number, userEmail, approval, 'Approval', fieldValues)
await getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Approval', null, fieldValues)
)
), ),
[context, userEmail, approval, editId, requestStatus, fieldValues, requestTypeId] [context, userEmail, approval, editId, requestStatus, fieldValues, requestTypeId]
); );
@@ -204,10 +195,8 @@ const Ticket1CForm: React.FC<IRequestFormProps> = ({
// в карточке Planner, в портале отмечает «Выполнено» (см. plannerHandoff.ts). // в карточке Planner, в портале отмечает «Выполнено» (см. plannerHandoff.ts).
const loadExecutors = useCallback( const loadExecutors = useCallback(
() => () =>
loadRoutePhase(approval, editId, requestStatus, 'Execution', async () => loadRoutePhase(approval, editId, requestStatus, 'Execution', () =>
dedupeRouteRows( planTicket1CPhase(context, requestTypeId as number, userEmail, approval, 'Execution', fieldValues)
await getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Execution', null, fieldValues)
)
), ),
[context, userEmail, approval, editId, requestStatus, fieldValues, requestTypeId] [context, userEmail, approval, editId, requestStatus, fieldValues, requestTypeId]
); );
@@ -0,0 +1,45 @@
import { WebPartContext } from '@microsoft/sp-webpart-base';
import {
ApprovalListService,
ApprovalStatus,
getPlannedMatrix,
IMatrixRow,
IResolveFieldValue
} from '../../shared/approval';
import { IFormData } from './model';
import { dedupeRouteRows } from './dedupeRoute';
/** Поля заявки, от которых зависит маршрут 1С (условия шагов в админке маршрута #8). */
export type Ticket1CRouteInput = Pick<IFormData, 'WorkType' | 'DB1C' | 'Department'>;
/**
* Значения полей для условных шагов маршрута (fieldValues при resolve).
*
* Базы 1С — мультивыбор, а approval-route принимает value только строкой:
* все выбранные базы уходят одной записью через пробел (как CompaniesJson в
* RecruitingHire), условия в админке — «DB1C содержит ЗУП/АТП/ATS».
*/
export function ticket1cRouteFieldValues(input: Ticket1CRouteInput): IResolveFieldValue[] {
return [
{ fieldName: 'WorkType', value: input.WorkType },
{ fieldName: 'DB1C', value: input.DB1C.slice().sort().join(' ') },
{ fieldName: 'Department', value: input.Department }
];
}
/**
* Запланированный маршрут одной фазы заявки в 1С. Один источник для формы и для
* страницы «Проверка маршрутов» — проверка должна видеть ровно то, что увидит
* пользователь, включая схлопывание повторов на шаге.
*/
export async function planTicket1CPhase(
context: WebPartContext,
requestTypeId: number,
userEmail: string,
approval: ApprovalListService,
phase: ApprovalStatus,
fieldValues: IResolveFieldValue[]
): Promise<IMatrixRow[]> {
const rows = await getPlannedMatrix(context, requestTypeId, userEmail, approval, phase, null, fieldValues);
return dedupeRouteRows(rows);
}