Skip to content

Latest commit

 

History

History
587 lines (491 loc) · 51.3 KB

File metadata and controls

587 lines (491 loc) · 51.3 KB

Интеграционный план: 1С-Битрикс

Реализация модуля импорта каталога OneCatalog Wiki API на платформе 1С-Битрикс (редакция «Малый бизнес»/«Бизнес», модуль iblock + catalog). Эталон — WooCommerce (см. INTEGRATION-STANDARD.md). Внешние контракты (§2) и инварианты (§5) стандарта неизменны; меняется только слой «как это лечь в Битрикс».

Модуль: отдельный сторонний модуль vendor.onecatalog (НЕ правка ядра, НЕ шаблон). Структура:

/bitrix/modules/vendor.onecatalog/
  include.php                 — автозагрузка классов (Loader::registerAutoLoadClasses)
  install/index.php           — vendor_onecatalog (CModule): InstallDB/UnInstallDB,
                                InstallEvents, InstallFiles, регистрация агентов, права
  install/version.php         — массив с 'VERSION' (SemVer) и 'VERSION_DATE'
  install/db/ (mysql)         — таблицы трекинга медиа/очереди (если не HL-блок)
  lib/
    api.php                   — Api: base URL, token, lang, GET, обёртка {data,success}
    units.php                 — Units: конвертер вес(г)/размеры(мм) → ед. каталога
    media.php                 — Media: скачивание, MIME, обложка/галерея, качество, дедуп
    taxonomies.php            — Taxonomies: свойства-списки/HL, термины, разделы
    collectionimporter.php
    brandimporter.php
    countryimporter.php
    productimporter.php       — оркестратор
    queue.php                 — Queue: порции, агент-обработчик, лог
    settings.php              — Options-обёртка над COption + валидация
  admin/
    vendor_onecatalog_settings.php   — страница настроек (options.php style)
    vendor_onecatalog_mapping.php    — ручной маппинг характеристик
    vendor_onecatalog_import.php     — кнопка/пикер + прогресс
    menu.php                          — пункт в админ-меню
  ajax/import.php             — контроллер приёма списка public_id + статус (аналог REST)
  lang/ru/, lang/en/          — языковые файлы
  options.php / .settings.php — при необходимости

