// Общая механика «шагов, которые могут идти параллельно» — 1:1 повторяет // request-portal/src/webparts/requestPortal/shared/approval/useMatrixEditor.ts // (blockRanges/swapBlocks/withOrders/markParallel), только обобщена под // generic-тип с полями order/parallelWithPrev, чтобы работать и с // ManagedApprovalRow здесь, и не тащить зависимость на request-portal. // // Параллельность кодируется ЧЕРЕЗ РАВНЫЙ order у соседних строк — отдельного // флага в SharePoint нет (см. FIELDS в requestManageService.ts: там только // StepOrder). parallelWithPrev — это производное поле, вычисляемое на клиенте // из совпадения order с предыдущей строкой (см. markParallel), а не то, что // хранится в списке. export interface IOrderedItem { order: number; parallelWithPrev?: boolean; } export interface IBlockRange { start: number; end: number; } /** Разбивает строки (уже отсортированные по order) на шаги — блоки с общим order. */ export function blockRanges(rows: T[]): IBlockRange[] { const ranges: IBlockRange[] = []; rows.forEach((r, i) => { if (i === 0 || !r.parallelWithPrev) ranges.push({ start: i, end: i }); else ranges[ranges.length - 1].end = i; }); return ranges; } export function blockIndexOf(ranges: IBlockRange[], row: number): number { for (let i = 0; i < ranges.length; i += 1) { if (row >= ranges[i].start && row <= ranges[i].end) return i; } return -1; } /** Меняет местами два блока (диапазона индексов), не трогая порядок строк внутри них. */ export function swapBlocks(rows: T[], a: IBlockRange, b: IBlockRange): T[] { return [ ...rows.slice(0, a.start), ...rows.slice(b.start, b.end + 1), ...rows.slice(a.start, a.end + 1), ...rows.slice(b.end + 1) ]; } /** * Пересчитывает order начиная с floor+1: у параллельных строк (parallelWithPrev) * order общий с предыдущей, у остальных — на единицу больше. Вызывается после * КАЖДОГО перемещения/переключения параллельности, иначе order разъедется с тем, * что показано на экране. floor — order последней ЗАБЛОКИРОВАННОЙ (уже решённой) * строки фазы; строки после неё не могут получить order <= floor — это и есть * гарантия «нельзя поставить согласующего выше того, кто уже одобрил». */ export function withOrdersFrom(rows: T[], floor: number): T[] { let order = floor; return rows.map((r, i) => { const parallel = i > 0 && !!r.parallelWithPrev; if (!parallel) order += 1; return { ...r, order, parallelWithPrev: parallel }; }); } /** Заполняет parallelWithPrev по факту совпадения order с предыдущей строкой (строки уже отсортированы по order). */ export function markParallel(rows: T[]): T[] { return rows.map((r, i) => ({ ...r, parallelWithPrev: i > 0 && r.order === rows[i - 1].order })); }