Разработка калькулятора комплектации на 1С-Битрикс

Наша компания занимается разработкой, поддержкой и обслуживанием решений на Битрикс и Битрикс24 любой сложности. От простых одностраничных сайтов до сложных интернет магазинов, CRM систем с интеграцией 1С и телефонии. Опыт разработчиков подтвержден сертификатами от вендора.
Предлагаемые услуги
Показано 1 из 1 услугВсе 1626 услуг
Разработка калькулятора комплектации на 1С-Битрикс
Средняя
~1-2 недели
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1177
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Разработка на базе Битрикс, Битрикс24, 1С для компании Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Разработка на базе 1С Предприятие для компании МИРСАНБЕЛ
    747
  • image_crm_dolbimby_434_0.webp
    Разработка сайта на CRM Битрикс24 для компании DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Разработка на базе Битрикс24 для компании ТЕХНОТОРГКОМПЛЕКС
    976

Разработка калькулятора комплектации на 1С-Битрикс

Калькулятор комплектации решает задачу, которую стандартный каталог 1С-Битрикс не закрывает: пользователь конфигурирует продукт из набора взаимозависимых опций, видит итоговую цену в реальном времени и добавляет в корзину уже собранную комплектацию. Актуально для автомобильных дилеров, производителей мебели, IT-интеграторов, поставщиков промышленного оборудования.

Разница между вариантами товара и комплектацией

В стандартном каталоге Битрикс торговые предложения (SKU) покрывают фиксированные комбинации атрибутов. Комплектация — динамическая сборка, где:

  • опции взаимозависимы: выбор пакета «Люкс» включает опции A, B, C и делает недоступной опцию D
  • итоговая цена — сумма компонентов, а не фиксированная цена SKU
  • результат — набор товаров для корзины, а не один товар

Модель данных

HL-блок ConfiguratorComponents:

Поле Тип Описание
UF_PRODUCT_ID Int ID базового товара в каталоге
UF_GROUP_CODE String Группа опций (engine, color, wheels)
UF_OPTION_CODE String Код опции
UF_OPTION_NAME String Название для отображения
UF_BASE_PRICE Float Цена опции
UF_PRICE_TYPE Enum fixed / percent / delta
UF_INCOMPATIBLE String Коды несовместимых опций (через запятую)
UF_REQUIRED_WITH String Обязательные при выборе этой опции

HL-блок ConfiguratorPresets — готовые комплектации (Базовая, Стандарт, Люкс):

Поле Тип Описание
UF_PRODUCT_ID Int Базовый товар
UF_PRESET_CODE String Код пресета
UF_PRESET_NAME String Название
UF_OPTIONS_JSON Text JSON с выбранными опциями
UF_DISCOUNT_PERCENT Float Скидка на пресет

Ядро конфигуратора: управление зависимостями

Центральная проблема конфигуратора — синхронизация зависимостей. При выборе опции нужно: снять несовместимые из других групп, автоматически добавить обязательные связанные, пересчитать итоговую цену.

class ProductConfigurator {
    constructor(basePrice, components) {
        this.basePrice  = basePrice;
        this.components = components;
        this.selected   = {};
        this.graph      = this.buildIncompatibilityGraph();
    }

    buildIncompatibilityGraph() {
        const graph = {};
        this.components.forEach(c => {
            if (c.uf_incompatible) {
                graph[c.uf_option_code] = c.uf_incompatible.split(',').map(s => s.trim());
            }
        });
        return graph;
    }

    selectOption(groupCode, optionCode) {
        const blocked = this.graph[optionCode] || [];
        Object.keys(this.selected).forEach(g => {
            if (blocked.includes(this.selected[g])) delete this.selected[g];
        });

        this.selected[groupCode] = optionCode;

        const c = this.components.find(
            x => x.uf_group_code === groupCode && x.uf_option_code === optionCode
        );
        if (c && c.uf_required_with) {
            c.uf_required_with.split(',').forEach(code => {
                const req = this.components.find(x => x.uf_option_code === code.trim());
                if (req) this.selected[req.uf_group_code] = req.uf_option_code;
            });
        }

        return this.calculate();
    }

