PHP
#bitrix#catalog#price#php#minimal#price-types

Получить минимальную цену с учётом нескольких типов цен

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

Как использовать

  1. Подключите модуль catalog, получите все типы цен (CCatalogGroup), для каждого проверьте доступность для пользователя и цену через CPrice::GetList и GetOptimalPrice.
  2. Верните минимальную цену среди доступных типов с полями PRICE, CURRENCY, PRICE_TYPE_ID.
  3. Для неавторизованного передавайте userId = 0; для расчёта с количеством передайте quantity.

В каталоге 1С-Битрикс у товара может быть несколько типов цен (розница, опт, спеццены для групп). В списках и карточках обычно показывают минимальную доступную цену для текущего пользователя с учётом скидок. Проблема: если брать только базовый тип цены или первую попавшуюся, отображается не лучшая цена; при этом типы цен привязаны к группам пользователей, и не все типы доступны гостю или другой группе. Симптомы: в каталоге завышенная цена, скидка не учитывается, для части пользователей цена не та. Ниже — функция, которая перебирает все типы цен, проверяет доступность по группам пользователя, учитывает GetOptimalPrice (скидки, персональные цены) и возвращает минимальную цену и тип.

Решение

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

use Bitrix\Main\Loader;

/**
 * Получить минимальную цену среди всех типов цен
 * @param int $productId ID товара
 * @param int $userId ID пользователя (для проверки доступности типов цен)
 * @param int $quantity Количество для расчёта
 * @return array|false ['PRICE' => float, 'CURRENCY' => string, 'PRICE_TYPE_ID' => int] или false
 */
function getMinimalPriceFromAllTypes($productId, $userId = 0, $quantity = 1) {
    if (!Loader::includeModule('catalog')) {
        return false;
    }
    
    $priceTypes = CCatalogGroup::GetList([], ['BASE' => 'N']);
    $allPriceTypes = [];
    while ($type = $priceTypes->Fetch()) {
        $allPriceTypes[] = $type['ID'];
    }
    
    $baseType = CCatalogGroup::GetBaseGroup();
    if ($baseType) {
        $allPriceTypes[] = $baseType['ID'];
    }
    
    $minimalPrice = null;
    $minimalPriceData = null;
    
    foreach ($allPriceTypes as $priceTypeId) {
        if ($userId > 0) {
            $userGroups = CUser::GetUserGroup($userId);
            $priceType = CCatalogGroup::GetByID($priceTypeId);
            if ($priceType && !empty($priceType['USER_GROUP'])) {
                $allowedGroups = explode(',', $priceType['USER_GROUP']);
                if (empty(array_intersect($userGroups, $allowedGroups))) {
                    continue;
                }
            }
        }
        
        $price = CPrice::GetList(
            [],
            [
                'PRODUCT_ID' => $productId,
                'CATALOG_GROUP_ID' => $priceTypeId
            ],
            false,
            false,
            ['PRICE', 'CURRENCY']
        )->Fetch();
        
        if ($price) {
            $priceValue = (float)$price['PRICE'];
            
            $optimalPrice = CCatalogProduct::GetOptimalPrice(
                $productId,
                $quantity,
                $userId,
                'N',
                [],
                false,
                false,
                [$priceTypeId]
            );
            
            if ($optimalPrice && isset($optimalPrice['PRICE']['PRICE'])) {
                $priceValue = (float)$optimalPrice['PRICE']['PRICE'];
            }
            
            if ($minimalPrice === null || $priceValue < $minimalPrice) {
                $minimalPrice = $priceValue;
                $minimalPriceData = [
                    'PRICE' => $priceValue,
                    'CURRENCY' => $price['CURRENCY'],
                    'PRICE_TYPE_ID' => $priceTypeId
                ];
            }
        }
    }
    
    return $minimalPriceData ?: false;
}

Пример использования:

$userId = $GLOBALS['USER']->GetID();
$minPrice = getMinimalPriceFromAllTypes(12345, $userId, 1);

if ($minPrice) {
    echo 'Минимальная цена: ' . $minPrice['PRICE'] . ' ' . $minPrice['CURRENCY'];
    echo 'Тип цены ID: ' . $minPrice['PRICE_TYPE_ID'];
}

$minPrice = getMinimalPriceFromAllTypes(12345, 0, 1); // неавторизованный

Доступность типов цен проверяется по группам пользователя (USER_GROUP у типа цены). Для гостя учитываются только типы без ограничения по группам или с пустым USER_GROUP. GetOptimalPrice учитывает скидки и персональные цены.

Проверка

  1. Товар с одним типом цены — вызовите функцию для товара с одной записью в b_catalog_price. Ожидаем массив с PRICE, CURRENCY, PRICE_TYPE_ID; цена должна совпадать с ценой в админке (с учётом скидки, если есть).

  2. Товар с несколькими типами цен — создайте два типа цен и записи в b_catalog_price с разными суммами. Результат должен содержать минимальную из цен среди типов, доступных текущему пользователю.

  3. Разные группы пользователя — для типа цены укажите ограничение по группе; залогиньтесь под пользователем из этой группы и без неё. В первом случае минимальная цена должна учитывать этот тип, во втором — нет (если других типов нет, вернётся false или цена другого типа).

  4. Диагностика: всегда false — проверьте, что модуль catalog подключён, у товара есть хотя бы одна запись в b_catalog_price и для пользователя доступен хотя бы один тип цены (группы и USER_GROUP типа).

Типичные ошибки

  • Учитывается только базовый тип — для каталога с несколькими типами цен нужно перебирать все типы и выбирать минимум; иначе оптовые/спеццены не отобразятся.
  • Не проверяется доступность типа для группы — тип цены может быть привязан к группам (USER_GROUP); без проверки группам показывается цена, которая им не предназначена. Используйте CUser::GetUserGroup и сравнение с USER_GROUP типа.
  • Скидки не учтены — базовая цена из CPrice::GetList без скидок. Для учёта скидок и персональных цен вызывайте CCatalogProduct::GetOptimalPrice с нужным типом цены.
  • Нет цен у товара — если у товара нет ни одной записи в b_catalog_price или ни один тип недоступен, функция вернёт false. Обрабатывайте этот случай в шаблоне (не показывать цену или «по запросу»).

Где применять

  • Prod / dev: списки товаров, карточка товара, фильтры по цене (минимальная цена для отображения и сортировки), API каталога.
  • Личный кабинет и публичная часть: передавайте текущего пользователя ($GLOBALS[‘USER’]->GetID()) или 0 для гостя.

Связанные сниппеты: Список цен товара, Родительский товар по артикулу, Изменение статуса заказа D7 и save.