Перейти к содержимому

Путь цены - от типа цены до суммы в корзине

Разбираем, как из цен товара получается сумма в корзине: типы цен и группы, скидки каталога, правила корзины, купоны, округление и налог.

Механика

У одного товара обычно несколько цен разных типов. Розница, опт и цена для дилеров живут отдельными записями, и покупатель видит не все, а только доступные его группам.

Доступ к типу цены выдаётся группам покупателей. Незалогиненный посетитель видит то, что разрешено группе всех посетителей, а оптовик - свой тип цены после входа на сайт.

Витрина показывает те типы цен, которые перечислены в параметрах компонента. Даже доступный группе тип не появится на странице, если его код не задан в вызове каталога.

Скидки каталога применяются к цене товара до корзины. Они привязаны к товару, разделу или условию и дают ту самую перечёркнутую старую цену рядом с новой.

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

Купон - это не отдельная скидка, а условие правила корзины. Он включает подготовленное правило, поэтому «купон не сработал» почти всегда означает несовпадение условий этого правила.

Округление применяется к итоговой цене по настройкам каталога. Отсюда расхождения в копейках между ценой на витрине, суммой в корзине и суммой в документах бухгалтерии.

Налог считается либо сверху цены, либо считается включённым в неё. Настройка одна на магазин, и её меняют вместе с бухгалтерией, а не по просьбе маркетолога.

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

Шаги

  1. Посмотреть все цены товара и понять, какие типы вообще заведены на проекте.
  2. Проверить, каким группам покупателей доступен нужный тип цены сейчас.
  3. Убедиться, что код типа цены перечислен в параметрах компонента витрины.
  4. Посчитать итоговую цену покупателя штатным вызовом с его группами.
  5. Сравнить полученное число с ценой позиции в корзине и найти расхождение.

Код

Смотрим все цены товара:

\Bitrix\Main\Loader::includeModule('catalog');
$rows = \Bitrix\Catalog\PriceTable::getList(['filter' => ['=PRODUCT_ID' => $productId],
'select' => ['CATALOG_GROUP_ID', 'PRICE', 'CURRENCY', 'QUANTITY_FROM', 'QUANTITY_TO']])->fetchAll();
print_r($rows);
printf("типов цен у товара: %d\n", count($rows));
// цена может зависеть от количества: тогда у типа заданы границы диапазона
// у товара с предложениями эти записи принадлежат вариантам, а не ему самому

Диапазоны по количеству легко забыть при разборе. Цена за одну штуку и цена за десять могут отличаться, и покупатель видит разные числа при смене количества в карточке.

Смотрим, кому доступен тип цены:

$access = \Bitrix\Catalog\GroupAccessTable::getList([
'filter' => ['=CATALOG_GROUP_ID' => $priceTypeId], 'select' => ['GROUP_ID', 'ACCESS']]);
print_r($access->fetchAll());
printf("группы покупателя: %s\n", implode(',', $USER->GetUserGroupArray()));
// без доступа группе тип цены не участвует в расчёте вовсе
// право покупать и право видеть цену выдаются раздельно

Считаем итоговую цену покупателя:

$optimal = \CCatalogProduct::GetOptimalPrice($productId, 1, $USER->GetUserGroupArray(),
'N', [], SITE_ID, []);
printf("итог: %s, база: %s\n", $optimal['DISCOUNT_PRICE'], $optimal['PRICE']['PRICE']);
print_r(array_column($optimal['DISCOUNT_LIST'] ?? [], 'NAME')); // какие скидки сработали
printf("тип цены: %s\n", $optimal['PRICE']['CATALOG_GROUP_NAME'] ?? '');
// третий аргумент - группы покупателя: под администратором результат будет другим

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

Смотрим цены позиции в корзине:

$item = $basket->getItemById($basketItemId);
printf("цена=%s база=%s скидка=%s\n", $item->getPrice(), $item->getBasePrice(),
$item->getDiscountPrice());
printf("своя цена: %s\n", $item->isCustomPrice() ? 'да' : 'нет');
printf("количество: %s\n", $item->getQuantity());
// признак своей цены отключает пересчёт: платформа такую позицию не трогает

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

