Разработка функционала "собери набор" на 1С-Битрикс
«Собери набор» — более лёгкая вариация конструктора: пользователь не настраивает совместимость компонентов, а просто выбирает N товаров из фиксированного пула, получая скидку за комплектность. Типичные кейсы: «Выбери 3 из 10 косметических средств», «Скомплектуй пицца + напиток + десерт», «Подарочная коробка: выбери 5 конфет из 30 вкусов». Задача кажется проще конструктора ПК, но в реализации есть нетривиальные моменты.
Структура функционала
Функционал «собери набор» состоит из трёх компонентов:
- Страница набора — описание акции, правила (сколько выбрать, фиксированная или переменная цена)
- Интерфейс выбора — сетка доступных товаров с чекбоксами/кнопками добавления
- Корзина набора — mini-корзина на странице, показывающая текущий выбор и прогресс
Хранение данных набора
Набор описывается элементом инфоблока (или записью кастомной таблицы):
// Свойства инфоблока "Наборы"
'MIN_ITEMS' => 3, // минимум позиций для активации скидки
'MAX_ITEMS' => 5, // максимум позиций в наборе
'SET_PRICE' => 1990, // фиксированная цена набора (если задана)
'DISCOUNT_PCT' => 15, // скидка на сумму выбранных товаров (альтернатива)
'PRODUCTS' => [12, 15, 18, 22, ...], // ID допустимых товаров (множественное св-во)
'ACTIVE_FROM' => '2025-03-01', // срок акции
'ACTIVE_TO' => '2025-04-01',
При большом пуле товаров (50+) список допустимых позиций задаётся не перечислением ID, а фильтром по разделу или тегу:
'ALLOWED_SECTIONS' => [5, 7, 12], // ID разделов, из которых можно выбирать
'ALLOWED_TAGS' => ['promo-may', 'gift-set'],
Интерфейс выбора: ключевые UX-паттерны
Счётчик прогресса — показывает «Выбрано 2 из 3». Без него пользователь теряется. Реализация:
const maxItems = 3;
let selected = [];
function toggleProduct(productId, btn) {
if (selected.includes(productId)) {
selected = selected.filter(id => id !== productId);
btn.classList.remove('selected');
} else if (selected.length < maxItems) {
selected.push(productId);
btn.classList.add('selected');
}
updateProgress();
}
function updateProgress() {
document.querySelector('.progress-text').textContent = `Выбрано ${selected.length} из ${maxItems}`;
document.querySelector('.add-to-cart-btn').disabled = selected.length < minItems;
}
Блокировка лишних выборов — когда выбрано максимальное количество, остальные товары становятся неактивными (disabled), но с возможностью снять выбор с уже добавленных. Визуально: карточка серая, кнопка неактивна.
Превью набора — справа (на десктопе) или снизу (на мобильном) фиксированная панель со списком выбранных товаров, финальной ценой и кнопкой «В корзину».
Расчёт цены
Два режима ценообразования:
Фиксированная цена набора. Пользователь выбрал любые 3 товара — платит 999 рублей, независимо от цен отдельных позиций. В корзине добавляется специальная позиция «Набор» с ценой 999 р., без детализации по товарам.
Скидка на выбранные позиции. Сумма цен выбранных товаров умножается на коэффициент (например, ×0,85 при скидке 15%). В корзине каждый товар добавляется по сниженной цене.
Для второго варианта в Битрикс можно использовать правила корзины (b_sale_discount), но проще установить цену программно при добавлении в корзину через поле PRICE позиции и CUSTOM_PRICE => 'Y'.
Добавление в корзину
// Контроллер AJAX-запроса добавления набора в корзину
public function addSetToCartAction(int $setId, array $productIds): array {
// 1. Валидация: все productIds допустимы для данного набора
$setData = $this->loadSetData($setId);
if (!$this->validateProducts($productIds, $setData)) {
return ['success' => false, 'error' => 'Недопустимые товары'];
}
// 2. Проверка количества
if (count($productIds) < $setData['MIN_ITEMS'] || count($productIds) > $setData['MAX_ITEMS']) {
return ['success' => false, 'error' => 'Неверное количество позиций'];
}
// 3. Рассчитать цены
$prices = $this->calcPrices($productIds, $setData);
// 4. Добавить в корзину
$setCode = 'bundle_' . $setId . '_' . uniqid();
$basket = \Bitrix\Sale\Basket::loadItemsForFUser(\CSaleBasket::GetBasketUserID(), SITE_ID);
foreach ($productIds as $i => $pid) {
$item = $basket->createItem('catalog', $pid);
$item->setFields([
'QUANTITY' => 1,
'CUSTOM_PRICE' => 'Y',
'PRICE' => $prices[$i],
'BASE_PRICE' => $prices[$i],
]);
// Записываем SET_CODE в props для группировки в корзине
$propCol = $item->getPropertyCollection();
$propCol->setProperty(['CODE' => 'SET_CODE', 'VALUE' => $setCode]);
}
$basket->save();
return ['success' => true, 'basket_count' => count($basket)];
}
Отображение в корзине и заказе
В корзине товары из одного набора должны отображаться как группа. Шаблон компонента bitrix:sale.basket.basket кастомизируется: товары группируются по SET_CODE, отображаются со скидкой и с возможностью редактировать набор (ссылка обратно на страницу конструктора).
В заказе (таблица b_sale_order_props) сохраняется SET_CODE как реквизит позиции — для корректной обработки возвратов и аналитики.
Ограничения по остаткам
Если товар из набора закончился на складе, его нельзя выбирать. Проверка остатков — через CCatalogProduct::GetByID() или через \Bitrix\Catalog\ProductTable:
$product = \Bitrix\Catalog\ProductTable::getRow([
'filter' => ['ID' => $productId],
'select' => ['QUANTITY', 'QUANTITY_TRACE', 'CAN_BUY_ZERO'],
]);
$available = ($product['CAN_BUY_ZERO'] === 'Y') || ($product['QUANTITY'] > 0);
Остатки кешируются на 5–10 минут — слишком частые запросы к базе при большом пуле товаров нагружают систему.
Аналитика
Набор — это конверсионный инструмент, его эффективность нужно измерять. Что отслеживать:
- Сколько пользователей начали выбор (просмотр страницы набора)
- Сколько завершили выбор и добавили в корзину
- Сколько дошли до оплаты
- Средний состав набора (какие товары выбираются чаще)
События передаются в Яндекс.Метрику или Google Analytics через dataLayer.push() при каждом шаге.
Сроки
| Вариант | Что входит | Срок |
|---|---|---|
| Простой набор (фикс. цена) | UI выбора + корзина + страница набора | 1–2 недели |
| Со скидкой на позиции + ограничения | + расчёт скидок, остатки, аналитика | 2–4 недели |
| Несколько активных наборов | + управление в админке, сроки акций | 3–5 недель |
Функционал «собери набор» работает лучше всего как ограниченная акция: срок действия создаёт urgency, а возможность выбора — ощущение персонализации. Это сочетание конвертирует лучше, чем статичный готовый набор по такой же цене.







