Правила работы с корзиной
- Основные объекты
- Как выполняется расчет правил
- Из чего состоит правило
- Подготовить данные для работы с правилами
- Создать правило
- Обновить правило
- Удалить правило
- Работать с купонами
- Получить подарки по правилу
- Получить подсказку по правилу
- Прочитать настройки правил
- Рассчитать и применить правила
- Проверить, почему правило не применилось
- Связь с торговым каталогом
Правила работы с корзиной описывают условия и действия, которые влияют на сумму корзины и заказа. Правило может уменьшить цену товаров, применить наценку, зафиксировать цену позиции, изменить стоимость доставки, добавить подарок или остановить дальнейшие скидки.
Через API разработчик создает, обновляет, удаляет и читает правила, запускает расчет корзины или заказа, получает результат применения и читает сохраненные скидки заказа.
Основные объекты
|
Объект |
Роль в сценарии |
|
|
Класс классического API для создания, обновления и чтения правил работы с корзиной. Настраивает условия, действия, группы пользователей, купоны и порядок применения |
|
|
Рассчитывает правила работы с корзиной для корзины или заказа. Возвращает результат расчета, цены для показа и данные примененных правил |
|
|
Хранит купоны текущего расчета, проверяет купоны и передает их в расчет скидок |
|
|
Сохраняет и загружает результат расчета скидок заказа |
|
|
ORM-таблица купонов. Хранит код купона, тип, активность и ограничения использования |
|
|
Выполняет действия правила над корзиной и доставкой: меняет цену, формирует описание результата и отмечает примененные позиции |
|
|
Возвращает список подарков, которые правило может добавить к корзине |
|
|
Возвращает подсказки по правилам. Подсказка показывает покупателю, что нужно сделать, чтобы получить скидку |
Сохраненный заказ содержит не только текущие настройки правила, но и результат, который применился при расчете. Примененные скидки, купоны, правила и описания результата хранятся в таблицах OrderDiscountTable, OrderCouponsTable, OrderRulesTable и OrderRulesDescrTable.
Как выполняется расчет правил
Расчет правил начинается с данных корзины или заказа.
-
Система получает товары, цены, доставку, сайт и контекст пользователя.
-
Если покупатель ввел купон, система передает его в
DiscountCouponsManager, а расчет использует купон как входные данные. -
Метод
Discount::calculate()загружает активные правила для групп пользователя, сайта, модулей и купонов. -
Расчет проверяет условия правила из
CONDITIONS_LIST. -
Если условия выполнены, расчет выполняет действия из
ACTIONS_LIST. -
Заказ через
Order::doFinalAction(true)применяет результат к корзине, доставке и налогам. -
Метод
Order::save()сохраняет заказ и результат скидок.
Для корзины без заказа запускайте предварительный расчет, чтобы показать цены, проверить купон и прочитать результат. Для оформляемого заказа запускайте расчет через объект заказа: так связанные коллекции и итоговые суммы изменятся согласованно.
Не рассчитывайте правила вручную и не меняйте итоговые цены прямой записью. Расчет учитывает сайт, пользователя, группы пользователя, купоны, условия правил, товары корзины, цены, доставку, налоги и настройки магазина. Если изменить цену без расчета правил, заказ может не совпасть с правами пользователя и историей примененных скидок.
Из чего состоит правило
Правило работы с корзиной можно описать набором полей.
|
Поле |
Как влияет на расчет |
|
|
Связывает правило с сайтом. Правило участвует только в расчете своего сайта |
|
|
Определяют активность и период действия правила |
|
|
Ограничивают правило по сумме заказа в валюте правила |
|
|
Хранит дерево условий: какие товары, свойства корзины, параметры заказа, покупателя или предыдущих заказов должны совпасть |
|
|
Хранит дерево действий: что изменить в корзине или доставке, какой подарок предложить, какие позиции затронуть |
|
|
Указывает, нужен ли купон для применения правила: |
|
|
Определяют порядок применения среди других правил |
|
|
Останавливают дальнейшее применение правил полностью или на текущем уровне приоритета |
|
|
Хранят текст и условия подсказки, которую можно показать покупателю до применения правила |
Какие условия может учитывать правило
Правило проверяет:
-
сумму корзины или заказа,
-
товары, разделы, свойства товаров и свойства позиций корзины,
-
количество, цену, вес и другие поля позиции корзины,
-
поля заказа и доставки,
-
пользователя и группы пользователя,
-
данные прошлых заказов и накопительные условия,
-
наличие или отсутствие уже примененной скидки.
Если одно из указанных условий не совпадает, правило не применяется.
Какие действия может выполнить правило
Действия правила влияют на сумму корзины или заказа. Правило может:
-
применить скидку или наценку к позициям корзины в процентах,
-
применить скидку или наценку фиксированной суммой,
-
распределить сумму скидки по подходящим позициям,
-
установить фиксированную цену позиции,
-
применить накопительную скидку по сумме заказов пользователя,
-
применить скидку или наценку к доставке,
-
добавить подарок.
Как настроить порядок применения правил
Если к корзине подходят несколько правил, система применяет их не как независимые формулы, а последовательно. Результат предыдущего правила может изменить цену позиции, доставку или доступность позиции для следующих скидок.
Порядок расчета задают два поля.
-
PRIORITY— уровень приоритета. Чем больше значение, тем раньше правило идет в расчете. -
SORT— сортировка внутри одного уровня приоритета. Если у нескольких правил одинаковыйPRIORITY, раньше рассчитывается правило с меньшимSORT.
Схема порядка:
-
Система выбирает активные правила, которые подходят по сайту, группам пользователей, периоду действия и купонам.
-
Система сортирует правила по
PRIORITYот большего значения к меньшему. -
Внутри одного
PRIORITYсистема сортирует правила поSORTот меньшего значения к большему. -
После применения правила система проверяет
LAST_LEVEL_DISCOUNTиLAST_DISCOUNT.
Флаги остановки настраивайте с учетом этого порядка:
-
LAST_LEVEL_DISCOUNT = Yостанавливает следующие правила с тем жеPRIORITY, но не запрещает расчет правил с другим уровнем приоритета. -
LAST_DISCOUNT = Yостанавливает дальнейший расчет правил после применения текущего правила. -
LAST_DISCOUNT = NиLAST_LEVEL_DISCOUNT = Nразрешают системе перейти к следующим правилам по общей очереди.
Пример очереди:
|
Правило |
PRIORITY |
SORT |
Когда применится |
|
Персональная скидка клиента |
|
|
Первым, потому что приоритет выше остальных |
|
Скидка на раздел |
|
|
Вторым: приоритет ниже, чем у персональной скидки |
|
Скидка от суммы заказа |
|
|
Третьим: приоритет такой же, как у скидки на раздел, но сортировка больше |
Если персональная скидка клиента применится с LAST_DISCOUNT = Y, две следующие скидки не будут рассчитаны. Если скидка на раздел применится с LAST_LEVEL_DISCOUNT = Y, скидка от суммы заказа с тем же PRIORITY = 10 не будет рассчитана.
Подготовить данные для работы с правилами
Примеры статьи — отдельные шаги сценария. Чтобы использовать их в своем коде, подготовьте данные магазина и объекты заказа:
-
$userId— идентификатор пользователя, для которого рассчитываются правила, -
$orderId— идентификатор существующего заказа, -
$siteId— идентификатор сайта, -
$userGroupIds— массив идентификаторов групп пользователей, для которых действует правило, -
$productId— идентификатор товара, -
$sectionId— идентификатор раздела инфоблока, -
$deliveryId— идентификатор службы или профиля доставки, -
$basket— объектBitrix\Sale\Basket, -
$order— объектBitrix\Sale\Order.
Создать правило
Создавайте правила через метод классического API CSaleDiscount::Add(). В метод передают массив с настройками правила: сайт, активность, группы пользователей, условия, действия, купоны и порядок применения.
Не используйте для создания правила Bitrix\Sale\Internals\DiscountTable::add(). Прямой ORM-вызов не выполняет всю подготовку правила и не создает привязки к группам пользователей.
Структура CONDITIONS и ACTIONS
В CSaleDiscount::Add() передайте исходные структуры условий и действий: CONDITIONS и ACTIONS. При сохранении метод проверяет параметры, привязывает правило к группам пользователей и формирует подготовленные структуры CONDITIONS_LIST и ACTIONS_LIST для расчета.
Поля CONDITIONS и ACTIONS состоят из вложенных узлов. Каждый узел указывает контроллер через CLASS_ID, передает ему параметры через DATA и может содержать дочерние узлы в CHILDREN. Для миграции или повторного создания правила можно взять структуру из сохраненного правила за основу.
|
Ключ |
Как заполнять |
|
|
Идентификатор контроллера условия или действия |
|
|
Параметры конкретного контроллера. Набор ключей зависит от |
|
|
Вложенные условия или ограничения действия. Если вложенных узлов нет, передайте пустой массив |
Идентификатор контроллера CLASS_ID
Идентификатор контроллера CLASS_ID определяет, какой контроллер обработает узел условия или действия. Для типовых сценариев можно использовать наиболее часто встречающиеся значения.
|
CLASS_ID |
Где используется |
Назначение |
|
|
|
Корневая группа дерева. Объединяет дочерние условия или действия |
|
|
|
Условие по сумме корзины или заказа |
|
|
|
Группа условий по позициям корзины: товар, раздел, свойства товара или позиции |
|
|
|
Условие по элементам инфоблока, то есть по товарам |
|
|
|
Условие по разделам инфоблока |
|
|
|
Условие по службе или профилю доставки |
|
|
|
Скидка, наценка или остановка скидок для позиций корзины |
|
|
Вложенные ограничения в |
Ограничивает действие только подходящими товарами, разделами или другими условиями внутри действия |
|
|
|
Скидка или наценка на доставку |
Если нужен другой тип условия или действия, сначала создайте похожее правило в административном разделе или мастере правил. Затем прочитайте правило через CSaleDiscount::GetByID() или CSaleDiscount::GetList() и используйте его структуру как основу для кода. В результате можно получить актуальные CLASS_ID, параметры DATA и вложенность CHILDREN для установленных модулей и версии продукта.
Параметры дерева
|
Параметр |
Где используется |
Возможные значения и правила заполнения |
|
|
Оператор сравнения в условии |
Базовый набор операторов:
Конкретный
|
|
|
Объединение вложенных условий. |
Возможные значения:
|
|
|
Проверка или инверсия результата группы. |
Возможные значения:
|
|
|
Поиск подходящих позиций в |
Возможные значения:
|
|
|
Тип действия. Возможные значения зависят от |
Для
Для
|
|
|
Единица значения действия. Возможные значения зависят от |
Для
Для
|
|
|
Число или идентификатор, с которым работает условие или действие. |
Регистр ключа важен:
|
|
|
Ограничение максимальной скидки в |
Возможные значения:
|
Примеры создания правил
1. Создать скидку от суммы заказа
Добавьте скидку 10% на корзину, если сумма заказа больше или равна 3000 в валюте сайта.
-
USER_GROUPSзадает группы пользователей, для которых правило активно, -
CONDITIONSописывает условие применения правила, -
ACTIONSописывает действие правила, -
USE_COUPONS = Nозначает, что правило применяется без купона, -
Unit = Percзадает процентную скидку, -
LAST_DISCOUNT = Nразрешает следующим правилам продолжить расчет.
$siteId = SITE_ID;
$userGroupIds = [2];
$discountId = \CSaleDiscount::Add([
'LID' => $siteId,
'NAME' => 'Скидка 10% от суммы заказа',
'ACTIVE' => 'Y',
'SORT' => 100,
'PRIORITY' => 1,
'LAST_DISCOUNT' => 'N',
'LAST_LEVEL_DISCOUNT' => 'N',
'CURRENCY' => \Bitrix\Sale\Internals\SiteCurrencyTable::getSiteCurrency($siteId),
'USER_GROUPS' => $userGroupIds,
'USE_COUPONS' => 'N',
// Общее условие применения правила
'CONDITIONS' => [
'CLASS_ID' => 'CondGroup',
'DATA' => [
'All' => 'AND',
'True' => 'True',
],
'CHILDREN' => [
[
'CLASS_ID' => 'CondBsktAmtGroup',
'DATA' => [
'logic' => 'EqGr',
'Value' => 3000,
'All' => 'AND',
],
'CHILDREN' => [],
],
],
],
// Действие, которое выполнится, если условия подошли
'ACTIONS' => [
'CLASS_ID' => 'CondGroup',
'DATA' => [
'All' => 'AND',
],
'CHILDREN' => [
[
'CLASS_ID' => 'ActSaleBsktGrp',
'DATA' => [
'Type' => 'Discount',
'Value' => 10,
'Unit' => 'Perc',
'Max' => 0,
'All' => 'AND',
'True' => 'True',
],
'CHILDREN' => [],
],
],
],
]);
if ((int)$discountId <= 0)
{
global $APPLICATION;
$exception = $APPLICATION->GetException();
$message = $exception ? $exception->GetString() : 'Не удалось создать правило';
throw new \RuntimeException($message);
}
2. Создать скидку на товар или раздел
Создайте скидку 15% на один товар и один раздел.
Для скидки на конкретные товары или разделы используйте условие CondBsktProductGroup. Внутри него можно указать товары через CondIBElement, а разделы через CondIBSection. Такое же ограничение нужно добавить в действие, чтобы скидка изменила только подходящие позиции корзины.
$siteId = SITE_ID;
$userGroupIds = [2];
$productId = 123;
$sectionId = 45;
$discountId = \CSaleDiscount::Add([
'LID' => $siteId,
'NAME' => 'Скидка 15% на товар и раздел',
'ACTIVE' => 'Y',
'SORT' => 100,
'PRIORITY' => 1,
'LAST_DISCOUNT' => 'N',
'LAST_LEVEL_DISCOUNT' => 'N',
'CURRENCY' => \Bitrix\Sale\Internals\SiteCurrencyTable::getSiteCurrency($siteId),
'USER_GROUPS' => $userGroupIds,
'USE_COUPONS' => 'N',
// Общее условие применения правила
'CONDITIONS' => [
'CLASS_ID' => 'CondGroup',
'DATA' => [
'All' => 'AND',
'True' => 'True',
],
'CHILDREN' => [
[
'CLASS_ID' => 'CondBsktProductGroup',
'DATA' => [
'Found' => 'Found',
'All' => 'OR',
],
'CHILDREN' => [
[
'CLASS_ID' => 'CondIBSection',
'DATA' => [
'logic' => 'Equal',
'value' => $sectionId,
],
],
[
'CLASS_ID' => 'CondIBElement',
'DATA' => [
'logic' => 'Equal',
'value' => [$productId],
],
],
],
],
],
],
// Действие, которое выполнится, если условия подошли
'ACTIONS' => [
'CLASS_ID' => 'CondGroup',
'DATA' => [
'All' => 'AND',
],
'CHILDREN' => [
[
'CLASS_ID' => 'ActSaleBsktGrp',
'DATA' => [
'Type' => 'Discount',
'Value' => 15,
'Unit' => 'Perc',
'Max' => 0,
'All' => 'OR',
'True' => 'True',
],
// Ограничение действия теми же товарами и разделами
// Без него скидка может примениться шире, чем условие правила
'CHILDREN' => [
[
'CLASS_ID' => 'ActSaleSubGrp',
'DATA' => [
'All' => 'OR',
'True' => 'True',
],
'CHILDREN' => [
[
'CLASS_ID' => 'CondIBSection',
'DATA' => [
'logic' => 'Equal',
'value' => $sectionId,
],
],
],
],
[
'CLASS_ID' => 'ActSaleSubGrp',
'DATA' => [
'All' => 'AND',
'True' => 'True',
],
'CHILDREN' => [
[
'CLASS_ID' => 'CondIBElement',
'DATA' => [
'logic' => 'Equal',
'value' => [$productId],
],
],
],
],
],
],
],
],
]);
if ((int)$discountId <= 0)
{
global $APPLICATION;
$exception = $APPLICATION->GetException();
$message = $exception ? $exception->GetString() : 'Не удалось создать правило';
throw new \RuntimeException($message);
}
3. Создать скидку на доставку
Создадим скидку 200 в валюте сайта на выбранную доставку, если сумма заказа больше или равна 5000.
Скидка на доставку использует действие ActSaleDelivery. В условиях правила укажите минимальную сумму заказа и выбранную службу доставки.
$siteId = SITE_ID;
$userGroupIds = [2];
$deliveryId = 3;
$discountId = \CSaleDiscount::Add([
'LID' => $siteId,
'NAME' => 'Скидка 200 на доставку от 5000',
'ACTIVE' => 'Y',
'SORT' => 100,
'PRIORITY' => 1,
'LAST_DISCOUNT' => 'N',
'LAST_LEVEL_DISCOUNT' => 'N',
'CURRENCY' => \Bitrix\Sale\Internals\SiteCurrencyTable::getSiteCurrency($siteId),
'USER_GROUPS' => $userGroupIds,
'USE_COUPONS' => 'N',
// Условие: сумма заказа и выбранная служба доставки
'CONDITIONS' => [
'CLASS_ID' => 'CondGroup',
'DATA' => [
'All' => 'AND',
'True' => 'True',
],
'CHILDREN' => [
[
'CLASS_ID' => 'CondBsktAmtGroup',
'DATA' => [
'logic' => 'EqGr',
'Value' => 5000,
'All' => 'AND',
],
'CHILDREN' => [],
],
[
'CLASS_ID' => 'CondSaleDelivery',
'DATA' => [
'logic' => 'Equal',
'value' => [$deliveryId],
],
],
],
],
// Действие: скидка на доставку
'ACTIONS' => [
'CLASS_ID' => 'CondGroup',
'DATA' => [
'All' => 'AND',
],
'CHILDREN' => [
[
'CLASS_ID' => 'ActSaleDelivery',
'DATA' => [
'Type' => 'Discount',
'Value' => 200,
'Unit' => 'Cur',
],
],
],
],
]);
if ((int)$discountId <= 0)
{
global $APPLICATION;
$exception = $APPLICATION->GetException();
$message = $exception ? $exception->GetString() : 'Не удалось создать правило';
throw new \RuntimeException($message);
}
Обновить правило
Для изменения существующего правила используйте метод CSaleDiscount::Update(). Передайте идентификатор правила и поля, которые нужно изменить.
$updatedDiscountId = \CSaleDiscount::Update(
$discountId,
[
'ACTIVE' => 'N',
'SORT' => 200,
]
);
if ((int)$updatedDiscountId <= 0)
{
global $APPLICATION;
$exception = $APPLICATION->GetException();
$message = $exception ? $exception->GetString() : 'Не удалось обновить правило';
throw new \RuntimeException($message);
}
Удалить правило
Для удаления правила используйте метод Bitrix\Sale\Internals\DiscountTable::delete(). Метод CSaleDiscount::Delete() устарел.
$deleteResult = \Bitrix\Sale\Internals\DiscountTable::delete($discountId);
if (!$deleteResult->isSuccess())
{
throw new \RuntimeException(implode('; ', $deleteResult->getErrorMessages()));
}
Работать с купонами
Класс DiscountCouponsManager хранит купоны в контексте текущего пользователя, менеджера или заказа. Перед расчетом выберите режим работы с купонами.
|
Режим |
Сценарий |
Что передать |
|
|
Покупатель вводит купон в публичной корзине. Купоны хранятся в пользовательском контексте |
|
|
|
Менеджер создает новый заказ для покупателя |
|
|
|
Менеджер или обработчик пересчитывает существующий заказ |
|
|
|
Внешний сценарий запускает расчет вне пользовательской корзины и заказа |
Параметры зависят от сценария внешнего расчета |
Для публичной корзины используйте режим клиента.
\Bitrix\Sale\DiscountCouponsManager::init(
\Bitrix\Sale\DiscountCouponsManager::MODE_CLIENT,
[
'userId' => $userId,
]
);
Если нужно начать расчет с пустого набора купонов, используйте метод reInit() с третьим параметром true. Это подходит для фонового сценария, теста или проверки условий. В публичной корзине не очищайте купоны автоматически: покупатель может потерять введенный код.
\Bitrix\Sale\DiscountCouponsManager::reInit(
\Bitrix\Sale\DiscountCouponsManager::MODE_CLIENT,
[
'userId' => $userId,
],
true
);
Создать купон для правила
Для правила, которое должно работать по купону, установите параметр USE_COUPONS = Y. Затем создайте купон методом \Bitrix\Sale\Internals\DiscountCouponTable::add.
$couponResult = \Bitrix\Sale\Internals\DiscountCouponTable::add([
'DISCOUNT_ID' => $discountId,
'COUPON' => 'SALE-10-ORDER',
'TYPE' => \Bitrix\Sale\Internals\DiscountCouponTable::TYPE_ONE_ORDER,
'MAX_USE' => 0,
'USER_ID' => 0,
]);
if (!$couponResult->isSuccess())
{
throw new \RuntimeException(implode('; ', $couponResult->getErrorMessages()));
}
Тип купона задает, как он расходуется:
-
TYPE_BASKET_ROW— купон, который расходуется при применении к позиции корзины, -
TYPE_ONE_ORDER— одноразовый купон на заказ, -
TYPE_MULTI_ORDER— многоразовый купон с учетом установленного лимитаMAX_USE.
Купон не применится до расчета через Discount::calculate() или Order::doFinalAction(true).
Добавить купон в расчет
Метод add() добавляет код купона в менеджер и возвращает true, если купон найден и не заблокирован для расчета.
$couponCode = 'SALE-10-PRODUCT';
if (!\Bitrix\Sale\DiscountCouponsManager::add($couponCode))
{
$errors = \Bitrix\Sale\DiscountCouponsManager::getErrors();
$message = $errors ? implode('; ', $errors) : 'Не удалось добавить купон в расчет';
throw new \RuntimeException($message);
}
Метод добавляет купон в менеджер, но не применяет скидку. Купон начнет влиять на цену только после расчета корзины или заказа.
Получить состояние купонов
После расчета получите купоны методом get().
get($extMode = true, $filter = [], $show = false, $final = false)
-
$extMode— определяет формат результата.-
true— возвращает расширенные данные купонов. В расширенных данных могут быть пояснения причины: купон неактивен, истек срок действия, не подходит пользователь или лимит использования. -
false— только коды купонов.
-
-
$filter— фильтрует список купонов. Значение[]возвращает все купоны текущего расчета. Можно передать массив условий по полям купона, например['COUPON' => $couponCode],['STATUS' => DiscountCouponsManager::STATUS_APPLYED]или условие с отрицанием['!STATUS' => DiscountCouponsManager::STATUS_NOT_FOUND]. -
$show— определяет режим получения.-
true— возвращает данные для показа пользователю, включая служебные статусы и подсказки проверки. -
false— возвращает купоны для применения в расчете.
-
-
$final— завершает проверку статусов.-
true— переводит купоны, которые не применились, в финальный статусSTATUS_NOT_APPLYED. -
false— оставляет текущие статусы.
-
$coupons = \Bitrix\Sale\DiscountCouponsManager::get(
true,
[],
true,
true
);
foreach ($coupons as $coupon)
{
echo $coupon['COUPON'] . ': ' . $coupon['STATUS'] . "\n";
}
Основные статусы купонов:
|
Статус |
Что означает |
|
|
Купон добавлен в расчет, но итоговое применение еще не подтверждено |
|
|
Купон применился к товару или доставке |
|
|
Купон существует, но не подошел под условия расчета |
|
|
Купон не найден |
|
|
Купон заблокирован для применения в текущем контексте |
Удалить или очистить купоны
Чтобы удалить один купон из текущего расчета, вызовите delete().
if (!\Bitrix\Sale\DiscountCouponsManager::delete($couponCode))
{
throw new \RuntimeException('Купон не найден в текущем расчете');
}
Чтобы очистить все купоны, вызовите clear(true). Параметр true очищает также хранилище купонов текущего режима.
\Bitrix\Sale\DiscountCouponsManager::clear(true);
После удаления или очистки купонов пересчитайте корзину или заказ. Иначе старые суммы могут остаться в уже рассчитанном объекте.
Получить подарки по правилу
Правило может предлагать подарок без изменения цены. Для получения подарков используйте Bitrix\Sale\Discount\Gift\Manager.
$giftManager = \Bitrix\Sale\Discount\Gift\Manager::getInstance()
->setUserId($userId)
;
$collections = $giftManager->getCollectionsByBasket($basket);
foreach ($collections as $collection)
{
foreach ($collection as $gift)
{
echo $gift->getProductId() . "\n";
}
}
Компоненты подарков используют менеджер для отображения доступных товаров. Покупатель может получить эти товары по правилам работы с корзиной. Доступность подарка зависит от условий правила и состава корзины.
Получить подсказку по правилу
Если у правила настроена подсказка, ее можно получить через Bitrix\Sale\Discount\Prediction\Manager. Подсказка показывает покупателю, какое действие необходимо, чтобы получить скидку или подарок.
$predictionText = \Bitrix\Sale\Discount\Prediction\Manager::getInstance()
->setUserId($userId)
->getFirstPredictionTextByProduct(
$basket,
[
'ID' => $productId,
'MODULE' => 'catalog',
'PRODUCT_PROVIDER_CLASS' => \Bitrix\Catalog\Product\Basket::getDefaultProviderName(),
'QUANTITY' => 1,
]
)
;
if ($predictionText !== null)
{
echo $predictionText;
}
Подсказка не применяет правило и не меняет корзину. Она только читает настроенные в правиле условия и текст.
Прочитать настройки правил
Для проверки правил используйте CSaleDiscount::GetList(). Чтение настроек помогает проверить активность, период, сайт, признак купонов и порядок применения.
$discountRows = [];
$discountIterator = \CSaleDiscount::GetList(
[
'PRIORITY' => 'DESC',
'SORT' => 'ASC',
'ID' => 'ASC',
],
[
'LID' => SITE_ID,
'ACTIVE' => 'Y',
],
false,
false,
[
'ID',
'LID',
'NAME',
'ACTIVE',
'ACTIVE_FROM',
'ACTIVE_TO',
'USE_COUPONS',
'SORT',
'PRIORITY',
'LAST_DISCOUNT',
'LAST_LEVEL_DISCOUNT',
]
);
while ($discount = $discountIterator->Fetch())
{
$discountRows[] = $discount;
}
Метод CSaleDiscount::GetList() показывает настройки правила, но не отвечает на вопрос, применится ли правило к конкретной корзине. Для этого нужен расчет через Discount::calculate() или Order::doFinalAction(true).
Рассчитать и применить правила
Правила применяются, когда условия подходят под корзину или заказ и действие может изменить расчет.
Рассчитать корзину
Чтобы рассчитать корзину без заказа, используйте метод \Bitrix\Sale\Discount::buildFromBasket(). Для предварительного расчета корзины создайте контекст покупателя, постройте объект скидок по корзине и выполните расчет.
// Обновляем цены и купоны перед расчетом
$basket->refreshData(['PRICE', 'COUPONS']);
// Создаем контекст покупателя, которому принадлежит корзина
$context = new \Bitrix\Sale\Discount\Context\Fuser($basket->getFUserId(true));
// Строим объект скидок по корзине без заказа
$discount = \Bitrix\Sale\Discount::buildFromBasket(
$basket,
$context
);
if ($discount === null)
{
throw new \RuntimeException('Корзина пуста');
}
// Запускаем расчет правил для корзины
$calculateResult = $discount->calculate();
if (!$calculateResult->isSuccess())
{
throw new \RuntimeException(implode('; ', $calculateResult->getErrorMessages()));
}
// Получаем расширенный результат применения правил
$applyResult = $discount->getApplyResult(true);
В DISCOUNT_LIST результата будут правила, которые участвовали в расчете. Поле APPLY покажет, применилось правило или нет.
Если корзина уже привязана к заказу, не используйте buildFromBasket(). Для такой корзины рассчитывайте заказ через $order->doFinalAction(true).
Рассчитать корзину с купоном
Расчет подходит для показа покупателю, применился ли купон и правила.
-
getApplyResult(true)— возвращает расширенный результат для отображения. Подробнее в разделе Прочитать результат расчета. -
getShowPrices()— возвращает цены для отображения в публичных компонентах, включая рассчитанные цены позиций корзины.
// Инициализируем менеджер купонов в режиме публичной корзины
\Bitrix\Sale\DiscountCouponsManager::init(
\Bitrix\Sale\DiscountCouponsManager::MODE_CLIENT,
[
'userId' => $userId,
]
);
// Добавляем купон в текущий расчет
if (!\Bitrix\Sale\DiscountCouponsManager::add($couponCode))
{
throw new \RuntimeException('Не удалось добавить купон в расчет');
}
// Обновляем цены и купоны перед расчетом
$basket->refreshData(['PRICE', 'COUPONS']);
// Создаем контекст покупателя, которому принадлежит корзина
$context = new \Bitrix\Sale\Discount\Context\Fuser($basket->getFUserId(true));
// Строим объект скидок по корзине без заказа
$discount = \Bitrix\Sale\Discount::buildFromBasket(
$basket,
$context
);
if ($discount === null)
{
throw new \RuntimeException('Корзина пуста');
}
// Запускаем расчет правил с учетом добавленного купона
$calculateResult = $discount->calculate();
if (!$calculateResult->isSuccess())
{
throw new \RuntimeException(implode('; ', $calculateResult->getErrorMessages()));
}
// Получаем результат применения и цены для показа покупателю
$applyResult = $discount->getApplyResult(true);
$showPrices = $discount->getShowPrices();
Применить правила к заказу
В заказе правила применяются через Order::doFinalAction(true). Метод получает объект скидок заказа, запускает расчет, применяет результат к корзине, доставке и налогам. После расчета сохраните заказ через Order::save().
Заказ без купона
Если правило не требует купон, достаточно пересчитать и сохранить заказ.
$calculateResult = $order->doFinalAction(true);
if (!$calculateResult->isSuccess())
{
throw new \RuntimeException(implode('; ', $calculateResult->getErrorMessages()));
}
$saveResult = $order->save();
if (!$saveResult->isSuccess())
{
throw new \RuntimeException(implode('; ', $saveResult->getErrorMessages()));
}
Заказ с новым купоном
Если покупатель вводит купон при оформлении заказа, инициализируйте менеджер купонов в режиме клиента, добавьте купон и затем пересчитайте заказ.
\Bitrix\Sale\DiscountCouponsManager::init(
\Bitrix\Sale\DiscountCouponsManager::MODE_CLIENT,
[
'userId' => $order->getUserId(),
]
);
if (!\Bitrix\Sale\DiscountCouponsManager::add($couponCode))
{
throw new \RuntimeException('Не удалось добавить купон в расчет');
}
$calculateResult = $order->doFinalAction(true);
if (!$calculateResult->isSuccess())
{
throw new \RuntimeException(implode('; ', $calculateResult->getErrorMessages()));
}
$saveResult = $order->save();
if (!$saveResult->isSuccess())
{
throw new \RuntimeException(implode('; ', $saveResult->getErrorMessages()));
}
После сохранения заказ хранит итоговую сумму, скидки на позиции корзины, скидки на доставку и примененные купоны. Сохраняйте заказ целиком через Order::save(), а не отдельные строки скидок.
Существующий заказ
Для редактирования существующего заказа используйте режим MODE_ORDER. Он загружает купоны, уже связанные с заказом, и позволяет добавить новые купоны в контекст редактирования.
\Bitrix\Sale\DiscountCouponsManager::init(
\Bitrix\Sale\DiscountCouponsManager::MODE_ORDER,
[
'userId' => $order->getUserId(),
'orderId' => $order->getId(),
]
);
if (!\Bitrix\Sale\DiscountCouponsManager::add($couponCode))
{
throw new \RuntimeException('Не удалось добавить купон в расчет');
}
$calculateResult = $order->doFinalAction(true);
if (!$calculateResult->isSuccess())
{
throw new \RuntimeException(implode('; ', $calculateResult->getErrorMessages()));
}
$saveResult = $order->save();
if (!$saveResult->isSuccess())
{
throw new \RuntimeException(implode('; ', $saveResult->getErrorMessages()));
}
Если при редактировании заказа нужно удалить старые купоны, очистите менеджер купонов и затем пересчитайте заказ.
Рассчитать правила для доставки
Правило может менять стоимость доставки. Для такого сценария в заказе должна быть отгрузка со службой доставки и рассчитанной базовой стоимостью. Затем Order::doFinalAction(true) применит правила к заказу и доставке.
$shipmentCollection = $order->getShipmentCollection();
$deliveryResult = $shipmentCollection->calculateDelivery();
if (!$deliveryResult->isSuccess())
{
throw new \RuntimeException(implode('; ', $deliveryResult->getErrorMessages()));
}
$calculateResult = $order->doFinalAction(true);
if (!$calculateResult->isSuccess())
{
throw new \RuntimeException(implode('; ', $calculateResult->getErrorMessages()));
}
$applyResult = $order->getDiscount()->getApplyResult(false);
$deliveryDiscounts = [];
foreach ($applyResult['APPLY_BLOCKS'] ?? [] as $applyBlock)
{
foreach ($applyBlock['ORDER'] ?? [] as $discount)
{
foreach ($discount['RESULT']['DELIVERY'] ?? [] as $deliveryResult)
{
$deliveryDiscounts[] = $deliveryResult;
}
}
}
Правило не изменит доставку, если у отгрузки установлена пользовательская стоимость доставки или если доставка не подходит под условия правила.
Прочитать результат расчета
Пока заказ находится в памяти, результат расчета доступен через объект скидок заказа.
$discount = $order->getDiscount();
$applyResult = $discount->getApplyResult(false);
Результат getApplyResult(false) содержит блоки:
|
Ключ |
Что хранит |
|
|
Список правил, которые участвовали в расчете |
|
|
Купоны, связанные с расчетом |
|
|
Блоки применения правил к товарам, доставке и заказу |
|
|
Расширенное описание правил |
В DISCOUNT_LIST и COUPON_LIST есть поле APPLY. Значение:
-
Y— правило или купон повлияли на расчет, -
N— объект участвовал в проверке, но не изменил итог.
Внутри APPLY_BLOCKS результат группируется по объектам:
-
BASKET— результат для конкретных позиций корзины, -
ORDER— результат правила на уровне заказа, включая действия по корзине и доставке, -
BASKET_ROUND— результат округления позиций корзины.
Метод getApplyResult(true) возвращает расширенный результат для отображения и убирает APPLY_BLOCKS. В нем примененные скидки по позициям находятся в RESULT['BASKET'].
$extendedApplyResult = $discount->getApplyResult(true);
$showPrices = $discount->getShowPrices();
Для вывода цены позиции используйте данные объекта корзины или метод getShowPrices(). Не пересчитывайте скидку повторно по проценту из названия правила.
Сохраненный результат заказа
После сохранения заказа можно загрузить результат из хранилища.
$result = \Bitrix\Sale\OrderDiscount::loadResultFromDb($orderId);
if (!$result->isSuccess())
{
throw new \RuntimeException(implode('; ', $result->getErrorMessages()));
}
$discountData = $result->getData();
Метод возвращает объект Bitrix\Sale\Result. В данных есть примененные блоки, список скидок, список купонов и служебные данные сохраненного расчета.
Для низкоуровневого чтения примененных правил используйте ORM-таблицу OrderRulesTable и метод getList().
$ruleRows = \Bitrix\Sale\Internals\OrderRulesTable::getList([
'select' => [
'ID',
'MODULE_ID',
'ORDER_DISCOUNT_ID',
'DISCOUNT_NAME' => 'ORDER_DISCOUNT.NAME',
'ENTITY_TYPE',
'ENTITY_ID',
'COUPON_ID',
'APPLY',
],
'filter' => [
'=ORDER_ID' => $orderId,
],
'order' => [
'ID' => 'ASC',
],
])->fetchAll();
Поле ENTITY_TYPE показывает, к чему относится результат: к позиции корзины или доставке. Для чтения названия и исходных данных сохраненного правила используйте связь с OrderDiscountTable.
Проверить, почему правило не применилось
Если правило или купон не применились, проверяйте расчет в том же контексте, в котором покупатель оформляет заказ. Если изменить пользователя, сайт, корзину, доставку или тип цены, результат может измениться.
-
Проверьте, что купон добавлен в менеджер и имеет корректный статус.
-
Запустите расчет корзины или заказа.
-
Получите купоны через
DiscountCouponsManager::get(true, [], true, true). -
Получите примененные правила через
$order->getDiscount()->getApplyResult(true)илиOrderDiscount::loadResultFromDb($orderId). -
Проверьте товарные данные, которые участвуют в условиях правила: товар, цену, валюту, количество, доступный тип цены, пользователя и сайт.
-
Проверьте доставку, если правило должно менять стоимость доставки.
-
Проверьте порядок правил,
LAST_DISCOUNT,LAST_LEVEL_DISCOUNTи режим применения скидок.
Пример проверки работы купона:
$coupons = \Bitrix\Sale\DiscountCouponsManager::get(
true,
[],
true,
true
);
foreach ($coupons as $coupon)
{
if ($coupon['STATUS'] === \Bitrix\Sale\DiscountCouponsManager::STATUS_APPLYED)
{
continue;
}
$reason = '';
if (!empty($coupon['CHECK_CODE_TEXT']) && is_array($coupon['CHECK_CODE_TEXT']))
{
$reason = implode('; ', $coupon['CHECK_CODE_TEXT']);
}
echo $coupon['COUPON'] . ': ' . ($reason ?: 'Купон не применился') . "\n";
}
Частые причины
-
Купон не найден, неактивен или вышел за период активности.
-
Правило скидки неактивно или не подходит по периоду действия.
-
Купон привязан к другому пользователю или исчерпал лимит использования.
-
Условия правила не совпали с товарами, суммой заказа, доставкой или сайтом.
-
Правило требует купон, но в расчете нет подходящего купона.
-
Правило не требует купон, но его остановило предыдущее правило.
-
Позиция корзины имеет пользовательскую цену или уже закрыта для следующих скидок.
-
Правило доставки проверяется до выбора или расчета службы доставки.
-
Корзина рассчитана без актуальных цен и купонов.
-
Заказ изменили после расчета, но не вызвали
doFinalAction(true).
Связь с торговым каталогом
Модуль sale применяет правила в контексте корзины или заказа. Исходные товарные данные приходят из модуля catalog: товары, торговые предложения, цены, типы цен, валюты и остатки.
Если товарная цена должна учитывать права пользователя, диапазонные цены и купоны на уровне каталога, используйте API каталога. Подробнее в разделе Торговый каталог.
Для корректного расчета правил работы с корзиной проверьте:
-
что товар добавлен в корзину с правильным
PRODUCT_ID,MODULE, ценой и валютой, -
цена товара доступна текущему пользователю и сайту,
-
базовая цена товара заполнена корректно, если включена настройка
get_discount_percent_from_base_price, -
количество товара в корзине соответствует условиям правила,
-
купон создан в том модуле, который участвует в расчете,
-
корзина обновлена через
refreshData(['PRICE', 'COUPONS'])перед предварительным расчетом.
Как округление влияет на итог
Округление цен настраивается в торговом каталоге и влияет на цену товара, которую получает корзина. Если товарная цена уже округлена по правилам каталога, правила работы с корзиной рассчитываются от этой цены. После применения скидок заказ хранит итоговые суммы с учетом рассчитанных цен корзины, скидок, доставки и налогов.
Не дублируйте округление в коде расчета правила. Если нужно проверить правило округления цены, используйте API каталога. Подробнее в статье Доступность, цены и подписка.