    calculate() {
        let total = this.basePrice;
        const breakdown = [];

        Object.entries(this.selected).forEach(([group, code]) => {
            const c = this.components.find(
                x => x.uf_group_code === group && x.uf_option_code === code
            );
            if (!c) return;

            let price = 0;
            if (c.uf_price_type === 'fixed')   price = c.uf_base_price;
            if (c.uf_price_type === 'percent')  price = this.basePrice * c.uf_base_price / 100;
            if (c.uf_price_type === 'delta')    price = c.uf_base_price;

            total += price;
            breakdown.push({ group, name: c.uf_option_name, price });
        });

        return { basePrice: this.basePrice, totalPrice: Math.round(total), breakdown };
    }
}

Пресеты как точка входа

Большинство пользователей не конфигурируют с нуля. Готовые пресеты служат отправной точкой с возможностью дотюнинга. Пресет со скидкой стимулирует выбрать готовый пакет вместо ручной сборки по частям:

function applyPreset(configurator, preset) {
    const options = JSON.parse(preset.uf_options_json);
    configurator.selected = {};
    Object.entries(options).forEach(([group, code]) => {
        configurator.selected[group] = code;
    });
    const result = configurator.calculate();
    if (preset.uf_discount_percent > 0) {
        result.totalPrice = Math.round(result.totalPrice * (1 - preset.uf_discount_percent / 100));
        result.presetDiscount = preset.uf_discount_percent;
    }
    return result;
}

Добавление комплектации в корзину Битрикс

public function addToCart(int $baseProductId, array $selectedOptions): \Bitrix\Main\Result
{
    $basket = \Bitrix\Sale\Basket::loadItemsForFUser(
        \Bitrix\Sale\Fuser::getId(),
        \Bitrix\Main\Context::getCurrent()->getSite()
    );

    $item = $basket->createItem('catalog', $baseProductId);
    $item->setFields([
        'QUANTITY' => 1,
        'PROPS' => [[
            'NAME'  => 'Комплектация',
            'CODE'  => 'CONFIGURATION',
            'VALUE' => json_encode($selectedOptions),
        ]]
    ]);

    foreach ($selectedOptions as $option) {
        if (!empty($option['product_id'])) {
            $optItem = $basket->createItem('catalog', $option['product_id']);
            $optItem->setFields([
                'QUANTITY'     => 1,
                'PRICE'        => $option['price'],
                'CUSTOM_PRICE' => 'Y',
            ]);
        }
    }

    return $basket->save();
}

Сохранение конфигурации пользователя

Для авторизованных пользователей — HL-блок SavedConfigurations:

$hash = md5($productId . json_encode($selectedOptions));

$existing = $SavedConfig::getList([
    'filter' => ['=UF_USER_ID' => $USER->GetID(), '=UF_HASH' => $hash]
])->fetch();

if (!$existing) {
    $SavedConfig::add([
        'UF_USER_ID'    => $USER->GetID(),
        'UF_PRODUCT_ID' => $productId,
        'UF_OPTIONS'    => json_encode($selectedOptions),
        'UF_PRICE'      => $totalPrice,
        'UF_HASH'       => $hash,
    ]);
}

Для гостей — sessionStorage или куки с ограниченным сроком жизни.

Сроки разработки

Масштаб Описание Срок
Базовый До 5 групп опций, без зависимостей, пресеты 5–8 дней
Стандартный 5–15 групп, несовместимости, сохранение конфигурации 2–3 недели
Сложный 15+ групп, позиции в корзине, история конфигураций 4–8 недель
Производственный Интеграция с 1С для актуальных цен и остатков 2–4 месяца

Главный технический вызов — граф зависимостей при большом числе опций. При 50+ вариантах ручное тестирование нереально: автоматизированные тесты для логики совместимости нужны с первого дня разработки.