Скидки из кода - создание, обязательные поля, перенос
Заводим скидки скриптом, когда их десятки или когда один и тот же набор нужен сразу на нескольких сайтах. Штатного копирования скидки на товары в админке нет, и вручную её приходится повторять для каждого сайта заново.
Решение
Где живут правила
Читаем оба семейства скидок:
use Bitrix\Main\Loader;
Loader::includeModule('sale');Loader::includeModule('catalog');
// правило корзины: считается на составе заказа целиком$rules = \Bitrix\Sale\Internals\DiscountTable::getList([ 'select' => ['ID', 'NAME', 'LID', 'ACTIVE'],])->fetchAll();
// скидка каталога: меняет цену товара ещё на витрине$catalog = \Bitrix\Catalog\DiscountTable::getList([ 'select' => ['ID', 'NAME', 'SITE_ID', 'VALUE_TYPE', 'ACTIVE'],])->fetchAll();Правило корзины и скидка каталога - разные сущности в разных модулях, и путать
их дорого. D7-таблицы здесь годятся только для чтения: методы add, update и
delete у Catalog\DiscountTable оставлены заглушками и бросают исключение.
Создание правила корзины скриптом
Заводим скидку десять процентов от суммы:
$discountId = \CSaleDiscount::Add([ 'LID' => 's1', 'NAME' => '10% для заказов от 5000', 'ACTIVE' => 'Y', 'USER_GROUPS' => [2], // обязателен и не бывает пустым 'CURRENCY' => 'RUB', // нужна и процентной скидке тоже 'CONDITIONS' => [ 'CLASS_ID' => 'CondGroup', 'DATA' => ['All' => 'AND', 'True' => 'True'], 'CHILDREN' => [[ 'CLASS_ID' => 'CondBsktAmtGroup', 'DATA' => ['logic' => 'EqGr', 'Value' => 5000], // EqGr это >= 'CHILDREN' => [], ]], ], 'ACTIONS' => [ 'CLASS_ID' => 'CondGroup', 'DATA' => ['All' => 'AND'], 'CHILDREN' => [[ 'CLASS_ID' => 'ActSaleBsktGrp', 'DATA' => ['Type' => 'Discount', 'Value' => 10, 'Unit' => 'Perc'], 'CHILDREN' => [], ]], ],]);Группы пользователей задаются в момент создания, и при пустом списке скидка
молча не применится ни к кому. Плоские поля вроде PRICE_FROM и
DISCOUNT_VALUE устарели с версии 14.0, поэтому условия описываются деревом.
Правку существующего правила делает CSaleDiscount::Update с тем же набором
полей.
Скидка каталога и её обязательные поля
Заводим процентную скидку на конкретный товар:
$catalogId = \CCatalogDiscount::Add([ 'SITE_ID' => 's1', 'ACTIVE' => 'Y', 'NAME' => 'Скидка 10% на товар', 'VALUE_TYPE' => \CCatalogDiscount::TYPE_PERCENT, 'VALUE' => 10, 'CURRENCY' => 'RUB', // без валюты Add вернёт false 'CONDITIONS' => [ 'CLASS_ID' => 'CondGroup', 'DATA' => ['All' => 'AND', 'True' => 'True'], 'CHILDREN' => [[ 'CLASS_ID' => 'CondIBElement', 'DATA' => ['logic' => 'Equal', 'value' => $productId], ]], ],]);if (!$catalogId) { global $APPLICATION; throw new \RuntimeException($APPLICATION->GetException()->GetString());}Метод возвращает false без единого слова о причине: текст ошибки лежит в
$APPLICATION->GetException(), и без этой проверки скрипт молча создаёт ноль
скидок. Условие CondIBElement ссылается на товар по идентификатору, а
несуществующий идентификатор в списке ломает всю запись целиком.
Включение купонов у скидки
Создаём купон и разрешаем его использование:
$coupon = \Bitrix\Catalog\DiscountCouponTable::add([ 'DISCOUNT_ID' => $catalogId, 'ACTIVE' => 'Y', 'COUPON' => \CatalogGenerateCoupon(), // не длиннее 32 символов 'TYPE' => \Bitrix\Catalog\DiscountCouponTable::TYPE_ONE_ORDER,]);// признак работы с купонами включается отдельным вызовом, а не полем скидки\Bitrix\Catalog\DiscountTable::setUseCoupons($catalogId, 'Y');Поле USE_COUPONS в CCatalogDiscount::Add передавать нельзя: купоны от этого
не включатся, и код будет отвергаться при проверке. Порядок обязателен - сначала
купон, потом признак у скидки.
Проверка, что скидка применилась
Смотрим скидки товара и сработавшие правила заказа:
global $USER;$active = \CCatalogDiscount::GetDiscountByProduct( $productId, $USER->GetUserGroupArray(), 'N', [$basePriceTypeId], SITE_ID);print_r($active); // пустой массив - скидок нет, false - товар некорректен
$order = \Bitrix\Sale\Order::load($orderId);print_r($order->getDiscount()->getApplyResult()); // что сработало в заказеПервый вызов проверяет скидку каталога с учётом групп покупателя и прав на тип цены. Второй показывает правила корзины по заказу и отвечает на вопрос точнее, чем сравнение сумм глазами.
Перенос набора скидок на другой стенд
Выгружаем правила вместе с условиями:
$dump = [];foreach (\Bitrix\Sale\Internals\DiscountTable::getList([ 'select' => ['ID', 'NAME', 'LID', 'ACTIVE', 'SORT', 'CURRENCY', 'CONDITIONS', 'ACTIONS'],])->fetchAll() as $row) { unset($row['ID']); // ID на приёмнике будет свой $row['CONDITIONS'] = unserialize($row['CONDITIONS']); $row['ACTIONS'] = unserialize($row['ACTIONS']); $dump[] = $row;}file_put_contents(__DIR__ . '/discounts.json', json_encode($dump));На приёмнике файл читается и каждая запись уходит в CSaleDiscount::Add. Перед
этим правятся все идентификаторы: LID берётся от нужного сайта, а товары и
разделы внутри условий сопоставляются по внешнему коду. Группы пользователей в
дамп не попадают: они хранятся отдельной связью и задаются полем USER_GROUPS
при создании, поэтому их сопоставляют по названию руками. Купоны, счётчики их
применения и статистика скидок тоже заводятся на приёмнике отдельно.
Скрипт переноса запускают из консоли, и там SITE_ID не определена сама собой.
Константу задают явно, иначе в LID уедет код языка вместо кода сайта, а
выборка вернёт пустой список при живых скидках в админке.
Типичные проблемы
Скидка создана скриптом, но не применяется ни к одному заказу.
Поле USER_GROUPS пустое либо не передано вовсе. Группы задаются только в момент создания, и без них правило неприменимо.
CCatalogDiscount::Add вернул false без сообщения.
Чаще всего не передана CURRENCY: она обязательна даже для процентной скидки. Текст ошибки лежит в $APPLICATION->GetException().
Обновление скидки через Update не проходит.
В условии остались идентификаторы товаров, которых на этом стенде нет. Несуществующий элемент в списке отменяет запись целиком.
Выборка скидок в скрипте по крону возвращает пусто.
В консоли константа SITE_ID не определена и содержит код языка вместо кода сайта. Фильтр по LID при этом не находит ничего.
Купон создан, а скидка его не видит.
Не вызван DiscountTable::setUseCoupons после добавления купона. Признак работы с купонами включается отдельно от полей скидки.
Метод добавления купона вернул объект вместо кода.
Это Result, а не строка: успех проверяют через isSuccess, идентификатор берут через getId.
Частые вопросы
Можно ли скопировать скидку на другой сайт из админки?
У правил доставки копирование есть, у скидок на товары его нет. Набор для нескольких сайтов переносят скриптом: выгружают записи с условиями и создают заново с другим LID.
Почему нельзя писать скидки через D7-таблицы?
Методы add, update и delete у Catalog\DiscountTable оставлены заглушками и бросают исключение. Запись идёт легаси-классами, а D7-таблица нужна для чтения и для setUseCoupons.
Как правильно обновлять скидку через CSaleDiscount::Update?
Тем же набором полей, что и при создании, включая деревья условий и действий. Частая причина отказа - несуществующие идентификаторы товаров в условии, и лишний уровень группы в дереве действий тоже можно не передавать.
Что переносится вместе со скидкой, а что нет?
Переезжают поля записи и деревья условий с действиями. Не переезжают идентификаторы скидок, купоны с их счётчиками и любые ссылки на группы, типы цен и товары: они пересобираются на приёмнике.
Как узнать, какая скидка сработала в конкретном заказе?
Методом getApplyResult у объекта скидки заказа после его загрузки. Если вызов падает на getDiscount, значит заказ по этому номеру не найден и загрузка вернула пустое значение.
Смежное
- Цены, скидки и купоны - оглавление подтемы
- Каталог и продажи - устройство магазина целиком
- Правила работы с корзиной: условия, порядок, отладка - как правило применяется и почему молчит
- Купоны: типы, выпуск пачкой, применение и проверка - разбор купонов целиком
- Скидка на отдельную позицию в корзине - частный случай правила
- Путь цены: от типа цены до суммы в корзине - место скидок в общем расчёте
- Типы цен: розница и опт, права групп, цена по количеству - что сопоставлять при переносе
- Массовая правка товаров: обновление свойств скриптом - соседний жанр консольного скрипта
- Скидки и ручные цены при обмене - что перезаписывает обмен с 1С
- Перенос заказов и клиентов со старого сайта - перенос данных магазина рядом