# Реализация создания торговых предложений (SKU) в Bitrix под Rest1C-API

## Цель
Сделать 100% рабочий механизм формирования SKU из групп похожих товаров, опираясь на практику Bitrix и демо-реализацию `citfact.argument/install/index.php::createDemoProductsWithOffers()`.

## Базовая модель Bitrix
- Родительский товар: элемент в `PRODUCTS_IBLOCK_ID`.
- SKU: элемент в `OFFERS_IBLOCK_ID`.
- Связь: свойство `CML2_LINK` в ИБ предложений.
- Цены: `CPrice`/`Bitrix\Catalog\PriceTable`.
- Остатки: `CCatalogProduct` или `CCatalogStoreProduct`.

## Минимально необходимое для работы
1. Модули включены: `iblock`, `catalog`.
2. Инфоблоки существуют и связаны:
   - `PRODUCTS_IBLOCK_ID` и `OFFERS_IBLOCK_ID`.
   - В `OFFERS_IBLOCK_ID` есть свойство `CML2_LINK` (тип: привязка к элементам, LINK_IBLOCK_ID = PRODUCTS_IBLOCK_ID).
3. Обязательные свойства для SKU существуют (минимум):
   - `CML2_ARTICLE`.
   - При необходимости: `CML2_BAR_CODE`, `CML2_BASE_UNIT`, `PACKAGE_COUNT`, `MIN_ORDER_COUNT`.
4. Источник цен и остатков определен и доступен.
5. Права на создание/обновление элементов, цен и остатков.

## Проверки перед созданием
- `CCatalogSKU::GetInfoByProductIBlock(PRODUCTS_IBLOCK_ID)` возвращает `OFFERS_IBLOCK_ID` и `SKU_PROPERTY_ID`.
- `CML2_LINK` существует в `OFFERS_IBLOCK_ID` и ссылается на `PRODUCTS_IBLOCK_ID`.
- Существует тип цены (например, BASE) и известен его ID.

## Входные данные
`similar_*.json`:
- `groups[]` с `items[]`.
- `ignored_props` содержит отличия, которые становятся свойствами SKU.
- `all_props` используется для дополнительных полей.

## Алгоритм (строго рабочий)
### 1. Выбор родителя
- Выбрать один элемент группы как родителя (правило фиксируется в коде).
- Родитель остается в `PRODUCTS_IBLOCK_ID`.

### 2. Обновить родителя
- Если родительского элемента нет, создать `CIBlockElement::Add`.
- Установить тип товара родителя:
  - `CCatalogProduct::Add/Update(['ID' => $productId, 'TYPE' => Bitrix\Catalog\ProductTable::TYPE_SKU])`.

### 3. Создать SKU
Для каждого элемента группы, кроме родителя:
- `CIBlockElement::Add` в `OFFERS_IBLOCK_ID`:
  - `PROPERTY_VALUES['CML2_LINK'] = $productId`.
  - свойства из `ignored_props` (артикул, штрихкод, упаковка и пр.).
- Зафиксировать SKU в каталоге:
  - `CCatalogProduct::Add/Update(['ID' => $offerId, 'TYPE' => Bitrix\Catalog\ProductTable::TYPE_OFFER, ...])`.
- Добавить цену:
  - `CPrice::Add(['PRODUCT_ID' => $offerId, 'CATALOG_GROUP_ID' => $priceTypeId, 'PRICE' => $price, 'CURRENCY' => 'RUB'])`.
- Добавить остаток:
  - `CCatalogProduct::Update($offerId, ['QUANTITY' => $qty])`.
  - При учете по складам: `CCatalogStoreProduct::Add/Update`.

### 4. Дубликаты в основном каталоге
- Все элементы группы, кроме родителя, удалить или деактивировать в `PRODUCTS_IBLOCK_ID`.

### 5. Итоговая проверка
- У родителя есть предложения в админке.
- На витрине отображается один товар с вариантами.

## Идемпотентность
- Перед созданием SKU проверять, не существует ли уже предложение с:
  - `CML2_LINK = $productId` и совпадающими ключевыми свойствами (например, `CML2_ARTICLE`).
- Если существует, обновлять, а не создавать дубликат.

## Маппинг свойств SKU
- Коды свойств SKU сопоставлены заранее.
- Значения из `ignored_props` приводятся к формату Bitrix (справочники, списки, множественные значения).
- Для свойств типа список использовать `CIBlockProperty::GetPropertyEnum` и ставить ID значения списка.

