diff --git a/src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/Step2CommercialOffers.tsx b/src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/Step2CommercialOffers.tsx index 9811ae1..6151124 100644 --- a/src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/Step2CommercialOffers.tsx +++ b/src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/Step2CommercialOffers.tsx @@ -14,6 +14,7 @@ import { UploadedKpList } from './UploadedKpList'; import { removeKpFileFromComparison } from './kpRemoval'; import { applySupplierCurrency, getSupplierCurrency } from './kpCurrency'; import { SaveDraftButton } from '../SaveDraftButton/SaveDraftButton'; +import { withoutPlaceholders } from '../utils/placeholderKpFile'; import styles from './Step2CommercialOffers.module.scss'; interface Props { @@ -160,6 +161,8 @@ export const Step2CommercialOffers: React.FC = ({ }; const canProceed = !isSaving && currencyRates.every(r => r.rateToKzt > 0); + // Заглушки поставщиков, добавленных на Шаге 3 вручную, — не КП: в списке не показываем. + const realKpFiles = withoutPlaceholders(formData.uploadedKpFiles ?? []); const currencyOptions = ['KZT', ...currencyRates.filter(r => r.code !== 'KZT').map(r => r.code)]; const uploadedFiles: UploadedFile[] = kp.kpFiles.map(f => ({ @@ -201,9 +204,9 @@ export const Step2CommercialOffers: React.FC = ({ - {!isRestrictedView && (formData.uploadedKpFiles?.length ?? 0) > 0 && ( + {!isRestrictedView && realKpFiles.length > 0 && ( getSupplierCurrency(formData.suppliers ?? [], file)} diff --git a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/Step3ComparisonTable.tsx b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/Step3ComparisonTable.tsx index 882392a..5b44902 100644 --- a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/Step3ComparisonTable.tsx +++ b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/Step3ComparisonTable.tsx @@ -20,6 +20,7 @@ import { useRecommendedPick } from './useRecommendedPick'; import { VendorPickerDialog } from './VendorPickerDialog'; import { ExcludedSuppliersList } from './ExcludedSuppliersList'; import { SaveDraftButton } from '../SaveDraftButton/SaveDraftButton'; +import { withoutPlaceholders } from '../utils/placeholderKpFile'; // Исключённых поставщиков в режиме просмотра (согласующий, отправленная СТ) пока не показываем — // решение от 2026-10-27 (docs/adr/0001). Данные грузятся и так: включить — поменять на true. @@ -202,7 +203,8 @@ export const Step3ComparisonTable: React.FC = ({ )} {/* В КП — цены: показываем тем же, кому видны цены (участникам согласования и автору). */} - {!isRestrictedView && } + {/* Заглушки поставщиков, добавленных вручную, — не КП: не показываем. */} + {!isRestrictedView && } ((resolve) => setTimeout(resolve, 300)); diff --git a/src/webparts/ctFormsPortal/forms/ct/components/utils/placeholderKpFile.test.ts b/src/webparts/ctFormsPortal/forms/ct/components/utils/placeholderKpFile.test.ts new file mode 100644 index 0000000..9c47c84 --- /dev/null +++ b/src/webparts/ctFormsPortal/forms/ct/components/utils/placeholderKpFile.test.ts @@ -0,0 +1,27 @@ +import { isPlaceholderKpFile, withoutPlaceholders } from './placeholderKpFile'; + +describe('isPlaceholderKpFile', () => { + // СТ 375: поставщик «fghfg» добавлен на Шаге 3 — заглушка видна в «Коммерческих предложениях». + it('заглушка поставщика, добавленного вручную', () => { + expect(isPlaceholderKpFile('fghfg_1793100061349.txt')).toBe(true); + expect(isPlaceholderKpFile('ООО Альфа_1793100061349.TXT')).toBe(true); + }); + + it('настоящие КП — не заглушки', () => { + expect(isPlaceholderKpFile('372_KP_SteelGate_Export_USD.pdf')).toBe(false); + expect(isPlaceholderKpFile('372_KP_TOO_Kaspiy_Dors.docx')).toBe(false); + expect(isPlaceholderKpFile('КП.doc')).toBe(false); + }); +}); + +describe('withoutPlaceholders', () => { + it('оставляет только настоящие КП, порядок сохраняется', () => { + const files = [ + { spId: 318, name: '372_KP_SteelGate.pdf' }, + { spId: 322, name: 'fghfg_1793100061349.txt' }, + { spId: 320, name: '372_KP_Kaspiy.docx' }, + ]; + + expect(withoutPlaceholders(files).map(f => f.spId)).toEqual([318, 320]); + }); +}); diff --git a/src/webparts/ctFormsPortal/forms/ct/components/utils/placeholderKpFile.ts b/src/webparts/ctFormsPortal/forms/ct/components/utils/placeholderKpFile.ts new file mode 100644 index 0000000..4b393aa --- /dev/null +++ b/src/webparts/ctFormsPortal/forms/ct/components/utils/placeholderKpFile.ts @@ -0,0 +1,18 @@ +// Файл-заглушка КП: создаётся для поставщика, добавленного на Шаге 3 вручную, — оффер в SP должен +// ссылаться на файл КП (CtService._createPlaceholderKpFile). Людям он не нужен: в списках КП и в +// пакете INCORE его не показываем. Узнаём по расширению: на Шаге 2 загрузить можно только +// .pdf/.docx/.doc (FileUploadArea), а заглушка всегда .txt. +const PLACEHOLDER_EXTENSION = '.txt'; + +// Подпись колонки поставщика, у которого вместо КП — заглушка. +export const PLACEHOLDER_COLUMN_LABEL = 'Добавлен вручную'; + +export function isPlaceholderKpFile(fileName: string): boolean { + return fileName.toLowerCase().endsWith(PLACEHOLDER_EXTENSION); +} + +// Настоящие КП — для показа и выгрузки. Данные формы (uploadedKpFiles) не фильтруем: по ним +// сохранение находит файл КП колонки, в том числе заглушку. +export function withoutPlaceholders(files: T[]): T[] { + return files.filter(file => !isPlaceholderKpFile(file.name)); +} 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 565708c..f19cbc7 100644 --- a/src/webparts/ctFormsPortal/forms/ct/components/utils/supplierColumns.test.ts +++ b/src/webparts/ctFormsPortal/forms/ct/components/utils/supplierColumns.test.ts @@ -93,6 +93,19 @@ describe('buildColumnsFromSpOffers (открытие по ссылке / без }); }); +describe('подпись колонки с файлом-заглушкой', () => { + // СТ 375: в шапке колонки показывалось имя заглушки «fghfg_1793100061349.txt». + it('вместо имени заглушки — «Добавлен вручную»', () => { + const columns: Supplier[] = [ + { id: '285_322', spId: 285, kpFileSpId: 322, name: 'fghfg', hasOffer: true }, + { id: '285_330', spId: 285, kpFileSpId: 330, name: 'fghfg', hasOffer: true }, + ]; + const files = [{ spId: 322, name: 'fghfg_1793100061349.txt' }, { spId: 330, name: '375_KP_fghfg.pdf' }]; + + expect(buildColumnLabels(columns, files)).toEqual({ '285_322': 'Добавлен вручную', '285_330': '375_KP_fghfg.pdf' }); + }); +}); + describe('подписи колонок поставщика с несколькими КП', () => { const ARLAN = 'ТОО «Арлан Офис Снаб»'; const columns: Supplier[] = [ diff --git a/src/webparts/ctFormsPortal/forms/ct/components/utils/supplierColumns.ts b/src/webparts/ctFormsPortal/forms/ct/components/utils/supplierColumns.ts index bf45540..c5d603e 100644 --- a/src/webparts/ctFormsPortal/forms/ct/components/utils/supplierColumns.ts +++ b/src/webparts/ctFormsPortal/forms/ct/components/utils/supplierColumns.ts @@ -1,4 +1,5 @@ import { Supplier } from '../types/domain'; +import { PLACEHOLDER_COLUMN_LABEL, isPlaceholderKpFile } from './placeholderKpFile'; // Колонка таблицы сравнения = поставщик + файл КП: у одного поставщика может быть несколько // предложений (напр. в тенге и в долларах). Разделитель «_», а не «::» — «::» уже служит @@ -91,6 +92,11 @@ export function buildColumnLabels( if (group.length < 2) return; group.forEach((c, index) => { const fileName = uploadedKpFiles.find(f => f.spId === c.kpFileSpId)?.name; + // Имя файла-заглушки (поставщик добавлен вручную) людям ничего не говорит. + if (fileName !== undefined && isPlaceholderKpFile(fileName)) { + labels[c.id] = PLACEHOLDER_COLUMN_LABEL; + return; + } labels[c.id] = fileName ?? `КП ${index + 1}`; }); });