From 09a08897416830586bae71829e93fffdec3030c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A2=D0=B8=D0=BC=D1=83=D1=80=20=D0=93=D0=BE=D1=80=D0=BE?= =?UTF-8?q?=D0=B4=D0=B5=D1=86=D0=BA=D0=B8=D0=B9?= Date: Tue, 27 Oct 2026 16:52:37 +0500 Subject: [PATCH] =?UTF-8?q?fix(ct):=20=D0=BF=D0=BE=D1=81=D1=82=D0=B0=D0=B2?= =?UTF-8?q?=D1=89=D0=B8=D0=BA=20=D0=A8=D0=B0=D0=B3=D0=B0=203=20=D0=BF?= =?UTF-8?q?=D0=BE=D1=81=D0=BB=D0=B5=20=D1=87=D0=B5=D1=80=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=B8=D0=BA=D0=B0=20=D0=BD=D0=B5=20=D0=B7=D0=B0=D0=B4=D0=B2?= =?UTF-8?q?=D0=B0=D0=B8=D0=B2=D0=B0=D0=B5=D1=82=D1=81=D1=8F=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B8=20=D0=BE=D1=82=D0=BA=D1=80=D1=8B=D1=82=D0=B8=D0=B8=20?= =?UTF-8?q?=D0=A1=D0=A2=20(#8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit «Сохранить черновик» оставляет поставщика, добавленного на Шаге 3, в списке шага, а в SP он уже лежит колонкой «поставщик_заглушка». Если других поставщиков в СТ нет, при открытии из реестра колонки собирались из SP — и поставщик приходил дважды: из SP без цен и из сессии с ценами (подписи колонок именем файла-заглушки .txt и «КП 2»). Теперь колонки из SP, которые уже есть поставщиком Шага 3, не добавляются, а их офферы идут к нему. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../forms/ct/components/CtCreateForm.tsx | 14 +++++++--- .../components/utils/supplierColumns.test.ts | 27 ++++++++++++++++++- .../ct/components/utils/supplierColumns.ts | 16 +++++++++++ 3 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/webparts/ctFormsPortal/forms/ct/components/CtCreateForm.tsx b/src/webparts/ctFormsPortal/forms/ct/components/CtCreateForm.tsx index 7a9173e..619ae6a 100644 --- a/src/webparts/ctFormsPortal/forms/ct/components/CtCreateForm.tsx +++ b/src/webparts/ctFormsPortal/forms/ct/components/CtCreateForm.tsx @@ -9,7 +9,7 @@ import { Step1PositionSelection } from './Step1PositionSelection/Step1PositionSe import { Step2CommercialOffers } from './Step2CommercialOffers/Step2CommercialOffers'; import { Step3ComparisonTable } from './Step3ComparisonTable/Step3ComparisonTable'; import { Step4Approval } from './Step4Approval/Step4Approval'; -import { buildColumnsFromSpOffers, buildSupplierColumnId, findColumnForRecord, findWinnerColumnId } from './utils/supplierColumns'; +import { buildColumnsFromSpOffers, buildSupplierColumnId, findColumnForRecord, findWinnerColumnId, withoutSessionExtras } from './utils/supplierColumns'; import { buildOffersFromSpRecords } from './utils/spOffers'; import { excludedItemsToFormData } from './utils/excludedColumns'; import { isUserInApprovalChain } from './utils/approvalAccess'; @@ -122,16 +122,22 @@ const CtCreateForm: React.FC = ({ context, userDisplayName, // КП в разных валютах. Используются, только когда в сессии колонок нет. const currencyCodeOf = (valutaSpId?: number): string => valutaSpId ? (rateById.get(valutaSpId)?.code ?? 'KZT') : 'KZT'; - const spSuppliers = buildColumnsFromSpOffers(rawOffers.map(o => ({ + // Поставщики Шага 3 из сессии (после «Сохранить черновик») уже лежат в SP — их колонки из SP + // не дублируем. + const sessionExtras = formData.step3ExtraSuppliers ?? []; + const spSuppliers = withoutSessionExtras(buildColumnsFromSpOffers(rawOffers.map(o => ({ supplierSpId: o.supplierSpId, supplierName: o.supplierName, offerFileSpId: o.offerFileSpId, currencyCode: currencyCodeOf(o.valutaSpId), - }))); + }))), sessionExtras); const supplierMap = new Map(); rawOffers.forEach(o => { if (!supplierMap.has(o.supplierSpId)) supplierMap.set(o.supplierSpId, o.supplierName); }); // Колонка оффера: из сессии, если она есть (в т.ч. старая колонка без файла КП), иначе — из SP. - const visibleColumns = formData.suppliers.length > 0 ? formData.suppliers : spSuppliers; + // Поставщики Шага 3 — тоже колонки: их офферы из SP должны попасть к ним, а не в убранную колонку. + const visibleColumns = formData.suppliers.length > 0 + ? formData.suppliers + : [...spSuppliers, ...sessionExtras.map(es => ({ id: es.id, spId: es.spId, name: es.name, hasOffer: true }))]; const columnIdOf = (supplierSpId: number, offerFileSpId?: number): string => findColumnForRecord(visibleColumns, supplierSpId, offerFileSpId)?.id ?? buildSupplierColumnId(supplierSpId, offerFileSpId); diff --git a/src/webparts/ctFormsPortal/forms/ct/components/utils/supplierColumns.test.ts b/src/webparts/ctFormsPortal/forms/ct/components/utils/supplierColumns.test.ts index 8e23794..565708c 100644 --- a/src/webparts/ctFormsPortal/forms/ct/components/utils/supplierColumns.test.ts +++ b/src/webparts/ctFormsPortal/forms/ct/components/utils/supplierColumns.test.ts @@ -1,6 +1,31 @@ -import { buildColumnDisplayNames, buildColumnLabels, buildColumnsFromSpOffers, buildSupplierColumnId, findColumnForRecord, findSiblingColumnIds, findWinnerColumnId } from './supplierColumns'; +import { buildColumnDisplayNames, buildColumnLabels, buildColumnsFromSpOffers, buildSupplierColumnId, findColumnForRecord, findSiblingColumnIds, findWinnerColumnId, withoutSessionExtras } from './supplierColumns'; import { Supplier } from '../types/domain'; +describe('withoutSessionExtras (поставщик Шага 3 после «Сохранить черновик»)', () => { + // СТ 375: единственный поставщик «fghfg» добавлен на Шаге 3, сохранён черновиком. При открытии из + // реестра колонка собиралась из SP («285_322», без цен) и ещё раз приходила из сессии (с ценами). + const fromSp: Supplier[] = [ + { id: '285_322', spId: 285, kpFileSpId: 322, name: 'fghfg', hasOffer: true }, + { id: '282_320', spId: 282, kpFileSpId: 320, name: 'Каспий Дорс', hasOffer: true }, + ]; + + it('колонку поставщика Шага 3 из SP не добавляет — она уже есть в сессии', () => { + const result = withoutSessionExtras(fromSp, [{ spId: 285, offerId: 322 }]); + + expect(result.map(c => c.id)).toEqual(['282_320']); + }); + + it('другой КП того же поставщика — отдельная колонка, остаётся', () => { + const result = withoutSessionExtras(fromSp, [{ spId: 285, offerId: 999 }]); + + expect(result.map(c => c.id)).toEqual(['285_322', '282_320']); + }); + + it('поставщик Шага 3 ещё не сохранён в SP — ничего не убирает', () => { + expect(withoutSessionExtras(fromSp, [{}])).toHaveLength(2); + }); +}); + describe('findWinnerColumnId (победитель из шапки СТ при открытии без сессии)', () => { const columns: Supplier[] = [ { id: '281_318', spId: 281, kpFileSpId: 318, name: 'SteelGate', hasOffer: true, exclusion: { reason: 'Дорого' } }, diff --git a/src/webparts/ctFormsPortal/forms/ct/components/utils/supplierColumns.ts b/src/webparts/ctFormsPortal/forms/ct/components/utils/supplierColumns.ts index 68afdbf..bf45540 100644 --- a/src/webparts/ctFormsPortal/forms/ct/components/utils/supplierColumns.ts +++ b/src/webparts/ctFormsPortal/forms/ct/components/utils/supplierColumns.ts @@ -44,6 +44,22 @@ export function buildColumnsFromSpOffers(records: SpOfferColumnSource[]): Suppli return Array.from(columnsById.values()); } +// Поставщик, добавленный на Шаге 3, в сессии: SP Id поставщика и его файла-заглушки КП. +export interface SessionExtraColumn { + spId?: number; + offerId?: number; +} + +// Колонки из SP без тех, что в сессии уже есть поставщиком Шага 3. «Сохранить черновик» оставляет +// такого поставщика в списке Шага 3 (см. buildSuppliersPatch), а в SP он уже лежит колонкой +// «поставщик_заглушка». Без этого при открытии СТ, где других поставщиков нет, колонка задваивалась: +// одна из SP без цен, вторая из сессии с ценами. +export function withoutSessionExtras(spColumns: Supplier[], extras: SessionExtraColumn[]): Supplier[] { + return spColumns.filter(column => !extras.some(extra => + extra.spId !== undefined && extra.spId === column.spId && extra.offerId === column.kpFileSpId, + )); +} + // Колонка победителя по winSupplier из шапки СТ (СТ открыта без сессии). В SP хранится поставщик, // а не колонка: у поставщика с несколькими КП берём первую неисключённую колонку. export function findWinnerColumnId(columns: Supplier[], winSupplierSpId: number | undefined): string | undefined {