## Логирование и откат
- Логировать: ID группы, выбранного родителя, созданные SKU, ошибки Bitrix.
- При ошибке создания SKU: записать в лог и продолжить обработку.
- Для массовых операций — режим dry-run.

## Тесты
- Unit: выбор родителя, маппинг `ignored_props` -> свойства.
- Integration: создание родителя + SKU + цена + остаток.
- E2E: проверка отображения SKU на карточке.

## Пример кода (минимум)
```php
// Родитель
$productId = (new CIBlockElement)->Add([
  'IBLOCK_ID' => PRODUCTS_IBLOCK_ID,
  'NAME' => 'Parent',
  'ACTIVE' => 'Y',
]);
CCatalogProduct::Add([
  'ID' => $productId,
  'TYPE' => \Bitrix\Catalog\ProductTable::TYPE_SKU,
]);

// SKU
$offerId = (new CIBlockElement)->Add([
  'IBLOCK_ID' => OFFERS_IBLOCK_ID,
  'NAME' => 'SKU 1',
  'ACTIVE' => 'Y',
  'PROPERTY_VALUES' => [
    'CML2_LINK' => $productId,
    'CML2_ARTICLE' => 'SKU-001',
  ],
]);
CCatalogProduct::Add([
  'ID' => $offerId,
  'TYPE' => \Bitrix\Catalog\ProductTable::TYPE_OFFER,
]);
CPrice::Add([
  'PRODUCT_ID' => $offerId,
  'CATALOG_GROUP_ID' => 1,
  'PRICE' => 1999,
  'CURRENCY' => 'RUB',
]);
CCatalogProduct::Update($offerId, ['QUANTITY' => 10]);
```

## Привязка к текущей системе
- Использовать модель, как в `createDemoProductsWithOffers()`:
  - родитель в `argumentCatalog`.
  - SKU в `argumentOffers`.
  - связь через `CML2_LINK`.
  - цена и каталог создаются отдельными API вызовами.

## Как создается демо-SKU в install
Источник: `citfact.argument/install/index.php::createDemoProductsWithOffers()`.

Шаги реализации в установщике:
1. Создает разделы в `argumentCatalog` (уровни 1 и 2) через `CIBlockSection::Add`.
2. Создает родительский товар в `argumentCatalog`:
   - `CIBlockElement::Add` с `IBLOCK_ID = $this->iblockIdCatalog`.
3. Формирует массив офферов `$arFieldsOffers` для `argumentOffers`:
   - `IBLOCK_ID = $this->iblockIdOffers`.
   - `PROPERTY_VALUES['CML2_LINK'] = $newElementId`.
   - `PROPERTY_VALUES['CML2_ARTICLE']` и отличающиеся свойства.
   - Свойства списка `MEMORY_SIZE` и `COLOR` ставятся как ID значения списка через `CIBlockProperty::GetPropertyEnum(...)->Fetch()['ID']`.
4. Для каждого оффера:
   - `CIBlockElement::Add` в `argumentOffers`.
   - Запись в каталог через `Product::add` с `VAT_INCLUDED` и `VAT_ID`.
   - Две цены через `Price::add`: базовая и партнерская (`$this->priceTypeIdBase`, `$this->priceTypeIdPartner`).

Примечания по демо-логике:
- `CML2_LINK` — ключевая связь между родителем и SKU.
- Цены добавляются отдельно от создания элемента предложения.
- Цена и остаток на родителе не используются, они на стороне SKU.
- Имена и коды офферов формируются явно (например, `apple-iphone-11-64-red`).
- Для enum-свойств используется ID значения списка, а не строковое значение.

