projects/org/@it-site-lideravto-cs-old/IMPORT-MECHANISM.md

title: Механизм импорта BAZON — lideravto.ru (CS-Cart 4.18) — ЭТАЛОН
project: "@it-site-lideravto-cscart"
form: text
status: authoritative
updated: 2026-07-06


Импорт BAZON на www.lideravto.ru — как устроено и как ДЕЛАТЬ

Этот документ — единственный источник правды по импорту. Если встретишь другое описание/скрипт, противоречащий этому — оно НЕВЕРНОЕ (список в конце). Читать ПЕРЕД любой работой с импортом.

TL;DR

ЧТО ТОЧНО ИЗВЕСТНО: весь боевой каталог (17456/17472 товаров имеют page_title) построен схемой-перехватом модуля my_changes (app/addons/my_changes/schemas/advanced_import/products.post.php). Она сама строит:
- имя товара = Наименование + Марка + Модель + OEM (перехват марки/модели);
- категории = Марка / Модель / Наименование (3 уровня);
- SEO = page_title, meta_description, meta_keywords, search_words, ЧПУ (seo_name), alt картинок;
- описания категорий (Марка/Модель/Наименование) с SEO.

Маркер, что схема отработала: у товара page_title НЕ пустой.

ТРИГГЕР СХЕМЫ — РЕШЕНО (проверено трейсом на клоне 2026-07-06):

Путь импорта Какие секции схемы срабатывают Имя/SEO
advanced_import (пресеты, advanced_import.import.import) только pre_processing ❌ нет имени/SEO
core exim fn_import (админ Экспорт/Импорт, или CLI-вызов fn_import) ВСЕ 3 (pre_processing + import_after_process_data + post_processing) ✅ имя+категории+SEO

Условие: секции берутся из паттерна. core exim читает schemas/exim/products.php (+ .post.php оверрайды), а схема my_changes лежит в schemas/advanced_import/products.post.php. → Чтобы core exim увидел секции, скопировать оверрайд в schemas/exim/products.post.php (проверено: после копии core-exim паттерн получает все 3 секции + поля Model/Level 3 Category).

Почему старый каталог с SEO, а мои 591 — без: старый каталог залит через core exim (админ Экспорт/Импорт) со схемой в exim/; мои 591 — через advanced_import (только pre_processing). На проде схема сейчас ТОЛЬКО в advanced_import/ → надо добавить копию в exim/.

Проверено end-to-end на клоне (fn_import через CLI): 3 товара созданы, трейс показал FILE_LOADED+PRE_PROCESSING+AFTER_PROCESS×3+POST_PROCESSING, имена собраны схемой («Тестовая кнопка Volvo 4-FH»). Осталась мелочь: page_title на клоне пуст (порядок записи объекта затирает db_query схемы) — на проде у старых товаров title есть; отдельно доотладить на реальных данных с OEM.

Инфраструктура

Как работает механизм (детально)

1. Точка входа — ядро fn_import (Экспорт/Импорт), НЕ advanced_import

Секции схемы (pre_processing, import_after_process_data, post_processing) исполняет только fn_import из ядра (app/functions/fn.exim.php).
Запуск fn_import происходит:
- штатно вручную: админка → Администрирование → Экспорт/Импорт → Товары → Импорт (веб-форма, upload CSV);
- для крона: небольшой CLI-скрипт, который бутстрапит CS-Cart и вызывает fn_import($pattern, $data, $import_options) напрямую ($pattern = fn_exim_get_pattern_definition('products','import')).

НЕ существует dispatch exim.import_file (в контроллере exim только mode import через веб-форму — проверено). Старый cron_import_and_activate.sh --dispatch=exim.import_file — неверный, вероятно не работал.
advanced_import секции схемы НЕ исполняет (свой Importer.php обходит fn_import) — проверено: старые товары через админку имеют page_title, мои 591 через advanced_import — нет.

1a. Формат CSV для fn_import (ПРОВЕРЕНО на клоне)

