баг с открытием заявки готов

This commit is contained in:
gorbenko.pavel
2026-08-18 11:32:05 +05:00
parent c85989230a
commit bc8a43ea35
5 changed files with 62 additions and 24 deletions
@@ -2,7 +2,7 @@ import * as React from 'react';
import type { WebPartContext } from '@microsoft/sp-webpart-base'; import type { WebPartContext } from '@microsoft/sp-webpart-base';
import styles from './ApproveFeature.module.scss'; import styles from './ApproveFeature.module.scss';
import { getViewComponent } from '../../forms/viewRegistry'; import { getViewComponent } from '../../forms/viewRegistry';
import { partitionForPageUrlAndListId } from '../../shared/approval'; import { isConfirmedStaleListId } from '../../shared/approval';
export interface IApproveFeatureProps { export interface IApproveFeatureProps {
context: WebPartContext; context: WebPartContext;
@@ -19,9 +19,10 @@ export interface IApproveFeatureProps {
const ApproveFeature: React.FC<IApproveFeatureProps> = ({ context, formKey, itemId, listId }) => { const ApproveFeature: React.FC<IApproveFeatureProps> = ({ context, formKey, itemId, listId }) => {
const ViewComponent = getViewComponent(formKey); const ViewComponent = getViewComponent(formKey);
// Партиция указана, но её нет в реестре — см. тот же комментарий в EditFeature. // Партиция указана и реестр ТОЧНО знает, что её там нет — см. тот же
// комментарий в EditFeature (isConfirmedStaleListId).
const partitionError = const partitionError =
listId && !partitionForPageUrlAndListId(formKey, listId) listId && isConfirmedStaleListId(formKey, listId)
? `Не удалось открыть заявку: список «${listId}» не найден в реестре партиций. Ссылка могла устареть — попробуйте открыть заявку заново из списка.` ? `Не удалось открыть заявку: список «${listId}» не найден в реестре партиций. Ссылка могла устареть — попробуйте открыть заявку заново из списка.`
: undefined; : undefined;
@@ -4,7 +4,7 @@ import styles from '../approve/ApproveFeature.module.scss';
import { getFormComponent } from '../../forms/registry'; import { getFormComponent } from '../../forms/registry';
import { REQUEST_TYPES } from '../../data/requests'; import { REQUEST_TYPES } from '../../data/requests';
import { emitRequestsChanged } from '../../shared/requestEvents'; import { emitRequestsChanged } from '../../shared/requestEvents';
import { partitionForPageUrlAndListId } from '../../shared/approval'; import { isConfirmedStaleListId } from '../../shared/approval';
export interface IEditFeatureProps { export interface IEditFeatureProps {
context: WebPartContext; context: WebPartContext;
@@ -33,12 +33,16 @@ const EditFeature: React.FC<IEditFeatureProps> = ({ context, formKey, itemId, li
return <div className={styles.state}>Для формы «{formKey}» редактирование не настроено.</div>; return <div className={styles.state}>Для формы «{formKey}» редактирование не настроено.</div>;
} }
// Партиция указана (заявки нумерованные, см. shared/approval/listPartitions.ts), // Партиция указана (заявки нумерованные, см. shared/approval/listPartitions.ts)
// но её нет в реестре — устаревшая/битая ссылка. Проверяем ЗДЕСЬ, ДО монтирования // и реестр ТОЧНО знает, что такого listId в серии типа нет (см.
// формы: внутри формы имя списка резолвится синхронно (useMemo) и там уже негде // isConfirmedStaleListId) — устаревшая/битая ссылка. Если реестр про серию
// красиво показать ошибку без риска уронить рендер всей панели — см. // просто ничего не знает (approval-route не ответил при старте) — это НЕ
// vacationApprovalService и аналоги в остальных формах. // повод отказывать: listId доверяем как есть, ровно как listTargetsForType в
if (listId && !partitionForPageUrlAndListId(formKey, listId)) { // myRequests.service.ts. Проверяем ЗДЕСЬ, ДО монтирования формы: внутри неё
// имя списка резолвится синхронно (useMemo) и там уже негде красиво показать
// ошибку без риска уронить рендер всей панели — см. vacationApprovalService и
// аналоги в остальных формах.
if (listId && isConfirmedStaleListId(formKey, listId)) {
return ( return (
<div className={styles.state}> <div className={styles.state}>
Не удалось открыть заявку: список «{listId}» не найден в реестре партиций. Ссылка могла устареть — Не удалось открыть заявку: список «{listId}» не найден в реестре партиций. Ссылка могла устареть —
@@ -29,6 +29,7 @@ export {
currentPartitionForTypeId, currentPartitionForTypeId,
currentPartitionForPageUrl, currentPartitionForPageUrl,
partitionForPageUrlAndListId, partitionForPageUrlAndListId,
isConfirmedStaleListId,
} from './listPartitions'; } from './listPartitions';
export { useRequestTypeId } from './useRequestTypeId'; export { useRequestTypeId } from './useRequestTypeId';
export type { IRequestTypeIdState } from './useRequestTypeId'; export type { IRequestTypeIdState } from './useRequestTypeId';
@@ -97,10 +97,9 @@ export function currentPartitionForPageUrl(pageUrl: string): IListPartition | un
* реестре, чтобы узнать имя списка (для парного списка согласования — см. * реестре, чтобы узнать имя списка (для парного списка согласования — см.
* shared/listTitles.ts, approvalListTitleForListId). * shared/listTitles.ts, approvalListTitleForListId).
* *
* undefined — партиции с таким listId в реестре нет (устарела ссылка, реестр ещё * undefined — либо реестр про тип вообще ничего не знает (серия пуста), либо
* не загрузился). Вызывающая сторона в этом случае должна показать ошибку, а не * partitions есть, но именно такого listId среди них нет. Эти два случая —
* молча откатываться на текущую партицию — иначе данные архивной заявки * РАЗНЫЕ по смыслу: см. isConfirmedStaleListId ниже, только он умеет их различать.
* читались/писались бы не в тот список.
*/ */
export function partitionForPageUrlAndListId(pageUrl: string, listId: string): IListPartition | undefined { export function partitionForPageUrlAndListId(pageUrl: string, listId: string): IListPartition | undefined {
const id = typeIdByPageUrl.get(pageUrl); const id = typeIdByPageUrl.get(pageUrl);
@@ -108,3 +107,24 @@ export function partitionForPageUrlAndListId(pageUrl: string, listId: string): I
const lower = listId.toLowerCase(); const lower = listId.toLowerCase();
return partitionsForTypeId(id).filter((p) => p.listId.toLowerCase() === lower)[0]; 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);
}
+22 -10
View File
@@ -1,5 +1,9 @@
import { RequestTypeId } from '../models/types'; import { RequestTypeId } from '../models/types';
import { currentPartitionForPageUrl, partitionForPageUrlAndListId } from './approval/listPartitions'; import {
currentPartitionForPageUrl,
isConfirmedStaleListId,
partitionForPageUrlAndListId
} from './approval/listPartitions';
/** /**
* ЕДИНЫЙ источник имён списков SharePoint. * ЕДИНЫЙ источник имён списков SharePoint.
@@ -98,22 +102,30 @@ export function approvalListTitle(id: RequestTypeId): string {
* Имя списка данных КОНКРЕТНОЙ партиции по её listId — для открытия/сохранения * Имя списка данных КОНКРЕТНОЙ партиции по её listId — для открытия/сохранения
* заявки, которая не обязательно лежит в текущей партиции (архив). * заявки, которая не обязательно лежит в текущей партиции (архив).
* *
* В отличие от dataListTitle() НИЧЕГО не подставляет по умолчанию: если listId * Бросает ТОЛЬКО когда реестр ТОЧНО знает, что такого listId в серии нет
* не нашёлся в реестре (устаревшая ссылка, партиция удалена из реестра), это * (isConfirmedStaleListId) — тогда мы рискуем прочитать/записать не тот список,
* означает, что мы рискуем прочитать/записать не тот список — правильный ответ * и правильный ответ — явная ошибка, а не молчаливый откат.
* тут явная ошибка, а не молчаливый откат на текущую партицию. *
* Если же реестр про серию просто ничего не знает (approval-route не ответил
* при старте) — это НЕ повод бросать: откатываемся на ту же конвенцию по номеру,
* что и dataListTitle() для текущей партиции без реестра. Сам listId при этом не
* используется как источник данных (данные читаются им напрямую, см. load.ts
* каждой формы) — здесь он нужен только чтобы вывести ИМЯ списка согласования
* по конвенции, а на неё влияет лишь номер партиции, не сам url.
*/ */
export function dataListTitleForListId(id: RequestTypeId, listId: string): string { export function dataListTitleForListId(id: RequestTypeId, listId: string): string {
const partition = partitionForPageUrlAndListId(id, listId); const partition = partitionForPageUrlAndListId(id, listId);
if (!partition) { if (partition?.title) return partition.title;
if (isConfirmedStaleListId(id, listId)) {
throw new Error( throw new Error(
`Список «${listId}» не найден в реестре партиций типа заявки «${id}» — возможно, ссылка устарела.` `Список «${listId}» не найден в реестре партиций типа заявки «${id}» — возможно, ссылка устарела.`
); );
} }
// Title кешируется в реестре на момент создания партиции — то же ограничение,
// что и у dataListTitle() для текущей партиции (см. её комментарий), поэтому // Партиция нашлась, но Title не закеширован (см. dataListTitle), либо реестр
// фолбэк на конвенцию по номеру здесь абсолютно тот же приём. // про серию вообще ничего не знает — оба случая закрываются одним фолбэком.
return partition.title || `${listTitleBase(id)} ${partition.number} — данные`; return `${listTitleBase(id)} ${partition?.number ?? FALLBACK_LIST_NUMBER} — данные`;
} }
/** Имя парного списка согласования конкретной партиции — см. dataListTitleForListId. */ /** Имя парного списка согласования конкретной партиции — см. dataListTitleForListId. */