## Текст из PDF (извлеченный)
```text
Анализ группы похожих товаров и
формирование SKU в 1C-Битрикс
Структура файла с групповыми товарами (похожие товары)
Файл similar_28954d6e-406e-442b-9004-705f15281dc1.json представляет собой результат
работы алгоритма поиска похожих товаров. В ветке develop реализована функция, которая
группирует товары с совпадающими ключевыми свойствами (бренд, раздел каталога и др.),
игнорируя определённые отличия. В итоге формируются группы товаров, каждый из которых
содержит несколько элементов каталога, считающихся дубликатами/вариациями одного
продукта. Структура JSON включает:
• summary – сводная информация (ID инфоблока, количество обработанных элементов,
число найденных групп дубликатов и пр.).
• groups – массив групп похожих товаров. Каждая группа содержит:
• signature_hash и signature – уникальный идентификатор группы (хэш и строка
свойств, по которым сгруппированы товары).
• items – список товаров в группе. У каждого товара указаны основные поля ( ID ,
XML_ID , NAME , brand, section) и два вложенных объекта:
◦ ignored_props – свойства, которые отличаются между товарами (эти коды были
исключены при составлении сигнатуры).
◦ all_props – все свойства товара (после нормализации значений).
Алгоритм группировки в коде ( admin.php ) формирует сигнатуру товара – совокупность
значений ключевых свойств – и использует её для идентификации дубликатов. В сигнатуру
включены общие характеристики (ID инфоблока, бренд, раздел каталога и др.), а исключены
свойства, которые могут различаться у вариаций одного продукта. Например, из сравнения
исключаются: артикул, штрихкод, базовая единица, коэффициент упаковки и пр. 1 . В
конфигурации сравнения ( getSimilarCompareConfig ) явно указаны игнорируемые коды
свойств:

$fixedIgnore = array_filter(array_map('strtoupper', array_merge(
$shortcodeCodes,
// например, 'SHORTCODE'
$barcodeCodes,
// например, 'CML2_BAR_CODE' и др.
['CML2_BASE_UNIT', $articlePropCode, 'PACKAGE_COUNT', 'MIN_ORDER_COUNT']
)));
Таким образом, товары считаются похожими, если совпадают все свойства кроме
перечисленных выше. В выходном JSON эти отличающиеся свойства каждого товара указаны в
ignored_props . Например, в группе могут быть товары, отличающиеся артикулом
( CML2_ARTICLE ), штрихкодом, упаковкой или минимальным количеством заказа – именно эти
поля вынесены как различия.

1

Выделение различий для торговых предложений
При анализе групп видно, что товары внутри одной группы имеют общую основу (название,
бренд, категорию и пр.), но отличаются отдельными параметрами. Эти параметры и есть
кандидаты на оформление как отдельные торговые предложения (SKU) одного товара. К
наиболее важным различиям относятся:
• Артикул (уникальный код товара, свойство CML2_ARTICLE ). В группе дубликатов каждый
элемент обычно имеет свой артикул 2 3 . Это ключевой идентификатор варианта
товара.
• Штрихкод ( CML2_BAR_CODE или дополнительные поля штрихкодов). У вариативных
товаров обычно разные штрихкоды у каждого SKU.
• Базовая единица измерения ( CML2_BASE_UNIT ) – например, один вариант продаётся
штуками, другой упаковками.
• Коэффициент в упаковке ( PACKAGE_COUNT ) – сколько штук в одной упаковке, может
отличаться у вариаций.
• Мин. партия/заказ ( MIN_ORDER_COUNT ) – минимальное количество для заказа, тоже
может различаться.
• Цена – в JSON-файле цена не указана напрямую (цены хранятся отдельно в каталоге), но
очевидно, что у разных вариантов товара могут быть разные цены.
• Остатки на складе – каждый вариант (SKU) имеет свой собственный остаток. В Bitrix
остатки хранятся не как свойство инфоблока, а в модуле каталога, но логически тоже
относятся к индивидуальному предложению.
Иными словами, все поля, которые намеренно игнорировались при поиске похожих товаров,
становятся отличительными атрибутами торговых предложений. В файле эти различия
перечислены в ignored_props каждого товара группы, что упрощает их выявление. Именно
сочетание этих отличий определяет каждое торговое предложение.
Например, если группа содержит три товара с одинаковыми названием и брендом, но разными
артикулами и ценами, то будем считать, что это один продукт с тремя SKU – каждый SKU имеет
свой артикул и цену, а общий товар – единое название и описание.

Формирование торговых предложений (SKU) в Bitrix
В 1С-Битрикс для реализации вариативности используется механизм торговых предложений.
Основная идея: один “родительский” товар в каталоге содержит общее описание, а конкретные
варианты (SKU) оформляются как элементы отдельного инфоблока торговых предложений,
связанные с родителем через свойство (обычно CML2_LINK ). На основе групп из JSON можно
автоматизировать создание таких связей следующим образом:
1. Подготовка инфоблоков: убедиться, что в Bitrix настроена пара связанных инфоблоков –
основной инфоблок товаров и инфоблок торговых предложений. В .env переменных
обычно

задаются

идентификаторы:

PRODUCTS_IBLOCK_ID

(товары)

и

OFFERS_IBLOCK_ID (торг. предложения). Также на стороне Bitrix каталог должен знать,
что OFFERS_IBLOCK_ID привязан к основному как инфоблок SKU.
2. Итерация по группам: прочитать JSON и пройтись по каждой группе в data.groups .
Если в группе только один элемент ( count == 1 ), её можно пропустить – это уникальный

2

товар без вариаций. Интерес представляют группы с count > 1 , где нужны торговые
предложения.
3. Выбор главного товара: для каждой группы решить, какой элемент станет родительским
товаром, а какие – его SKU:
4. Чаще удобно взять один из элементов группы за основной товар. Например, первый в
списке или товар с наибольшим остатком/основным артикулом. Этот элемент останется
(или будет создан) в основном инфоблоке каталога товаров.
5. Остальные элементы группы будут оформлены как торговые предложения. Их не должно
быть в основном инфоблоке как отдельные активные товары, иначе на сайте будут
дубликаты. Если эти элементы уже существуют в каталоге, их можно деактивировать или
удалить из основного инфоблока после переноса в предложения.
6. Создание торговых предложений: для каждого “дочернего” элемента группы выполнить
добавление в инфоблок предложений, связав с родителем:
7. Использовать API Битрикс, например класс CIBlockElement . Заполнить поля нового
предложения:
◦ IBLOCK_ID – ID инфоблока торговых предложений.
◦ NAME – название. Обычно можно дублировать название родителя либо уточнить
(например добавить размер/вариант, если есть соответствующее свойство).
◦ PROPERTY_VALUES – задать значения свойств торгового предложения:
◦ Ссылка на товар – свойство-связка (например, код свойства CML2_LINK ):
значение – ID родительского товара.
◦ Артикул – свойство CML2_ARTICLE : значение из ignored_props текущего
элемента 2 .
◦ Прочие свойства варианта – например, штрихкод ( CML2_BAR_CODE ), единица
( CML2_BASE_UNIT ), упаковка ( PACKAGE_COUNT ), мин.заказ ( MIN_ORDER_COUNT ) и
т.д. – установить из данных этого элемента (они доступны в ignored_props или
all_props группы

4

).

8. Пример кода добавления (псевдокод):

$el = new CIBlockElement;
$offerFields = [
"IBLOCK_ID" => OFFERS_IBLOCK_ID,
"NAME"
=> $offerName,
"PROPERTY_VALUES" => [
"CML2_LINK"
=> $mainProductId,
// привязка к товару
"CML2_ARTICLE" => $item['ignored_props']->CML2_ARTICLE,
"CML2_BAR_CODE" => $item['ignored_props']->CML2_BAR_CODE,
// ... другие свойства варианта
],
];
$offerID = $el->Add($offerFields);
if ($offerID) {
// Установить цену для предложения (например, базовая цена, ID типа
цен = 1)

3

CPrice::Add([
"PRODUCT_ID" => $offerID,
"CATALOG_GROUP_ID" => 1,
"PRICE" => $itemPrice,
"CURRENCY" => "RUB"
]);
// Установить остаток на складе
CCatalogProduct::Add([
"ID" => $offerID,
"QUANTITY" => $itemQuantity
]);
}
Здесь CPrice и CCatalogProduct – классы модуля catalog Битрикс для управления
ценами и товарными параметрами. Мы передаём PRODUCT_ID равным ID нового SKU,
назначаем ему цену и количество. Важно: Цены и остатки берутся из системы (например,
1С) для каждого товара. Их нужно либо заранее иметь в данных, либо получить через API
(в контексте Rest1C-API, цены/остатки могли приходить отдельно).
9. Обновление родительского товара: убедиться, что у основного товара корректно
отражается наличие торговых предложений. Обычно, если инфоблоки правильно
связаны, добавление SKU автоматически помечает родительский элемент как товар с
торговыми предложениями. Можно дополнительно проверить/установить тип товара:
10. В модуле catalog есть понятие типа товара: обычный (без SKU) или родительский (с SKU).
Для родителя можно вызвать
CCatalogProduct::Update($mainProductId, ["TYPE" =>
\Bitrix\Catalog\ProductTable::TYPE_SKU]) – это обозначит, что у товара есть
предложения.
11. Цены и остатки у родительского товара, как правило, не используются (можно оставить
пустыми). В карточке товара на сайте будут отображаться предложения с их ценами.
12. Удаление/деактивация дубликатов: все элементы группы (кроме выбранного родителя)
следует убрать из основного каталога товаров, чтобы они не показывались отдельно.
Если мы использовали существующие элементы:
13. Один мы оставили как родителя. Ему можно при необходимости присвоить, скажем, самый
общий артикул (или не задавать артикул вовсе, т.к. у родителя может быть свойство
артикул, но в Bitrix часто артикул только у SKU).
14. Остальные – либо удалить, либо деактивировать (чтобы не отображались). Их функционал
теперь выполняют созданные торговые предложения. В случае деактивации можно
сохранить их в базе (на случай ссылок/истории), но исключить с витрины.
15. Проверка и тестирование: после генерации предложений проверить в админке 1CБитрикс:
16. У родительского товара должна появиться вкладка с торговыми предложениями,
перечисляющая все созданные SKU с их ценами и остатками.

4

17. На сайте в каталоге этот товар теперь представлен как один позиционный товар, внутри
которого можно выбрать вариант (SKU) по отличающимся параметрам (цене, упаковке и
т.д.).

Пример из ветки develop и автоматизация процесса
В ветке develop репозитория уже реализован поиск и группировка похожих товаров (через
admin.php?action=find_similar ). Эта реализация показывает, какие свойства нужно
учитывать и игнорировать при определении дубликатов. На её основе можно построить
скрипт автоматического формирования SKU. Ключевые моменты реализации в коде:
• Получение списка свойств инфоблока и определение кода свойства бренда и других
полей для сравнения. Например, в коде автоматически находится свойство “Бренд” и тоже
исключается из сравнения, чтобы не объединять товары разных брендов 5 6 .
• Использование игнорируемых кодов (см. выше) при построении сигнатуры товара.
Благодаря этому разные артикулы, штрихкоды и единицы не мешают объединять позиции
3 .
• Запись результата в файл JSON для анализа. Пример формирования одной группы в коде:
$groups[$hash]['items'][] = [
'ID'
=> (int)$fields['ID'],
'NAME' => (string)$fields['NAME'],
'XML_ID'=> (string)$fields['XML_ID'],
'brand' => $brandValue,
'type_section_id'
=> $sectionId,
'type_section_xml_id'=> $sectionXmlId,
'type_section_path' => $sectionPath,
'ignored_props' => (object)$ignoredValues,
'all_props'
=> (object)$allPropsValues,
];
Комментарий: здесь ignored_props содержит пары имя свойства => значение, именно
эти пары различаются у товаров группы 7 . В нашем случае они и станут
характеристиками разных торговых предложений.
Опираясь на этот пример, можно рекомендовать следующее для автоматизации:
- Добавить новый режим/скрипт в Rest1C-API, который будет читать сгенерированный JSON и
выполнять слияние товаров. Например, команду в admin.php или отдельный CLI-скрипт,
который по request_id загрузит соответствующий файл из storage/similar_products/ и
произведёт шаги 2–6, описанные выше.
- Аккуратно тестировать на копии базы: объединение товаров – необратимый процесс. Стоит
проверить, что все данные (особенно цены, остатки, ссылки на товары) корректно переносятся.
Возможно, временно отключить обмен с 1С на время слияния, чтобы предотвратить
рассинхронизацию.
В целом, автоматизация сводится к тому, чтобы по каждой группе из JSON создать один товар
с несколькими торговыми предложениями. Все различия по цене, артикулу, остаткам и др.
переносятся в уровень SKU. После этого 1C-Битрикс будет рассматривать группу как единый
товар с вариациями.

Примечание: убедитесь, что при интеграции с 1С новые торговые предложения не нарушат
обмен. В 1С обычно тоже есть понятие варианта (например, через одинаковый код
номенклатуры и разные характеристики). Возможно, придётся доработать обмен, чтобы 1С тоже
понимала эти SKU. В Rest1C-API можно использовать поле XML_ID родительского товара и,
например, дополнительные атрибуты для SKU, чтобы однозначно соотнести позиции.
Следуя приведённому подходу и опираясь на реализацию в ветке
устойчивый

механизм

формирования

торговых

предложений

develop , вы получите
из

предварительно

сгруппированных похожих товаров. Это позволит автоматически устранять дубли в каталоге и
корректно отображать вариативные товары на сайте, учитывая различия в цене, остатках,
артикулах и других свойствах каждого SKU. 3 7

1

2

3

4

5

6

7

admin.php

https://github.com/OOO-Alpha/Rest1C-API/blob/550aeaad5a7bc32a26573fa411359fdce80cfae6/admin.php

6


```