Заголовки CSV = имена полей exim (не русские колонки фида), разделитель ; (код опции delimiter='S'). Обязательные/нужные заголовки:

Product code(=Артикул) ; Store ; Category(=Марка) ; Model(=Модель) ; Level 3 Category(=Наименование) ; Price(=Цена)

CLI-вызов (эталон, проверен):

require 'init.php'; // AREA=A, ACCOUNT_TYPE=admin
$pattern = fn_exim_get_pattern_definition('products','import');
$options = ['delimiter'=>'S','category_delimiter'=>'///','features_delimiter'=>'///','lang_code'=>'ru','company_id'=>1,'reset_inventory'=>'N'];
$data = fn_exim_get_csv($pattern, '<file.csv>', $options);   // вернёт FALSE если нет Store/неверный разделитель
fn_import($pattern, $data, $options);                        // ← запускает все секции схемы

Для «новые=create, продано=amount 0» → reset_inventory='Y' (обнуляет отсутствующих в файле).

2. Модуль my_changes — перехват (штатная схема)

Файл: app/addons/my_changes/schemas/advanced_import/products.post.php (+ init.php с OEM_FEATURE_ID=1).
Регистрирует 3 callback'а в схеме:

Секция exim Функция Что делает
pre_processing fn_exim_set_product_categories_bazon склеивает Category = Марка///Модель///Наименование; product = Наименование (короткое)
import_after_process_data fn_exim_import_after_process_data_bazon строит имя product = "{Наименование} {Марка} {Модель} {OEM}"; пишет SEO товара; обновляет SEO категорий Марка/Модель/Наименование
post_processing fn_exim_post_processing_bazon_bazon генерит ЧПУ (seo_name), alt картинок