Зависимости направлены «вниз» (как в §4): ProductImporter оркестрирует Api/Media/Taxonomies/*Importer; Queue вызывает ProductImporter; AJAX-контроллер вызывает Queue. Циклов нет.


Маппинг сущностей и реализация

Базовая структура каталога

Создаём один торговый инфоблок ONECATALOG_CATALOG (тип 1c_catalog или свой). Привязываем к каталогу: CCatalog::Add(['IBLOCK_ID' => $id]). Из стандарта торговые предложения (SKU/вариации) НЕ требуются — у OneCatalog нет вариаций товара, options[] это характеристики-фасеты, а не SKU. Поэтому простой каталог без инфоблока ТП (если позже понадобятся ТП — это отдельная задача, см. блокеры).

OneCatalog Битрикс — сущность Механизм / API
product Элемент инфоблока «Каталог» CIBlockElement::Add/Update
public_id (OC.IND.1) XML_ID элемента поле XML_ID, идемпотентность по нему
article (артикул произв.) Свойство ARTICLE (строка) + CCatalogProduct нет своего «SKU» свойство типа «строка»
options[] (характеристики) Свойства инфоблока типа «список» (L) или привязка к HL CIBlockProperty + CIBlockPropertyEnum
categories[] Разделы инфоблока (CIBlockSection) многоуровневая привязка элемента к разделам
brand Свойство-справочник (HL) или список или раздел — выбор в настройках BRAND
country Свойство-справочник (HL) или список — выбор в настройках COUNTRY
collections[] Отдельный инфоблок или раздел или свойство — выбор в настройках CIBlockElement / CIBlockSection / property
tags[] Множественное свойство-список TAGS (вкл/выкл) CIBlockProperty (MULTIPLE=Y)
images_urls / files PREVIEW_PICTURE + DETAIL_PICTURE + множественное свойство-файл MORE_PHOTO CFile::MakeFileArray
measurement_unit, sizes, areas_per_package, product_shape, … через событие OnAfterProductImported (сайтовый слой) событие модуля

public_id → XML_ID (идемпотентность, §5.1)

  • public_id пишем в XML_ID элемента. Поиск перед импортом: CIBlockElement::GetList([], ['IBLOCK_ID'=>$id, 'XML_ID'=>$publicId], false, false, ['ID']) → найден → Update, иначе Add. XML_ID редактируем в админке (вкладка «Подробно»).
  • ⚠️ Важно: XML_ID в Битрикс НЕ уникален на уровне БД и НЕ уникален в рамках инфоблока по умолчанию. Уникальность надо обеспечивать самим модулем (всегда искать по XML_ID перед Add) и желательно включить флаг инфоблока «Проверять уникальность XML_ID» (CIBlock field XML_ID unique — задаётся в свойствах инфоблока, но это контроль только в админ-форме, не в API). В коде — всегда find-first.
  • Альтернатива/дубль ключа: отдельное строковое свойство OC_PUBLIC_ID (на случай если XML_ID уже занят интеграцией с 1С). По умолчанию — XML_ID, переопределяемо настройкой/событием. Рекомендация: использовать XML_ID, но предусмотреть конфликт с обменом 1С (см. вопрос 3).
  • Легаси-фолбэк (§5.1): если ранее ключ лежал в другом поле — поиск по нему как фолбэк + миграция в XML_ID.

article → SKU/артикул (§5.2)

  • В Битриксе нет родного поля «Артикул» у элемента инфоблока (в отличие от WooCommerce SKU). Есть свойство-строка ARTICLE (часто заводят в торговых каталогах) — пишем article туда. public_id и article — разные поля.
  • Если article пуст — свойство не заполняем (не синтезировать).
  • Коллизия артикула: артикул в Битриксе по умолчанию НЕ уникален, но по стандарту «не перезаписывать чужой» — перед записью можно проверить, нет ли другого элемента с тем же ARTICLE и иным XML_ID; при коллизии — лог + пропуск записи свойства (значение источника всё равно сохраняем в событие). См. вопрос 8.

options[] → характеристики (§5.4)

Два режима (как в стандарте — ручной маппинг по specification_id ИЛИ по имени):

  1. Свойства типа «список» (PROPERTY_TYPE='L') — основной путь. Для каждой характеристики (specification_id + specification_label):
    • find-or-create свойство: ищем CIBlockProperty::GetList по инфоблоку с XML_ID свойства = oc_spec_<specification_id> (стабильный ключ маппинга), не по CODE (код мог быть транслитерирован по-разному). Если нет — создаём CIBlockProperty::Add(['PROPERTY_TYPE'=>'L', 'XML_ID'=>'oc_spec_<id>', ...]).
    • значения (термы): CIBlockPropertyEnumfind-or-create по XML_ID enum = specification_option_id (стабильно), либо по человекочитаемому VALUE (label) без учёта регистра, НЕ по CODE/слагу (§5.1 — иначе дубли при разных транслитах). Слаг/XML_ID — только для создания нового.
    • привязка значения к элементу: CIBlockElement::SetPropertyValues($elId, $iblock, [$enumId], $propCode).
  2. HL-блок — альтернатива для характеристик-справочников с доп. полями (если нужно хранить numeric/единицы). Свойство типа «привязка к элементам HL-блока» (UserType='directory'). Сложнее в установке (создание HL-блока, прав, таблицы). Делаем опциональным; по умолчанию — списки.

boolean-характеристики (§2.2, §5.6): specification_option_name = null, значение в bool_option. Без явного правила «true/false → значение терма» теряется. Решение: для boolean-свойства создаём два enum-значения (label из настройки маппинга, по умолчанию «Да»/«Нет» — локализуемо). bool_option === true → терм «Да», иначе (null/false/отсутствует) → терм «Нет» (§5.6: false по умолчанию). В ручном маппинге (строгий режим) вебмастер задаёт VALUE термов для true/false.

numeric-характеристики: numeric_option. В «список» класть число как label неудобно (фасеты разбухнут). Лучше — отдельное свойство типа «число» (N) с XML_ID='oc_spec_<id>'. Делаем определение типа свойства по specification_type (text→L, boolean→L с Да/Нет, numeric→N). Это отступление от строки таблицы «всё в список» — обосновано (см. вопрос 5).

categories[] → разделы (§5.4)

  • CIBlockSection: find-or-create по XML_ID раздела = id категории OneCatalog (стабильно), фолбэк-поиск по NAME (label) без учёта регистра (§5.1). Слаг — в CODE только при создании.
  • Иерархия: payload даёт плоский categories[] (id, menutitle, slug) без явного родителя в примере. Если иерархия не приходит — создаём плоско под корнем; если приходит parent — строим дерево (IBLOCK_SECTION_ID). См. вопрос 6.
  • Привязка элемента к разделам: основной раздел — IBLOCK_SECTION_ID; множественная привязка — поле IBLOCK_SECTION (массив) в CIBlockElement::Add (требует bWorkFlow=false, bUpdateSearch по вкусу).

brand / country → «тип объекта + цель» (§3, §7)

Настройка по каждой сущности: тип хранилища = свойство-список | свойство-справочник (HL) | раздел (для бренда) + конкретная цель.

  • Список (L): find-or-create enum по label → SetPropertyValues.
  • HL-справочник: find-or-create записи HL-блока (DataManager::add сгенерён. через HighloadBlockTable + CompileEntity), find по UF_NAME (label) или UF_XML_ID = id OneCatalog. Свойство товара — тип directory. Доп. поля бренда (страна, год основания, сайт, лого) кладутся в поля HL-записи через событие.
  • Раздел (для бренда — «бренд как раздел/страница»): CIBlockSection в отдельном инфоблоке «Бренды».

Дефолты (адаптация §7): бренд — HL-блок-справочник (ближайший аналог «нативной таксономии брендов» WooCommerce; в Битриксе нативной сущности «бренд» нет), включён; страна — свойство-список, выключена.

collections[] → инфоблок | раздел | свойство (§3, §7)

Настройка «тип объекта + цель»:

  • Отдельный инфоблок «Коллекции» (аналог WP CPT): элемент-коллекция, идемпотентность по XML_ID = id коллекции; поля (link_3d, link_official_site, лого) — свойства. Связь товар↔коллекция — свойство товара типа «привязка к элементам» (E) на инфоблок коллекций.
  • Раздел инфоблока каталога (коллекция как подраздел).
  • Свойство-список (простой случай).

Дефолт: отдельный инфоблок (богаче полей, как CPT в эталоне). Поля коллекции заполняются из эмбеда collections[] товара (стандарт §2.1: /collections/{slug}/ отдаёт 500 — НЕ дёргать, всё уже в payload товара).

tags[] → свойство (§7)

Множественное свойство-список TAGS (PROPERTY_TYPE='L', MULTIPLE='Y'), вкл/выкл, find-or-create enum по title (label), не по слагу. По умолчанию выкл.

media → PREVIEW/DETAIL + MORE_PHOTO (§2.3, §5.3)

  • images_urls.{min|middle|max} (обложка) → PREVIEW_PICTURE и/или DETAIL_PICTURE. files[] (галерея, category=images) → множественное свойство-файл MORE_PHOTO (тип F, MULTIPLE='Y') — стандартное имя для «детальных картинок» в Битриксе.
  • URL без расширения (§2.3): media_sideload-аналога нет; качаем вручную (HttpClient::get в temp-файл), определяем MIME по содержимому (CFile::GetContentType / finfo), формируем массив CFile::MakeFileArray($tmp, $mime) (с корректным расширением в имени, иначе CFile отвергнет/сохранит без типа). Передаём в DETAIL_PICTURE/SetPropertyValues.
  • Выбор размера: с токеном — max, без — min (§2.3). Размер — из настройки/ события (фильтр «порядок размеров»).
  • Трекинг качества (§5.3): рядом с файлом хранить выбранный размер. У CFile нет места под произвольную мету → ведём свою таблицу (или HL-блок) oc_media: content_key (hash(path#size)), size, FILE_ID, ссылки. На повторном импорте: если доступен размер лучше записанного — перекачать, заменить (CFile::Delete старого, если НЕ общий), обновить запись.
  • Дедуп по контент-ключу (§5.3): из base64-префикса URL извлекаем path, ключ = hash(path#size). Перед скачиванием ищем в oc_media по ключу → нашли → переиспользуем FILE_ID (без скачивания). ⚠️ Общие файлы (один файл у многих товаров / лого бренда) НЕ удалять при апгрейде качества (счётчик ссылок или флаг shared в oc_media).
  • Галерея идемпотентна по имени файла (§5.3): подписи в URL меняются — по URL не сравнивать; сравнивать по images_data.name/files[].name + контент-ключу.
  • ⚠️ CIBlockElement::Update со старой картинкой требует явного ['DETAIL_PICTURE' => $arr]; для удаления старого файла — ['del'=>'Y'] или CFile::Delete. Множественные свойства-файлы при апдейте надо аккуратно диффать (удалить лишние enum по PROPERTY_*_VALUE_ID), иначе дубли.

Конверсия единиц (§5.6) — Units

  • OneCatalog: вес — граммы, размеры — миллиметры (подписи локализованы: «мм», «г»); единица из sizes.*_unit, иначе мм/г.
  • Битрикс: вес товара — поле WEIGHT в CCatalogProductграммах — внутр. единица каталога!). Размеры — WIDTH/LENGTH/HEIGHT в CCatalogProductмиллиметрах). То есть внутренние единицы Битрикса СОВПАДАЮТ с OneCatalog (г/мм) — конверсия фактически тождественна, но конвертер всё равно обязателен:
    • нормализовать локализованную подпись единицы → коэффициент к базовой;
    • привести к г/мм (на случай, если источник отдаст кг/см или другую подпись);
    • таблица коэффициентов + нормализация подписей (как в §5.6), переопределяемо событием.
  • ⚠️ Тонкость: единицы отображения в публичной части задаются отдельно (measurement_unit товара — это «м²/шт» из payload, кладётся через событие, НЕ влияет на WEIGHT/WIDTH). Не путать ед. измерения товара (продажную) с ед. габаритов (физической). WEIGHT пишем через CCatalogProduct::Add/Update, а не через свойство.

Цена (§5.6) — НЕ задаётся

В источнике цены нет → не создавать ценовую запись CPrice. По умолчанию товар без цены (или в каталоге без модуля «Каталог» вообще цены нет). Для подстановки своей цены — событие/фильтр (сайтовый слой). Поля «только при создании» (статус активности ACTIVE) — не перезатирать при апдейте (§5.6).

Статус новых (§5.6, §7)

Поле ACTIVE элемента (Y/N). Битрикс не имеет 4 статусов как WP (publish/pending/draft/private). Маппинг: настройка «статус новых» → ACTIVE='Y'|'N' (+ опц. ACTIVE_FROM для отложенной публикации). Только при создании; при апдейте — не трогаем. См. вопрос 9 (нет полного аналога draft/ pending).


Фоновая очередь, медиа, настройки, i18n, жизненный цикл

Фоновая очередь (§6) — агенты + AJAX-степпер

  • Список public_id режем на порции по «шагу импорта» (минимум 10).
  • Два механизма (как и в стандарте — фолбэк без планировщика):
    1. AJAX-степпер (основной для UI): админ-страница шлёт ajax/import.php порцию public_id, контроллер импортирует их ProductImporter'ом синхронно в рамках одного запроса (порция ≤ шаг → укладываемся в таймаут), возвращает прогресс/лог; JS опрашивает и шлёт следующую порцию. Это надёжнее агентов (агенты в Битриксе запускаются на хитах/кроне и непредсказуемы по времени).
    2. Агенты (CAgent::AddAgent) — фолбэк/фоновый режим: регистрируем агент, который на каждом запуске берёт следующую порцию из своей таблицы очереди oc_queue (статусы pending/running/done/error), импортирует и помечает. Агент не должен крутить весь импорт в одном запуске (память/таймаут) — одна порция за вызов, далее агент перезапланируется.
  • Очередь хранить в своей таблице oc_queue (id, batch, public_id, status, message, ts) — НЕ в опциях. Лог последних N результатов — оттуда.
  • Идемпотентность очереди: повторная постановка того же public_id не плодит задачи (find-or-skip по статусу).
  • ⚠️ Агенты на shared-хостинге без cron запускаются «на хите» → могут стоять часами без посетителей. Поэтому основной путь — AJAX-степпер (активная вкладка админа гонит порции), агент — фон. См. вопрос 11.

Медиа

См. раздел маппинга выше (Media-слой): ручное скачивание HttpClient → MIME-детект → CFile::MakeFileArray; трекинг качества и дедуп в таблице oc_media; общие файлы не удалять. HttpClient с таймаутом, ошибки → null/skip (§5.5), без падения пакета.

Настройки (§7) — COption + админ-страница

Все опции через COption::SetOptionString('vendor.onecatalog', $key, $val) / GetOptionString. Страница настроек — admin/vendor_onecatalog_settings.php (стиль options.php, табы, CAdminTabControl).

Опция (COMODULE vendor.onecatalog) Назначение Дефолт
API_TOKEN X-API-Key (env/константа приоритетнее) пусто
API_BASE_URL базовый URL (переопределяемо) https://api.onecatalog.net/wiki/v1
LANG lang, только из en/ru/ar/zh/kk en
STEP размер порции, кламп ≥ 10 10
NEW_STATUS ACTIVE Y/N для новых Y
IMPORT_COLLECTIONS + COLLECTION_TARGET_TYPE/_TARGET вкл; инфоблок/раздел/свойство + цель вкл, инфоблок
IMPORT_BRAND + BRAND_TARGET_TYPE/_TARGET вкл; HL/список/раздел + цель вкл, HL
IMPORT_COUNTRY + COUNTRY_TARGET_TYPE/_TARGET список/HL + цель выкл, список
IMPORT_TAGS вкл/выкл выкл
SPEC_MANUAL_MAPPING + карта строгий маппинг spec_id→свойство, bool true/false термы выкл
CATALOG_IBLOCK_ID целевой инфоблок каталога — (выбор при установке)
  • Валидация и кламп (STEP ≥ 10); LANG — только из списка; приоритет env/константы (define('ONECATALOG_API_TOKEN', …) в dbconn.php/.settings.php) над опцией токена.
  • Для справочных сущностей — единый UI-паттерн «тип объекта + цель»; JS скрывает нерелевантные поля цели по выбранному типу.
  • Ручной маппинг характеристик — отдельная страница vendor_onecatalog_mapping.php: тянет /specifications/ (одним запросом с высоким limit — §2.1!), показывает список spec → выбор свойства инфоблока; для boolean — поля VALUE термов true/false. Ключ — specification_id (стабильный).

i18n (§9) — lang/

  • Исходные строки — английские, через Loc::getMessage('VENDOR_OC_…').
  • Файлы lang/en/…php и lang/ru/…php (массив $MESS[...]) — зеркало структуры файлов модуля (lang/<lang>/lib/api.php и т.д.).
  • Строки фронтовых JS (прогресс, пикер) — передавать из PHP в JS локализованными (CUtil::PhpToJSObject / BX.message), НЕ хардкодить в JS.
  • ⚠️ Битрикс не использует POT/PO/MO (это WordPress-механизм). Аналог — пары файлов lang/en + lang/ru. При добавлении строки UI — добавить в оба языка (иначе на одном языке покажется код сообщения). Это замена «пересборки .mo» из стандарта.

Жизненный цикл (§10) — модуль Битрикс

  • Манифест: install/index.php (класс vendor_onecatalog extends CModule): MODULE_ID, MODULE_VERSION/MODULE_VERSION_DATE (из install/version.php), MODULE_NAME, требования (MODULE_GROUP_RIGHTS='Y'). Зависимость от движка магазина: в DoInstall() проверять IsModuleInstalled('iblock') и IsModuleInstalled('catalog') — без них установка корректно сообщает об ошибке, не падает (§10: «активация не должна падать без движка магазина»).
  • SemVer + CHANGELOG (§10, и по памяти проекта): версия в install/version.php (одно место — лучше, чем 2 в WP); вести CHANGELOG.md (Keep a Changelog), бэклог = «Не выпущено», поднимать версию перед сборкой дистрибутива (.tar.gz модуля / last_version в update_info). Новые строки UI — в lang/en+lang/ru.
  • Установка (DoInstall): регистрация классов (include.php), создание инфоблока(ов) каталога/коллекций/брендов и базовых свойств (ARTICLE/MORE_PHOTO/TAGS/служебные), таблиц oc_queue/oc_media (или HL-блоков), регистрация агента (CAgent::AddAgent), регистрация событий модуля (RegisterModuleDependences), установка прав (InstallFiles/группы доступа).
  • Обновление/миграция (§10): механизм updater Битрикса (updater.php в install/), флаг «миграция выполнена» в опции; перенос старых ключей (напр. public_id из свойства в XML_ID), удаление устаревших опций.
  • Деинсталляция: DoUninstall — снять агент, события, опц. удалить таблицы (с подтверждением «сохранять данные?»). Инфоблоки/товары по умолчанию НЕ удалять.

Права (§11)

  • Модульные права через MODULE_GROUP_RIGHTS='Y' + GetModuleRightList() (уровни D/R/W). AJAX-контроллер ajax/import.php проверяет: $USER->IsAdmin() или право модуля ≥ W, и check_bitrix_sessid() (аналог nonce/CSRF из §11) на каждый POST. Без валидной сессии — 403.

❓ Вопросы и сомнения (ответить ДО старта)

  1. Целевой инфоблок: создавать модулем или брать существующий? На ceram-online уже может быть каталог (фронт — Vue/Nuxt по памяти проекта). Модуль должен создать свой инфоблок ONECATALOG_CATALOG или импортировать в действующий каталог сайта? От этого зависит вся схема свойств и риск конфликта кодов свойств.

  2. Простой каталог или торговые предложения (SKU)? В стандарте у товара нет вариаций (options[] — фасеты, не SKU). Подтвердить, что ТП (инфоблок offers) НЕ нужны. Если на сайте каталог построен на ТП — товар без ТП может не отображаться корректно в типовых компонентах.

  3. public_id → XML_ID: нет ли конфликта с обменом 1С? XML_ID — это поле, которым пользуется штатный обмен 1С:Предприятие ↔ Битрикс (CommerceML). Если на сайте есть выгрузка из 1С, она перезапишет XML_ID своими GUID-ами. Тогда public_id нужно класть в отдельное свойство OC_PUBLIC_ID, а не в XML_ID. Используется ли обмен с 1С? (Влияет на ключ идемпотентности — критично.)

  4. Уникальность XML_ID. Битрикс не гарантирует уникальность XML_ID. ОК ли полагаться на «модуль всегда ищет перед Add», или нужен жёсткий constraint (своя проверка + индекс)? Подтвердить политику при гонке параллельных импортов.

  5. Тип свойства для характеристик. Стандарт/таблица говорит «всё в список». Но numeric-характеристики разумнее хранить как свойство «число», а boolean — как список «Да/Нет». Согласовать: (а) numeric → число или список? (б) использовать HL-блоки-справочники для характеристик с доп. данными или хватит plain enum-списков?

  6. Иерархия категорий. В payload categories[] — плоский массив (id, menutitle, slug) без явного parent. Приходит ли родитель/дерево из API (отдельный вызов /categories/)? Строить плоско под корнем или дерево? Нужна разведка API (§порядок прототипирования, шаг 0).

  7. Единицы габаритов товара. Внутренние единицы Битрикса — г/мм (совпадают с OneCatalog). Подтвердить, что писать надо именно в CCatalogProduct.WEIGHT (граммы) и WIDTH/LENGTH/HEIGHT (мм), а НЕ в произвольные свойства. И отдельно: measurement_unit товара (м²/шт) — куда (свойство-список «единица», справочник measurement-units)? — это сайтовый слой (событие) или ядро?

  8. Уникальность артикула. Нужна ли проверка уникальности ARTICLE (как SKU в WP)? В Битриксе артикул по умолчанию не уникален. Блокировать запись при коллизии или просто логировать?

  9. Статусы товара. В WP 4 статуса, в Битриксе фактически ACTIVE=Y/N (+ACTIVE_FROM). Как маппить «pending/draft/private»? Достаточно ли Y/N, или нужен доп. свойство-статус «на модерации»?

  10. HL-блоки: редакция и права. Highload-блоки требуют модуль highloadblock (есть в «Малом бизнесе»? — да, начиная с определённых версий). Если бренд/ страна планируются в HL — подтвердить наличие модуля и право на создание HL-блока при установке. Иначе дефолт — свойства-списки.

  11. Очередь: cron настроен? AJAX-степпер требует открытой вкладки админа; агенты без cron запускаются «на хите». На каком хостинге крутится сайт, есть ли системный cron (cron_events.php / agent on cron)? Это определяет, можно ли закрывать вкладку во время большого импорта.

  12. Лимиты API и пагинация. Каков фактический дефолтный limit инстанса (§2.1 предупреждает о «молчаливом обрезании»)? Есть ли rate-limit на X-API-Key? Сколько товаров реально импортируем (десятки/тысячи)? — влияет на размер порции и тайминги.

  13. Пикер (iframe) в админке Битрикса. Виджет tools.onecatalog.net/picker.html грузится в iframe c postMessage. Админка Битрикса часто за HTTPS + CSP/X-Frame-Options. Разрешён ли внешний iframe в админке (CSP инстанса)? Если нет — фолбэк: импорт по вставленному списку public_id (стандарт §2.4 допускает «просто по списку ID»). Нужен ли пикер вообще на первом этапе?

  14. Язык и мультиязычность. Сайт одноязычный (ru)? Если мультиязычный — в Битриксе классический паттерн «инфоблок на язык», и тогда lang API ↔ языковая версия инфоблока — отдельная нетривиальная задача. Подтвердить, что нужен один язык (например ru).

  15. Версия Битрикса и редакция. Точная версия ядра/редакция (для доступности Bitrix\Main\Web\HttpClient, ORM, HL-блоков, D7-API). Можно ли писать на D7 (ORM, Bitrix\Main) или нужна совместимость со старым ядром (CIBlock*)?

  16. Дедуп медиа: где хранить контент-ключ. Своя таблица oc_media или HL-блок? У CFile нет места под мету. Подтвердить, что создание своей таблицы при установке приемлемо (а не только HL).


🚫 Сложно / невозможно на 1С-Битрикс

  1. Нет понятия «таксономия» — характеристики и справочники это РАЗНЫЕ механизмы. В WP атрибут/таксономия — единая абстракция (термины + связь). В Битриксе нет единой сущности: характеристика = свойство-список (enum) ИЛИ HL-справочник; категория = раздел; тег = свойство; бренд = HL/список/раздел. Поэтому «универсальный Taxonomies-слой» из §4 распадается на несколько разнотипных обработчиков. Обход: свой Taxonomies-слой с адаптерами под каждый тип цели (enum / HL / section), единый интерфейс find-or-create поверх них.

  2. «post type vs таксономия» для коллекции — нет CPT. В Битриксе нет «типов записей» как в WP; ближайший аналог CPT — отдельный инфоблок. Обход: коллекция-как-инфоблок (богатые поля, дефолт) / коллекция-как-раздел / коллекция-как-свойство — настройка «тип объекта + цель» реализуется через разные сущности Битрикса, но семантика «CPT» подменяется «инфоблоком», что тяжелее в создании/правах.

  3. Нет родного «бренда». В отличие от WooCommerce-таксономии product_brand, у Битрикса нет нативной сущности бренда. Обход: HL-блок-справочник (дефолт) — но это требует модуля highloadblock и создания таблицы/прав при установке. Дефолт стандарта «нативная таксономия брендов» на Битриксе невыполним as-is.

  4. XML_ID не уникален на уровне БД. Идемпотентность по XML_ID (§5.1) в WP опирается на уникальную мету; в Битриксе уникальность — только «по соглашению». Обход: всегда find-by-XML_ID перед Add + опц. включить контроль уникальности в настройках инфоблока (работает только в админ-форме, не в API) + защита от гонок на уровне модуля (блокировка/транзакция). Полной гарантии БД нет.

  5. Нет родного поля «Артикул/SKU» у элемента. WP SKU уникален и встроен; в Битриксе артикул — обычное свойство-строка без уникальности. Обход: свойство ARTICLE + (опц.) самостоятельная проверка коллизий. «SKU уникален» (§5.2) — не гарантируется платформой.

  6. CFile не хранит произвольную мету → трекинг качества/дедуп невозможны «рядом с файлом». В WP мета вложения хранит размер и контент-ключ. У CFile таких полей нет. Обход: своя таблица oc_media (content_key, size, FILE_ID, shared) — обязательна; иначе §5.3 (трекинг качества + дедуп) не выполнить. Доп. сложность: счётчик ссылок, чтобы «общие файлы не удалять при апгрейде».

  7. URL изображений без расширения + CFile придирчив к типу. Аналога media_sideload_image нет; CFile::MakeFileArray ждёт корректные имя/MIME. Обход: ручное скачивание HttpClient → MIME-детект по содержимому → подстановка расширения в имя файла перед MakeFileArray. Без этого картинки сохранятся без типа/не сохранятся.

  8. Агенты ≠ полноценная очередь. В WP — Action Scheduler (надёжный планировщик). Битрикс-агенты запускаются «на хите» (без cron — непредсказуемо), не дают гарантий времени, плохо параллелятся, легко «залипают» при ошибке. Обход: основной механизм — AJAX-степпер (синхронные порции под управлением вкладки админа), агенты — только фоновый фолбэк; своя таблица очереди со статусами. Импорт «в фоне при закрытой вкладке» надёжен только при системном cron.

  9. iframe-пикер в админке под CSP/X-Frame-Options. Админка Битрикса может запрещать внешние iframe (CSP инстанса/прокси). Обход: фолбэк «импорт по списку public_id» (стандарт это допускает, §2.4); пикер — опционально, при совместимом CSP. postMessage + проверка event.origin реализуемы, но встраивание не гарантировано.

  10. Мультиязычность через «инфоблок на язык» — дорого. WP-модель «один пост, переводы плагином» в Битриксе обычно = отдельный инфоблок на каждый язык. Связать lang API с языковой версией и сохранить идемпотентность по public_id между языковыми инфоблоками — отдельная крупная задача. Обход: на первом этапе — один язык (см. вопрос 14); мультиязык — отдельная фаза.

  11. Нет полного аналога статусов publish/pending/draft/private. Битрикс — ACTIVE=Y/N (+ACTIVE_FROM). Обход: маппинг в Y/N; «на модерации» — через доп. свойство-статус, но это уже не родной механизм, а соглашение модуля.

  12. «Цена не задаётся» — но без CPrice товар может выпасть из типовых компонентов каталога. Это не блокер импорта, но штатные компоненты каталога/корзины ждут цену; товар без CPrice корректно импортируется, однако может не показываться/не покупаться. Обход: цена — забота сайта через событие (§5.6/§8); на витрине ceram (Vue/Nuxt) это может быть неважно, но для типовых Битрикс-компонентов — учитывать.

  13. POT/PO/MO неприменимы. Механизм переводов стандарта (§9) — WordPress-овый. Обход: родные lang/<lang>/*.php ($MESS); «пересборка .mo» заменяется «обновить оба языковых файла». Семантически эквивалентно, но это другой инструментарий — пункт чек-листа §11 «POT + переводы» на Битриксе читать как «lang/en + lang/ru».


🔄 Синхронизация цен и остатков (§13 стандарта) — вопросы

Новый сценарий стандарта поверх импорта каталога: живой B2B-фид цен/остатков (retailer-share-products, отдельная авторизация url_key+private_key). Внешний контракт §13.1 и инварианты §13.4 неизменны; ниже — как это лечь в Битрикс и что подтвердить. Хорошая новость: у Битрикса цены/склады — нативные (типы цен, склады), поэтому раскладка по регионам/складам делается ядром, а не сайтовым слоем.

Маппинг §13 на Битрикс (предварительный)

§13 источник Битрикс — сущность Механизм
base_price (регион) CPrice для типа цены (CCatalogGroup) CPrice::Add/Update (PRODUCT_ID, CATALOG_GROUP_ID, CURRENCY)
регионы product_prices[] типы цен (опт/розница/регион) ИЛИ один тип BASE маппинг region_id → CATALOG_GROUP_ID
promo_price отдельный тип цены ИЛИ CCatalogDiscount нет нативного «sale_price» — см. вопрос 2
products_stocks[warehouse_id] склады CCatalogStore + CCatalogStoreProduct нативно поштучно по складам!
суммарный остаток CCatalogProduct.QUANTITY сумма по складам (Битрикс считает сам при включённом складском учёте)
код поставщика свойство OC_SUPPLIER_CODE для unknown-сопоставления
сигнатура change-detection свойство OC_PRICESTOCK_SIG или таблица oc_pricestock §13.4

Вопросы (ответить ДО старта §13)

  1. Регионы → типы цен или один регион? Битрикс умеет несколько типов цен (CCatalogGroup). Раскладывать product_prices[] по типам цен (тип на регион) или взять один регион по приоритету в базовый тип? От этого зависит схема цен.
  2. promo_price — как? В Битриксе нет поля «цена со скидкой» как в WC. Варианты: (а) отдельный тип цены «промо»; (б) скидка CCatalogDiscount; (в) писать promo в базовую цену; (г) игнорировать. Что нужно витрине?
  3. Остатки: нативные склады или суммарно? Использовать складской учёт Битрикса (CCatalogStore/CCatalogStoreProduct, раскладка по складам — нативно) или только суммарный QUANTITY? Включён ли складской учёт на сайте? Маппинг warehouse_id фида → склад Битрикса (find-or-create по XML_ID/названию).
  4. Валюта. В фиде валюты нет (base_price — число). CPrice требует валюту. Какая валюта каталога по умолчанию (CCurrency)?
  5. ⚠️ Конфликт с обменом 1С (критично). Если на сайте есть обмен 1С ↔ Битрикс, цены и остатки ведёт 1С — B2B-синк будет с ним конфликтовать (как XML_ID в вопросе 3 импорта). Тогда синк цен/остатков либо не нужен, либо пишет в отдельный тип цены/склад, не пересекаясь с 1С. Используется ли обмен 1С для цен/остатков?
  6. Доступность при нуле. Остаток 0 → деактивировать (ACTIVE=N), пометить «нет на складе», или оставить с «Разрешить покупку при отсутствии» (CAN_BUY_ZERO)? Зависит от витрины.
  7. Change-detection — где хранить сигнатуру? Свойство элемента OC_PRICESTOCK_SIG (проще) или своя таблица oc_pricestock (как oc_media)? §13.4 требует префетч сигнатур одним запросом — свойство индексируется, подойдёт.
  8. Расписание/нагрузка. Скан фида + запись цен/остатков порциями — агент CAgent (как очередь импорта, §6) или AJAX-степпер? На больших каталогах запись CPrice/CCatalogStoreProduct дозировать. Системный cron есть?
  9. unknown по коду поставщика. Свойство OC_SUPPLIER_CODE (множественное), поиск элемента по нему. Куда класть код, чтобы не конфликтовать с ARTICLE (артикул производителя, §5.2 импорта)?

🚫 Сложно / специфично для §13 на Битриксе

  1. Нет нативного «sale_price». Акция (promo_price) выражается типом цены или CCatalogDiscount — это не одно поле, а отдельный механизм (см. вопрос 2).
  2. Конфликт с 1С по цене/остатку — куда серьёзнее, чем в WC: 1С штатно перезаписывает цены/остатки. Нужна явная развязка (отдельный тип цены/склад) или отказ от синка там, где это ведёт 1С (вопрос 5).
  3. Change-detection без меты у элемента — как и в §5.3 (медиа): своя таблица/свойство под сигнатуру (вопрос 7).
  4. Плюс Битрикса: раскладку цен по регионам (типы цен) и остатков по складам §13.7 делает ядро нативно — сайтовый слой/ACF-аналог не требуется (в отличие от WC, где это через события). Точки расширения (события до/после) всё равно предусмотреть для проектных полей.