Проверяем правила корзины:

$discounts = \Bitrix\Sale\Discount::buildFromBasket($basket, new \Bitrix\Sale\Discount\Context\Fuser($fUserId));
$discounts->calculate();
print_r($discounts->getApplyResult(true)); // какие правила применились к корзине
// результат показывает и сработавшие правила, и позиции, к которым они применились

Смотрим налог и округление:

$product = \Bitrix\Catalog\ProductTable::getRow(['filter' => ['=ID' => $productId],
'select' => ['ID', 'VAT_ID', 'VAT_INCLUDED']]);
printf("ставка=%s налог в цене=%s\n", $product['VAT_ID'], $product['VAT_INCLUDED']);
print_r(\Bitrix\Catalog\RoundingTable::getList(['select' => ['*']])->fetchAll());
// правила округления задаются по типам цен и применяются к итогу расчёта

Признак включённого налога хранится у самого товара. Один и тот же магазин может держать позиции с налогом в цене и без него, и в документах это видно сразу.

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

Ограничения

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

Скидка каталога и правило корзины не всегда складываются. Порядок и совместимость задают настройками правил, и проверять их приходится на живых сценариях.

Своя цена позиции отключает пересчёт для этой строки. Акции, купоны и правила корзины на такую позицию больше не действуют до снятия признака.

Округление меняет итог, а не отдельные слагаемые. Сумма позиций и итог заказа могут отличаться на копейки, и бухгалтерия это заметит раньше вас.

Валюта показа и валюта хранения - разные вещи. Пересчёт по курсу выполняется при выводе, и на нём же появляются расхождения в копейках между витриной и заказом.

Расчёт цены зависит от групп покупателя, поэтому кэшировать его нужно осторожно. Ключ кэша обязан включать набор групп, иначе оптовик получит розничную цену из чужой готовой копии страницы.

Типичные проблемы

Оптовик видит розничную цену после входа.

Его группе не выдан доступ к оптовому типу цены либо код типа не указан в параметрах компонента. Проверяют и права группы на тип цены, и список типов в вызове каталога.

Скидка видна на витрине и пропадает в корзине.

На витрине сработала скидка каталога, а в корзине позиция получила признак своей цены. Позиции со своей ценой платформа больше не пересчитывает.

Купон не даёт обещанной скидки.

Купон включает правило корзины, а условия этого правила не выполняются для текущего состава. Разбирают именно условия правила, а не сам купон.

Сумма заказа отличается от суммы позиций на копейки.

Округление применяется к итогу и к позициям по разным правилам настройки каталога. Правила округления сверяют с требованиями бухгалтерии до запуска магазина.

Цена меняется при изменении количества в карточке.

У типа цены заданы диапазоны по количеству, и для десяти штук действует другая цена. Это штатное поведение, и его показывают покупателю явно.

После обмена цены на витрине не изменились.

Новая цена приехала в тип, который витрина не показывает, либо кэш каталога не сброшен. Сверяют тип цены обмена с типами в параметрах компонента.

Частые вопросы

Как узнать, какая цена покажется покупателю?

Штатным расчётом оптимальной цены с его группами: он повторяет логику витрины. Вызов возвращает и итог, и список применённых скидок.

Чем скидка каталога отличается от правила корзины?

Скидка каталога меняет цену товара до корзины, правило корзины работает уже с составом заказа. Второе умеет условия по сумме, доставке и наборам товаров.

Почему купон ничего не изменил?

Купон включает правило корзины, а его условия не выполнились для текущего состава. Смотреть нужно условия правила и его совместимость с другими скидками.

Где задаётся округление?

Правилами округления в настройках торгового каталога, отдельно по типам цен. Они применяются к итоговой цене и объясняют расхождения в копейках.

Что делать с ручной ценой менеджера?

Помнить, что позиция со своей ценой выпадает из всех автоматических пересчётов. Для акций такую позицию либо пересоздают, либо снимают признак своей цены.

Смежное

Первоисточник