From 53e1d58ffeafa4a618cac481d8d2287335a9fe7e Mon Sep 17 00:00:00 2001 From: "gorbenko.pavel" Date: Fri, 14 Aug 2026 15:31:27 +0500 Subject: [PATCH] =?UTF-8?q?=D0=B1=D0=B0=D1=82=D1=87=D0=B8=D0=BD=D0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shared/approval/approvalList.service.ts | 47 +++++++++++++++---- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/src/webparts/requestPortal/shared/approval/approvalList.service.ts b/src/webparts/requestPortal/shared/approval/approvalList.service.ts index d145a60..7c0acc6 100644 --- a/src/webparts/requestPortal/shared/approval/approvalList.service.ts +++ b/src/webparts/requestPortal/shared/approval/approvalList.service.ts @@ -5,6 +5,7 @@ import { IList } from '@pnp/sp/lists'; import '@pnp/sp/items'; import '@pnp/sp/site-users/web'; import '@pnp/sp/profiles'; +import '@pnp/sp/batching'; import { getImpersonatedEmail } from '../impersonation'; import { IApprovalRow } from './models/IApprovalRow'; import { IMatrixRow } from './models/IMatrixRow'; @@ -106,11 +107,31 @@ export class ApprovalListService { ); } - for (const { a, order } of withOrder.filter((x) => !!x.a.email)) { - const approverId = await this.resolveUserId(a.email); - await this.list().items.add({ + const toWrite = withOrder.filter((x) => !!x.a.email); + if (toWrite.length === 0) return; + + // Резолв Id согласующих — независимые запросы (ensureUser по email), + // ждать их по очереди незачем. На длинном маршруте (десяток+ шагов) это + // было основным источником задержки наравне с самой записью строк ниже. + const approverIds = await Promise.all(toWrite.map(({ a }) => this.resolveUserId(a.email))); + + // Сами строки — ОДНИМ HTTP-запросом через batch API SharePoint, а не по + // POST на строку: add() на batched-инстансе не уходит в сеть сразу, а + // копится и отправляется одним вызовом по execute(). + // + // ВАЖНО: batchedList (сам список) переиспользуем, а .items достаём ЗАНОВО + // на каждой итерации — PnPjs один и тот же Items-инстанс во второй add() + // внутри одного батча не даёт («This instance is already part of a batch», + // см. docs/concepts/batching#reuse). .getById(id) ниже в deleteForRequest + // этой проблемы не имеет — он и так создаёт новый инстанс на каждый вызов. + const [batchedSP, execute] = getSP().batched(); + const batchedList = batchedSP.web.lists.getByTitle(this.listTitle); + toWrite.forEach(({ a, order }, i) => { + // void: промис от add() на batched-инстансе нарочно не ждём поштучно — + // он разрешится вместе со всеми остальными после execute() ниже. + void batchedList.items.add({ Title: a.position || a.email, - [`${FIELDS.approver}Id`]: approverId, + [`${FIELDS.approver}Id`]: approverIds[i], [FIELDS.status]: status, [`${FIELDS.ticket}Id`]: requestId, [FIELDS.order]: order, @@ -120,7 +141,8 @@ export class ApprovalListService { [FIELDS.slaHours]: a.slaHours ?? null, [FIELDS.askOverdueReason]: a.askOverdueReason ?? null, }); - } + }); + await execute(); } // Продвигает очередь: среди ещё не активных строк со статусом «Ждёт очереди» @@ -344,9 +366,18 @@ export class ApprovalListService { // Удаляет все строки согласования по заявке (при возврате/повторной отправке). public async deleteForRequest(requestId: number): Promise { const rows = await this.getForRequest(requestId); - for (const row of rows) { - await this.list().items.getById(row.id).delete(); - } + if (rows.length === 0) return; + + // writeRoute вызывает это перед перезаписью (пересохранение черновика, + // повторная отправка) — тот же per-row POST/DELETE в цикле, что и + // раньше в createSteps, и то же решение: один batch вместо N запросов. + const [batchedSP, execute] = getSP().batched(); + const batchedItems = batchedSP.web.lists.getByTitle(this.listTitle).items; + rows.forEach((row) => { + // void — по той же причине, что и в createSteps выше. + void batchedItems.getById(row.id).delete(); + }); + await execute(); } // Id текущего пользователя на сайте (для сравнения с полем «Согласующий»).