diff --git a/src/webparts/requestPortal/forms/Ticket1C/Ticket1CForm.tsx b/src/webparts/requestPortal/forms/Ticket1C/Ticket1CForm.tsx index 8476f3f..bf4d696 100644 --- a/src/webparts/requestPortal/forms/Ticket1C/Ticket1CForm.tsx +++ b/src/webparts/requestPortal/forms/Ticket1C/Ticket1CForm.tsx @@ -21,7 +21,6 @@ import { ApprovalHistory, StatusBadge, useMatrixEditor, - getPlannedMatrix, loadRoutePhase, isOpenedRouteReady, resolveWriteListId, @@ -57,7 +56,7 @@ import { RequestSection } from './sections/RequestSection'; import { ContactSection } from './sections/ContactSection'; import { useStableCallback } from '../../shared/useStableCallback'; import { applyManagerOverride, managerEmail } from './managerOverride'; -import { dedupeRouteRows } from './dedupeRoute'; +import { planTicket1CPhase, ticket1cRouteFieldValues } from './planRoute'; import { handoffToPlanner } from './plannerHandoff'; import { submitStatus } from './submitStatus'; @@ -174,27 +173,19 @@ const Ticket1CForm: React.FC = ({ // см. fieldValues), а не выбирается заявителем. Поэтому матрица здесь ВСЕГДА // нередактируема (editable=false) — заявитель только видит, кто согласует, без // возможности добавить/убрать/переставить согласующего. - // Базы 1С — мультивыбор, а approval-route принимает value только строкой: - // передаём все выбранные базы одной записью через пробел (как CompaniesJson в - // RecruitingHire), условия в админке — «База 1С содержит ЗУП/АТП/ATS». - const databasesFieldValue = data.DB1C.slice().sort().join(' '); + // Расчёт маршрута вынесен в planRoute.ts — им же пользуется страница + // «Проверка маршрутов», чтобы проверять ровно то, что видит заявитель. const fieldValues = useMemo( - () => [ - { fieldName: 'WorkType', value: data.WorkType }, - { fieldName: 'DB1C', value: databasesFieldValue }, - { fieldName: 'Department', value: data.Department } - ], - [data.WorkType, databasesFieldValue, data.Department] + () => ticket1cRouteFieldValues({ WorkType: data.WorkType, DB1C: data.DB1C, Department: data.Department }), + [data.WorkType, data.DB1C, 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 = isOpenedRouteReady(editId, requestStatus, requestTypeId) && (!!editId || (!!data.WorkType && !!requestTypeId)); const loadMatrix = useCallback( () => - loadRoutePhase(approval, editId, requestStatus, 'Approval', async () => - dedupeRouteRows( - await getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Approval', null, fieldValues) - ) + loadRoutePhase(approval, editId, requestStatus, 'Approval', () => + planTicket1CPhase(context, requestTypeId as number, userEmail, approval, 'Approval', fieldValues) ), [context, userEmail, approval, editId, requestStatus, fieldValues, requestTypeId] ); @@ -204,10 +195,8 @@ const Ticket1CForm: React.FC = ({ // в карточке Planner, в портале отмечает «Выполнено» (см. plannerHandoff.ts). const loadExecutors = useCallback( () => - loadRoutePhase(approval, editId, requestStatus, 'Execution', async () => - dedupeRouteRows( - await getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Execution', null, fieldValues) - ) + loadRoutePhase(approval, editId, requestStatus, 'Execution', () => + planTicket1CPhase(context, requestTypeId as number, userEmail, approval, 'Execution', fieldValues) ), [context, userEmail, approval, editId, requestStatus, fieldValues, requestTypeId] ); diff --git a/src/webparts/requestPortal/forms/Ticket1C/planRoute.ts b/src/webparts/requestPortal/forms/Ticket1C/planRoute.ts new file mode 100644 index 0000000..8efc6bd --- /dev/null +++ b/src/webparts/requestPortal/forms/Ticket1C/planRoute.ts @@ -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; + +/** + * Значения полей для условных шагов маршрута (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 { + const rows = await getPlannedMatrix(context, requestTypeId, userEmail, approval, phase, null, fieldValues); + return dedupeRouteRows(rows); +}