diff --git a/src/webparts/ctFormsPortal/forms/ct/components/utils/kpFileName.test.ts b/src/webparts/ctFormsPortal/forms/ct/components/utils/kpFileName.test.ts index b8158dd..c93921a 100644 --- a/src/webparts/ctFormsPortal/forms/ct/components/utils/kpFileName.test.ts +++ b/src/webparts/ctFormsPortal/forms/ct/components/utils/kpFileName.test.ts @@ -4,14 +4,44 @@ describe('buildKpStorageName (имя файла КП в библиотеке)', // КП сохранялись под исходным именем с перезаписью: «КП.pdf» из одной СТ затирал «КП.pdf» другой, // и согласующий первой СТ открывал чужое КП. Префикс СТ делает имя уникальным. it('добавляет префикс СТ', () => { - expect(buildKpStorageName('357', 'КП.pdf')).toBe('357_КП.pdf'); + expect(buildKpStorageName('357', 'KP.pdf')).toBe('357_KP.pdf'); }); it('не добавляет префикс второй раз', () => { - expect(buildKpStorageName('357', '357_КП.pdf')).toBe('357_КП.pdf'); + expect(buildKpStorageName('357', '357_KP.pdf')).toBe('357_KP.pdf'); }); - it('без СТ — исходное имя', () => { - expect(buildKpStorageName(undefined, 'КП.pdf')).toBe('КП.pdf'); + it('без СТ — имя без префикса', () => { + expect(buildKpStorageName(undefined, 'KP.pdf')).toBe('KP.pdf'); + }); +}); + +describe('buildKpStorageName — только латиница', () => { + // СТ 365, файл 306: PDF с кириллицей в имени → extractPDFText 500 (OCR 415 «unsupported file type ''»), + // тот же PDF с латинским именем проходит. Бэкенд не наш — храним файл под латинским именем. + it('транслитерирует кириллицу', () => { + expect(buildKpStorageName('365', 'КП_тест_кириллица.pdf')).toBe('365_KP_test_kirillitsa.pdf'); + }); + + it('транслитерирует заглавные и казахские буквы', () => { + expect(buildKpStorageName('274', 'Қорғаныш_Әлем_Щит.pdf')).toBe('274_Qorganysh_Alem_Shchit.pdf'); + }); + + it('пробелы, №, кавычки и тире — в безопасные символы', () => { + expect(buildKpStorageName('363', 'Счет №1891 ТОО «Орда» — копия (2).pdf')) + .toBe('363_Schet_N1891_TOO_Orda_kopiya_(2).pdf'); + }); + + it('латинское имя не меняется', () => { + expect(buildKpStorageName('364', 'KP_02_Qurylys-Material_KZT.pdf')).toBe('364_KP_02_Qurylys-Material_KZT.pdf'); + }); + + it('уже сохранённое имя при повторном вызове не меняется (по нему ищем загруженный файл)', () => { + const stored = buildKpStorageName('274', 'Северный мост.pdf'); + expect(buildKpStorageName('274', stored)).toBe(stored); + }); + + it('расширение сохраняется', () => { + expect(buildKpStorageName('1', 'КП.PDF')).toBe('1_KP.PDF'); }); }); diff --git a/src/webparts/ctFormsPortal/forms/ct/components/utils/kpFileName.ts b/src/webparts/ctFormsPortal/forms/ct/components/utils/kpFileName.ts index 5181be6..584a149 100644 --- a/src/webparts/ctFormsPortal/forms/ct/components/utils/kpFileName.ts +++ b/src/webparts/ctFormsPortal/forms/ct/components/utils/kpFileName.ts @@ -1,8 +1,42 @@ +// Транслитерация кириллицы (русский + казахский) для имени файла в SP. +const TRANSLIT: Record = { + а: 'a', б: 'b', в: 'v', г: 'g', д: 'd', е: 'e', ё: 'e', ж: 'zh', з: 'z', и: 'i', й: 'y', + к: 'k', л: 'l', м: 'm', н: 'n', о: 'o', п: 'p', р: 'r', с: 's', т: 't', у: 'u', ф: 'f', + х: 'kh', ц: 'ts', ч: 'ch', ш: 'sh', щ: 'shch', ъ: '', ы: 'y', ь: '', э: 'e', ю: 'yu', я: 'ya', + ә: 'a', ғ: 'g', қ: 'q', ң: 'n', ө: 'o', ұ: 'u', ү: 'u', һ: 'h', і: 'i', + '№': 'N', +}; + +// Всё, кроме этих символов, заменяем на «_». +const UNSAFE_CHARS = /[^A-Za-z0-9_.()-]/g; + +function transliterateChar(char: string): string { + const lower = char.toLowerCase(); + const mapped = TRANSLIT[lower]; + if (mapped === undefined) return char; + // Заглавная буква: «Щ» → «Shch», а не «SHCH». + if (char !== lower && mapped.length > 0) return mapped[0].toUpperCase() + mapped.slice(1); + return mapped; +} + +// Бэкенд extractPDFText теряет расширение у PDF с кириллицей в имени (OCR 415 «unsupported file type ''») +// и отвечает 500 — КП падало при каждой первой загрузке. Бэкенд не наш, поэтому храним файл под именем +// только из латиницы, цифр и «_ - . ( )». Результат детерминированный: по нему ищем уже загруженный файл. +function toSafeFileName(name: string): string { + return Array.from(name) + .map(transliterateChar) + .join('') + .replace(UNSAFE_CHARS, '_') + .replace(/_+/g, '_') + .replace(/_+\./g, '.'); +} + // Имя файла КП в библиотеке «Коммерческие предложения» — с префиксом СТ. Файлы лежат в корне библиотеки // и загружались под исходным именем с перезаписью: «КП.pdf» одной СТ затирал «КП.pdf» другой, и // согласующий открывал чужое КП. Ссылки, выгрузка INCORE и восстановление по ссылке работают по этому имени. export function buildKpStorageName(ctId: string | undefined, originalName: string): string { - if (!ctId) return originalName; + const safeName = toSafeFileName(originalName); + if (!ctId) return safeName; const prefix = `${ctId}_`; - return originalName.startsWith(prefix) ? originalName : `${prefix}${originalName}`; + return safeName.startsWith(prefix) ? safeName : `${prefix}${safeName}`; }