Архивация списков #9

Merged
gorbenko.pavel merged 17 commits from archive into main 2026-08-19 09:07:53 +00:00
5 changed files with 62 additions and 24 deletions
Showing only changes of commit bc8a43ea35 - Show all commits
@@ -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<IApproveFeatureProps> = ({ context, formKey, itemId, listId }) => {
const ViewComponent = getViewComponent(formKey);
// Партиция указана, но её нет в реестре — см. тот же комментарий в EditFeature.
// Партиция указана и реестр ТОЧНО знает, что её там нет — см. тот же
// комментарий в EditFeature (isConfirmedStaleListId).
const partitionError =
listId && !partitionForPageUrlAndListId(formKey, listId)
listId && isConfirmedStaleListId(formKey, listId)
? `Не удалось открыть заявку: список «${listId}» не найден в реестре партиций. Ссылка могла устареть — попробуйте открыть заявку заново из списка.`
: undefined;
@@ -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<IEditFeatureProps> = ({ context, formKey, itemId, li
return <div className={styles.state}>Для формы «{formKey}» редактирование не настроено.</div>;
}
// Партиция указана (заявки нумерованные, см. 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 (
<div className={styles.state}>
Не удалось открыть заявку: список «{listId}» не найден в реестре партиций. Ссылка могла устареть —
@@ -29,6 +29,7 @@ export {
currentPartitionForTypeId,
currentPartitionForPageUrl,
partitionForPageUrlAndListId,
isConfirmedStaleListId,
} from './listPartitions';
export { useRequestTypeId } from './useRequestTypeId';
export type { IRequestTypeIdState } from './useRequestTypeId';
@@ -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);
}
+22 -10
View File
@@ -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. */