diff --git a/src/webparts/requestPortal/features/approve/ApproveFeature.tsx b/src/webparts/requestPortal/features/approve/ApproveFeature.tsx index 49a5d0a..6df2bcd 100644 --- a/src/webparts/requestPortal/features/approve/ApproveFeature.tsx +++ b/src/webparts/requestPortal/features/approve/ApproveFeature.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import type { WebPartContext } from '@microsoft/sp-webpart-base'; import styles from './ApproveFeature.module.scss'; import { getViewComponent } from '../../forms/viewRegistry'; -import { partitionForPageUrlAndListId } from '../../shared/approval'; +import { isConfirmedStaleListId } from '../../shared/approval'; export interface IApproveFeatureProps { context: WebPartContext; @@ -19,9 +19,10 @@ export interface IApproveFeatureProps { const ApproveFeature: React.FC = ({ context, formKey, itemId, listId }) => { const ViewComponent = getViewComponent(formKey); - // Партиция указана, но её нет в реестре — см. тот же комментарий в EditFeature. + // Партиция указана и реестр ТОЧНО знает, что её там нет — см. тот же + // комментарий в EditFeature (isConfirmedStaleListId). const partitionError = - listId && !partitionForPageUrlAndListId(formKey, listId) + listId && isConfirmedStaleListId(formKey, listId) ? `Не удалось открыть заявку: список «${listId}» не найден в реестре партиций. Ссылка могла устареть — попробуйте открыть заявку заново из списка.` : undefined; diff --git a/src/webparts/requestPortal/features/edit/EditFeature.tsx b/src/webparts/requestPortal/features/edit/EditFeature.tsx index 72a57b4..df2a4c0 100644 --- a/src/webparts/requestPortal/features/edit/EditFeature.tsx +++ b/src/webparts/requestPortal/features/edit/EditFeature.tsx @@ -4,7 +4,7 @@ import styles from '../approve/ApproveFeature.module.scss'; import { getFormComponent } from '../../forms/registry'; import { REQUEST_TYPES } from '../../data/requests'; import { emitRequestsChanged } from '../../shared/requestEvents'; -import { partitionForPageUrlAndListId } from '../../shared/approval'; +import { isConfirmedStaleListId } from '../../shared/approval'; export interface IEditFeatureProps { context: WebPartContext; @@ -33,12 +33,16 @@ const EditFeature: React.FC = ({ context, formKey, itemId, li return
Для формы «{formKey}» редактирование не настроено.
; } - // Партиция указана (заявки нумерованные, см. shared/approval/listPartitions.ts), - // но её нет в реестре — устаревшая/битая ссылка. Проверяем ЗДЕСЬ, ДО монтирования - // формы: внутри формы имя списка резолвится синхронно (useMemo) и там уже негде - // красиво показать ошибку без риска уронить рендер всей панели — см. - // vacationApprovalService и аналоги в остальных формах. - if (listId && !partitionForPageUrlAndListId(formKey, listId)) { + // Партиция указана (заявки нумерованные, см. shared/approval/listPartitions.ts) + // и реестр ТОЧНО знает, что такого listId в серии типа нет (см. + // isConfirmedStaleListId) — устаревшая/битая ссылка. Если реестр про серию + // просто ничего не знает (approval-route не ответил при старте) — это НЕ + // повод отказывать: listId доверяем как есть, ровно как listTargetsForType в + // myRequests.service.ts. Проверяем ЗДЕСЬ, ДО монтирования формы: внутри неё + // имя списка резолвится синхронно (useMemo) и там уже негде красиво показать + // ошибку без риска уронить рендер всей панели — см. vacationApprovalService и + // аналоги в остальных формах. + if (listId && isConfirmedStaleListId(formKey, listId)) { return (
Не удалось открыть заявку: список «{listId}» не найден в реестре партиций. Ссылка могла устареть — diff --git a/src/webparts/requestPortal/shared/approval/index.ts b/src/webparts/requestPortal/shared/approval/index.ts index 1ef398b..abc7170 100644 --- a/src/webparts/requestPortal/shared/approval/index.ts +++ b/src/webparts/requestPortal/shared/approval/index.ts @@ -29,6 +29,7 @@ export { currentPartitionForTypeId, currentPartitionForPageUrl, partitionForPageUrlAndListId, + isConfirmedStaleListId, } from './listPartitions'; export { useRequestTypeId } from './useRequestTypeId'; export type { IRequestTypeIdState } from './useRequestTypeId'; diff --git a/src/webparts/requestPortal/shared/approval/listPartitions.ts b/src/webparts/requestPortal/shared/approval/listPartitions.ts index 5bfa377..be98070 100644 --- a/src/webparts/requestPortal/shared/approval/listPartitions.ts +++ b/src/webparts/requestPortal/shared/approval/listPartitions.ts @@ -97,10 +97,9 @@ export function currentPartitionForPageUrl(pageUrl: string): IListPartition | un * реестре, чтобы узнать имя списка (для парного списка согласования — см. * shared/listTitles.ts, approvalListTitleForListId). * - * undefined — партиции с таким listId в реестре нет (устарела ссылка, реестр ещё - * не загрузился). Вызывающая сторона в этом случае должна показать ошибку, а не - * молча откатываться на текущую партицию — иначе данные архивной заявки - * читались/писались бы не в тот список. + * undefined — либо реестр про тип вообще ничего не знает (серия пуста), либо + * partitions есть, но именно такого listId среди них нет. Эти два случая — + * РАЗНЫЕ по смыслу: см. isConfirmedStaleListId ниже, только он умеет их различать. */ export function partitionForPageUrlAndListId(pageUrl: string, listId: string): IListPartition | undefined { const id = typeIdByPageUrl.get(pageUrl); @@ -108,3 +107,24 @@ export function partitionForPageUrlAndListId(pageUrl: string, listId: string): I const lower = listId.toLowerCase(); return partitionsForTypeId(id).filter((p) => p.listId.toLowerCase() === lower)[0]; } + +/** + * true — ТОЛЬКО когда реестр про серию этого типа реально что-то знает (она не + * пуста) и listId в неё не входит. Тогда ссылка гарантированно битая/устаревшая + * (например, партицию удалили из реестра руками) — вот здесь уместна ошибка. + * + * Если же серия пуста — approval-route не ответил при старте, тип только что + * появился, реестр ещё не успел зарегистрировать эту серию — мы просто НЕ ЗНАЕМ, + * валиден ли listId, и вины на нём нет. В этом случае false: тот же приём + * «доверять RequestType.listId без реестра», что и у listTargetsForType в + * myRequests.service.ts — иначе временная недоступность approval-route выглядела + * бы как «эта заявка вообще не открывается», хотя ссылка совершенно рабочая. + */ +export function isConfirmedStaleListId(pageUrl: string, listId: string): boolean { + const id = typeIdByPageUrl.get(pageUrl); + if (id === undefined) return false; + const series = partitionsForTypeId(id); + if (series.length === 0) return false; + const lower = listId.toLowerCase(); + return !series.some((p) => p.listId.toLowerCase() === lower); +} diff --git a/src/webparts/requestPortal/shared/listTitles.ts b/src/webparts/requestPortal/shared/listTitles.ts index 52d0f47..fa0f882 100644 --- a/src/webparts/requestPortal/shared/listTitles.ts +++ b/src/webparts/requestPortal/shared/listTitles.ts @@ -1,5 +1,9 @@ import { RequestTypeId } from '../models/types'; -import { currentPartitionForPageUrl, partitionForPageUrlAndListId } from './approval/listPartitions'; +import { + currentPartitionForPageUrl, + isConfirmedStaleListId, + partitionForPageUrlAndListId +} from './approval/listPartitions'; /** * ЕДИНЫЙ источник имён списков SharePoint. @@ -98,22 +102,30 @@ export function approvalListTitle(id: RequestTypeId): string { * Имя списка данных КОНКРЕТНОЙ партиции по её listId — для открытия/сохранения * заявки, которая не обязательно лежит в текущей партиции (архив). * - * В отличие от dataListTitle() НИЧЕГО не подставляет по умолчанию: если listId - * не нашёлся в реестре (устаревшая ссылка, партиция удалена из реестра), это - * означает, что мы рискуем прочитать/записать не тот список — правильный ответ - * тут явная ошибка, а не молчаливый откат на текущую партицию. + * Бросает ТОЛЬКО когда реестр ТОЧНО знает, что такого listId в серии нет + * (isConfirmedStaleListId) — тогда мы рискуем прочитать/записать не тот список, + * и правильный ответ — явная ошибка, а не молчаливый откат. + * + * Если же реестр про серию просто ничего не знает (approval-route не ответил + * при старте) — это НЕ повод бросать: откатываемся на ту же конвенцию по номеру, + * что и dataListTitle() для текущей партиции без реестра. Сам listId при этом не + * используется как источник данных (данные читаются им напрямую, см. load.ts + * каждой формы) — здесь он нужен только чтобы вывести ИМЯ списка согласования + * по конвенции, а на неё влияет лишь номер партиции, не сам url. */ export function dataListTitleForListId(id: RequestTypeId, listId: string): string { const partition = partitionForPageUrlAndListId(id, listId); - if (!partition) { + if (partition?.title) return partition.title; + + if (isConfirmedStaleListId(id, listId)) { throw new Error( `Список «${listId}» не найден в реестре партиций типа заявки «${id}» — возможно, ссылка устарела.` ); } - // Title кешируется в реестре на момент создания партиции — то же ограничение, - // что и у dataListTitle() для текущей партиции (см. её комментарий), поэтому - // фолбэк на конвенцию по номеру здесь абсолютно тот же приём. - return partition.title || `${listTitleBase(id)} ${partition.number} — данные`; + + // Партиция нашлась, но Title не закеширован (см. dataListTitle), либо реестр + // про серию вообще ничего не знает — оба случая закрываются одним фолбэком. + return `${listTitleBase(id)} ${partition?.number ?? FALLBACK_LIST_NUMBER} — данные`; } /** Имя парного списка согласования конкретной партиции — см. dataListTitleForListId. */