edit full article archive to publication standard
Build and deploy / deploy (push) Successful in 18s

This commit is contained in:
2026-07-31 23:08:19 +03:00
parent 440c8721dc
commit 7c5b19c960
28 changed files with 583 additions and 39 deletions
+5 -4
View File
@@ -107,7 +107,7 @@ const drafts = [
readingMinutes: 10,
sources: [translit, getList, addElement],
bodyHtml: [
paragraph('Добавляем товар в Bitrix и берём <code>CODE</code> из названия. На тесте всё выглядит хорошо. Потом менеджер заводит «Кофе Classic 250 г» второй раз — с запятой или лишним пробелом. После транслитерации получается тот же адрес, а ссылка из каталога ведёт к записи, которую никто не собирался открывать. Главный вопрос этой заметки простой: как получить читаемый код и не принять совпадение за успех?'),
paragraph('Добавляем товар в Bitrix и берём <code>CODE</code> из названия. На тесте всё выглядит хорошо. Потом менеджер заводит «Кофе Classic 250 г» второй раз — с запятой или лишним пробелом. После транслитерации получается тот же адрес, а ссылка из каталога ведёт к записи, которую никто не собирался открывать. Главный вопрос этой заметки простой: как получить читаемый код и не принять совпадение за успех? Цена ошибки — неверная карточка, потерянная ссылка и ручная чистка дублей.'),
paragraph('Сначала важная оговорка. Транслитерация не выбирает свободный URL. Она преобразует строку по заданным правилам. Уникальность — уже правило конкретного инфоблока и конкретного способа создания элементов. Поэтому проверяем не «красиво ли выглядит код», а есть ли другой элемент с тем же значением там, где его будет искать каталог.'),
heading('Что даёт системный транслит'),
paragraph('В Bitrix для этой задачи есть <code>CUtil::translit</code>. Метод принимает строку, язык и набор параметров. В нём можно задать регистр, замену пробелов и прочих символов, ограничение длины, а также удаление повторяющихся замен. Для адреса каталога мне удобнее дефис и нижний регистр: в результате не приходится отдельно объяснять, почему одни карточки имеют подчёркивание, а другие — дефис.'),
@@ -207,7 +207,7 @@ const drafts = [
readingMinutes: 10,
sources: [parseComponentPath, makePathFromTemplate, getList],
bodyHtml: [
paragraph('Иногда символьный код в элементе правильный, а карточка всё равно отвечает 404. В другой раз тот же код работает только без раздела в адресе. Причина обычно не в транслите: путь сначала разбирает компонент, а уже потом его переменные попадают в фильтр инфоблока. Разберём один вопрос: что должно совпасть, чтобы адрес каталога действительно стал значением <code>ELEMENT_CODE</code>?'),
paragraph('Иногда символьный код в элементе правильный, а карточка всё равно отвечает 404. В другой раз тот же код работает только без раздела в адресе. Причина обычно не в транслите: путь сначала разбирает компонент, а уже потом его переменные попадают в фильтр инфоблока. Разберём один вопрос: что должно совпасть, чтобы адрес каталога действительно стал значением <code>ELEMENT_CODE</code>? Цена ошибки — менять данные элемента, когда проблема находится в маршруте.'),
paragraph('Это полезно отделить в голове. Адрес <code>/catalog/kofe/classic-250-g/</code> не является запросом к таблице элементов. Для комплексного компонента Bitrix сначала определяет, какой шаблон пути подошёл, и восстанавливает переменные из URL. Только затем код компонента решает, как искать элемент. Если смешать эти два шага, начинается бесконечная правка <code>CODE</code>, хотя ошибка сидит в шаблоне или в имени переменной.'),
heading('Что делает движок ЧПУ'),
paragraph('В документации <code>CComponentEngine::ParseComponentPath</code> описано, что метод получает папку ЧПУ, массив шаблонов и текущий путь. Он возвращает код найденного шаблона, а переменные из пути записывает в переданный массив. Если шаблон не найден, результат — пустая строка. Значит, до запроса к инфоблоку можно и нужно посмотреть две вещи: какой шаблон распознан и какое значение оказалось в <code>ELEMENT_CODE</code>.'),
@@ -316,7 +316,7 @@ const drafts = [
readingMinutes: 11,
sources: [getList, parseComponentPath, updateElement],
bodyHtml: [
paragraph('Есть неприятная ошибка, которую легко принять за кеш: открываешь карточку товара, а видишь другой товар с похожим названием. Особенно странно это выглядит после импорта — обе записи есть в админке, у обеих нормальные картинки, а URL одной вдруг показывает соседнюю. Здесь не нужно начинать с очистки кеша. Главный вопрос: как доказать конфликт <code>CODE</code> или широкий фильтр до того, как менять данные?'),
paragraph('Есть неприятная ошибка, которую легко принять за кеш: открываешь карточку товара, а видишь другой товар с похожим названием. Особенно странно это выглядит после импорта — обе записи есть в админке, у обеих нормальные картинки, а URL одной вдруг показывает соседнюю. Здесь не нужно начинать с очистки кеша. Главный вопрос: как доказать конфликт <code>CODE</code> или широкий фильтр до того, как менять данные? Цена ошибки — исправить правильную запись и получить новый конфликт.'),
paragraph('Первое правило — не смотреть только на название. Компонент получает строку из адреса и строит по ней выборку. Если выборка возвращает несколько элементов, значение «первого» зависит от порядка и условий запроса. Если она не возвращает ничего, компонент может отдать 404 или подставить другую ветку своей логики. Поэтому нам нужны три наблюдаемых факта: что было в URL, какую переменную получил компонент и сколько записей удовлетворяют его фильтру.'),
heading('Не путать симптом и причину'),
paragraph('Похожее название не доказывает конфликт. В одном каталоге может быть несколько позиций «Classic 250 г» в разных разделах, и тогда адрес обязан содержать достаточный контекст. Наоборот, разные названия могут получить одинаковый код после нормализации. Диагностику начинаю с конкретного сломанного адреса и ID товара, который ожидали увидеть. Только потом читаю список элементов по фактическому <code>ELEMENT_CODE</code>.'),
@@ -477,12 +477,13 @@ const revisions = drafts.map((draft) => {
contentHtml: [bodyHtml, heading('Проверяемые источники'), sourceList(sources)].join('\n'),
};
});
export { revisions };
if (process.argv.includes('--print-revisions')) {
console.log(JSON.stringify(revisions, null, 2));
} else if (process.argv.includes('--check')) {
console.log(JSON.stringify(reports, null, 2));
} else {
} else if (process.argv[1]?.endsWith('upgrade-2018-04.mjs')) {
console.error('Usage: node web/scripts/upgrade-2018-04.mjs --print-revisions | --check');
process.exitCode = 1;
}