fix(ct): своя строка «Дополнительной информации» по ссылке — без HTML-обёртки SharePoint

Значение своей строки хранится в comment, а это в «Критерии» ОМТС многострочное поле с
форматированием: SharePoint отдаёт его как <div class="ExternalClass…">да</div>, и по ссылке
показывалась разметка. При загрузке теперь остаётся чистый текст (plainTextFromSp).
This commit is contained in:
Тимур Городецкий
2026-10-01 16:29:13 +05:00
parent 2b102b8772
commit 64a377276c
3 changed files with 40 additions and 3 deletions
@@ -1,4 +1,4 @@
import { buildCriteriaRecords, isPaymentTypeOption, restoreCriteria } from './criteria';
import { buildCriteriaRecords, isPaymentTypeOption, plainTextFromSp, restoreCriteria } from './criteria';
import { Supplier } from '../types/domain';
const ARLAN_KZT: Supplier = { id: '263_277', spId: 263, kpFileSpId: 277, name: 'ТОО «Арлан Офис Снаб»', hasOffer: true };
@@ -96,3 +96,19 @@ describe('isPaymentTypeOption', () => {
expect(isPaymentTypeOption('40% предоплата, 60% по аккредитиву')).toBe(false);
});
});
describe('plainTextFromSp', () => {
// comment в SP «Критерии» — многострочное поле с форматированием: SharePoint заворачивает текст в HTML,
// и по ссылке своя строка показывалась как «<div class="ExternalClass…">да</div>».
it('убирает обёртку ExternalClass и теги', () => {
expect(plainTextFromSp('<div class="ExternalClass11A142948BDD4FEB93E8B98BB14AE465">да</div>')).toBe('да');
});
it('переносы строк сохраняет, HTML-сущности раскодирует', () => {
expect(plainTextFromSp('<div>12&nbsp;мес<br>от &quot;поставки&quot; &amp; монтажа</div>')).toBe('12 мес\nот "поставки" & монтажа');
});
it('обычный текст не трогает', () => {
expect(plainTextFromSp('12 мес')).toBe('12 мес');
});
});
@@ -36,6 +36,26 @@ const EMPTY_ENTRY: CriteriaEntry = { paymentType: '', deliveryType: '', deadline
const hasAnyValue = (e: CriteriaEntry): boolean =>
[e.paymentType, e.deliveryType, e.deadline, e.sertificates].some(v => (v ?? '').trim() !== '');
const HTML_ENTITIES: Record<string, string> = {
'&nbsp;': ' ',
'&amp;': '&',
'&lt;': '<',
'&gt;': '>',
'&quot;': '"',
'&#39;': "'",
};
// Текст из многострочного поля SP с форматированием (comment в «Критерии»): SharePoint хранит его как HTML
// в обёртке <div class="ExternalClass…">. Оставляем чистый текст, переносы строк — как есть.
export function plainTextFromSp(value: string): string {
if (!value.includes('<')) return value;
return value
.replace(/<br\s*\/?>/gi, '\n')
.replace(/<[^>]+>/g, '')
.replace(/&(nbsp|amp|lt|gt|quot|#39);/g, entity => HTML_ENTITIES[entity] ?? entity)
.trim();
}
export function isPaymentTypeOption(value: string): boolean {
return PAYMENT_TYPE_OPTIONS.includes(value.trim());
}
@@ -29,7 +29,7 @@ import {
keepLatestOfferRecords,
parseOfferItemIndex,
} from "../../../shared/offers/offerRecords";
import { CriteriaRecord } from "../components/utils/criteria";
import { CriteriaRecord, plainTextFromSp } from "../components/utils/criteria";
import {
NoOfferSupplierEntry,
NoOfferSupplierRecord,
@@ -680,7 +680,8 @@ export class CtService {
return ((data.value ?? []) as Array<Record<string, unknown>>).map((item): CriteriaRecord => {
const base = { id: Number(item.Id), supplierSpId: Number(item.supplierId) };
const label = String(item.criteriaLabel ?? "").trim();
if (label) return { ...base, criteriaLabel: label, value: String(item.comment ?? "") };
// comment — многострочное поле с форматированием: SharePoint отдаёт его в HTML-обёртке.
if (label) return { ...base, criteriaLabel: label, value: plainTextFromSp(String(item.comment ?? "")) };
return {
...base,
paymentType: String(item.paymentType ?? ""),