Схема грузится штатно (это НЕ func.php — а schemas/*.post.php, которые ядро подключает при сборке схемы). func.php-хуки на этом Beget НЕ грузятся (проверено маркером — см. «неверные решения»).

3. Стикер «Продано» — шаблонный хук (тоже my_changes/тема)

Отдельно от импорта. templates/addons/my_changes/hooks/products/{product_labels,list_images_block}.post.tpl + CSS в head_scripts.post.tpl{literal}). Условие: {if $product.amount <= 0 && $product.tracking != 'D'}. Шаблонные хуки грузятся (в отличие от func.php).

Реализация на тесте — СТАТУС 2026-07-06

Скрипты (на сервере ~/import_run/): feed_to_exim.php (prep: fac5a → exim-CSV, колонки Product code;Store;Language;Category;Model;Level 3 Category;Price;Features, OEM как OEM номер: <n>) + exim_import.php (бутстрап + fn_exim_get_csv + fn_import). Схема my_changes скопирована в schemas/exim/.

Работает (проверено на 20 товарах, путь UPDATE существующих):
- ✅ имя = Наименование Марка Модель (схема)
- ✅ 3-уровневые категории Марка/Модель/Наименование
- ✅ page_title + SEO (schema import_after_process)
- ✅ OEM сохраняется в фичу id=1

OEM-в-имени — ПОЧИНЕНО (2026-07-06): схема читала $object['feature_1'] (пуст в момент сборки имени), но OEM есть в $object['Features'] = "OEM номер: 21174442". Добавлен fallback в схему (schemas/exim/products.post.php, строка после $oem = ...): если feature_1 пуст — парсить OEM из Features. Обратно-совместимо. Результат: имя Воздухозаборник Volvo 4-FH 21174442 + title с OEM. ⚠ Этот же fallback НУЖНО применить в схему на проде (после отладки).

ЧЕТЫРЕ пресета (через fn_import, опции new_products/update_existing/reset_inventory)

Пресет new upd reset Фид (заголовки exim) Назначение Статус
3 — полное обновление (разово) N Y N полный: code;Store;Language;Category;Model;Level 3 Category;Price;Features имя/SEO/OEM у всех ✅ проверен (50)
1 — добавление (крон) Y N N полный создать новые ⚠ create-баг (page_title/OEM-фича)
2 — цены+остатки (крон) N Y Y мин: code;Store;Price → схема пропускает имя (нет Category/Model) цены + продано (amount 0) не тестирован
4 — ФОТО (отдельно) N Y N code;Store;Detailed image (URL) скачать+привязать фото ✅ проверен

Раннер: ~/import_run/exim_import.php <file> <new> <upd> <reset>. Prep: ~/import_run/feed_to_exim.php <out> [limit] (нужен отдельный prep для фото: code;Store;Detailed image).

Поле фото: Detailed image (URL) — fn_exim_import_images скачивает по URL. Thumbnail авто. Множественные — поле Additional images (проверить).

Находка: Page titleимпортируемое поле (fn_import_product_descr). Можно обойти create-баг: prep строит page_title строкой в колонку Page title → пишется штатно даже на create.

СТАТУС: ✅ пресеты 3, 4 работают. Осталось: пресет 1 (create-баг: либо колонка Page title в фиде, либо create+scoped-update новых), пресет 2 (мин.фид), полный прогон, прод (вернуть OEM-fallback в прод-схему + схему в prod schemas/exim/).

⚠ Схему my_changes на проде НЕ трогать вслепую (работает для старого каталога). Правки — только после отладки на клоне.

🛡 УКРЕПЛЕНИЕ 2026-07-15 — импорт защищён от известных сбоев

Крон-скрипты (preset1_add.sh, preset2_prices.sh, preset3_full.sh) пересозданы с защитами:
1. Валидация фида~/import_run/fetch_feed.php качает fac5a → feed_raw.csv, проверяет: скачался + ≥10000 строк. Если нет → импорт ОТМЕНЁН (exit 1, лог !!! ОТМЕНА). Защита от главного риска: пустой/обрезанный фид + reset_inventory = «всё продано». (BAZON уже раз переезжал → фид 404.)
2. flock (/tmp/lider_import.lock) — второй импорт не стартует, если первый идёт (крон+ручной не столкнутся).
3. Лог только сводка (grep -aoE по «Всего/Новые/Обновленные/Пропущенные/Ошибка/rows=/import_done) — раньше в лог падал гигантский однострочный спам импорта (18M/прогон) → переполнял диск → ломал миниатюры. Теперь логи компактные. 4. **preset1 теперь сам дотягивает фото новым** (шагfeed_photo_missing.php` + импорт) — новые карточки сразу с фото.

Известные ограничения (следить): фото растут (18G) → квота; следить по битых миниатюр в health-отчёте. Оставшиеся без фото — те, у кого нет фото в самом фиде.

🚨 ИНЦИДЕНТ 2026-07-07 — preset2 стёр имена + обнулил остатки (ИСПРАВЛЕНО)

Что случилось: ночной крон preset2_prices (16:00) на МИНИМАЛЬНОМ фиде (code;Store;Price) → у 14577 товаров стёрлись имена + все стали «продано».
Причина: секция схемы fn_exim_set_product_categories_bazon (pre_processing) ставит $import_data[$key]['product'] = $data['Level 3 Category'] БЕЗУСЛОВНО. В минимальном фиде колонки Level 3 Category нет → product затирается пустым. Плюс reset_inventory=Y + нет количества в фиде → всем amount=0.
Восстановление: отключён крон preset2 (Beget API delete row); остатки восстановлены SQL по присутствию в фиде (in-feed→1, нет→0); имена восстановлены прогоном preset3_full.sh (полный фид с Level 3 Category → схема строит имена верно). Итог: без имени 1, наличие 14576/продано 1417 (активных), сайт 200.
⚠ ДО возврата preset2 на крон ОБЯЗАТЕЛЬНО:
1. Защитить схему: product ставить только if (!empty($data['Level 3 Category'])) (в обеих копиях: schemas/exim/ и schemas/advanced_import/). Аналогично проверить import_after_process — не затирает ли на минимальном фиде.
2. preset2 переделать: либо ПОЛНЫЙ фид (все колонки, схема строит имена) + отдельно остатки, либо остатки/цены прямым SQL. Количество: фид числа не даёт → in-feed=1 / not-in-feed=0 (reset_inventory без колонки количества обнуляет ВСЕХ — не использовать «голым»).
3. Тестировать на клоне на ПРОД-ПОДОБНЫХ данных, только потом крон.

УРОК: любой fn_import через этот паттерн запускает ВСЮ схему (не только когда есть Category/Model). Минимальные фиды опасны. Тестировать каждый пресет на клоне ДО постановки на крон.

ФИНАЛ 2026-07-07 — автоимпорт настроен (4 пресета + крон)

Скрипты ~/import_run/: preset1_add.sh, preset2_prices.sh, preset3_full.sh, preset4_photo.sh (+ preps feed_to_exim.php, feed_prices.php, feed_photo.php, feed_target.php, раннер exim_import.php). Схема my_changes в prod schemas/exim/ с OEM-fallback.
- Крон Beget: 03:00 → preset1_add.sh (row 2294702), 16:00 → preset2_prices.sh (row 2294703). Пресеты 3/4 — вручную.
- preset1_add = 2 прохода: create новых (new=Y,upd=N) + доп.SEO новым через feed_target (page_title=''). Существующие не трогает (провалидировано).
- preset2_prices = мин.фид (code;Store;Price) + reset_inventory=Y (продано→amount 0). Схема имя не трогает (нет Category/Model).
- Старые advanced_import-скрипты (run_products.sh и т.д.) — в ~/import_run/_deprecated/. advanced_import-пресеты в БД восстановлены к оригиналу (14.02), НЕ используются.
- Логи: ~/logs/preset*.log.

Как ДЕЛАТЬ два импорта (штатно)

Оба — через exim.import_file на fac5a, оба дёргают схему my_changes.

Импорт 1 — ТОВАРЫ (новые карточки), крон 03:00

Импорт 2 — ЦЕНЫ + КОЛИЧЕСТВО, крон 16:00

Что НЕ делать

Кодировка

Ядро exim (fn_exim_get_csv/fn_detect_encoding) конвертит cp1251 само. Патч advanced_import/Readers/Csv.php нужен ТОЛЬКО если импортировать через advanced_import (чего делать НЕ надо). При exim-импорте патч не требуется.

Проверка после импорта

-- имя+SEO собраны схемой (у правильно импортированных page_title НЕ пустой):
SELECT COUNT(*) FROM cscart_product_descriptions WHERE page_title<>'';   -- должно ≈ числу товаров
-- количество/наличие:
SELECT (amount>0) в_наличии, COUNT(*) FROM cscart_products GROUP BY amount>0;

Плюс на живом сайте: карточка нового товара 200, имя «Деталь Марка Модель OEM», у проданного — бейдж «Продано».


НЕВЕРНЫЕ РЕШЕНИЯ — удалить / не использовать

Всё ниже — тупиковые/ошибочные подходы этой и прошлых сессий. Удалить файлы и НЕ описывать как рабочее.

Скрипты на сервере (~/import_run/, ~/lideravto.ru/) — УДАЛИТЬ

Аддон lider_import (~/lider_import/) — НЕ использовать

Хуки (hooks.php, func.php) никогда не срабатывали — в advanced_import нет хук-точек, func.php не грузится. Его же ARCHITECTURE.md это признаёт. Логика узлов (node_mapping) — другая модель (Марка/Модель/Узел), не текущая (Марка/Модель/Наименование). Не устанавливать.

Модуль lider_sold — выкинуть

app/addons/lider_sold, cscart_lider_sold, lider_sold_v1.0.0.tgz. func.php не грузится; наличие — по amount.

Неверные ОПИСАНИЯ (исправить/удалить)

Почему так вышло (чтобы не повторить)

advanced_import выглядит «современнее» и имеет удобные пресеты в админке — но в этой версии он не исполняет секции схемы (import_after_process_data и т.д.), поэтому обходит перехват my_changes. Рабочий каталог (17456 товаров с SEO) построен ядром exim. Всегда проверять: у правильно импортированного товара page_title НЕ пустой — это маркер, что схема my_changes отработала.