Цена и торговые параметры товара в модуле "Каталог" хранятся отдельно
от самого элемента инфоблока — в собственных таблицах, с которыми
работают классы CCatalogProduct и CPrice.
Разберём, как читать и менять цену и остаток программно, не через
админку.
Почему цена — это не свойство элемента
Товар в 1С-Битрикс — это элемент инфоблока, а его торговые параметры
(цена, остаток на складе, вес, единица измерения) хранятся
в отдельных таблицах модуля catalog, привязанных к тому
же ID элемента. Поэтому смена цены не делается через
CIBlockElement::Update — для этого есть отдельные классы.
Чтение цены товара
<?php
use Bitrix\Main\Loader;
Loader::includeModule('catalog');
$priceResult = CPrice::GetList(
[],
['PRODUCT_ID' => $productId, 'CATALOG_GROUP_ID' => 1] // базовая группа цен
);
$price = $priceResult->Fetch();
echo $price['PRICE'] . ' ' . $price['CURRENCY'];
CATALOG_GROUP_ID — тип цены (базовая, оптовая,
розничная и так далее), настроенных в разделе
Магазин → Настройки → Типы
цен. У товара может быть несколько записей цены — по одной
на каждый настроенный тип.
Нужна помощь с Битрикс?
Установка новой цены
<?php
$existingPrice = CPrice::GetList(
[],
['PRODUCT_ID' => $productId, 'CATALOG_GROUP_ID' => 1]
)->Fetch();
if ($existingPrice) {
CPrice::Update($existingPrice['ID'], ['PRICE' => 1990]);
} else {
CPrice::Add([
'PRODUCT_ID' => $productId,
'CATALOG_GROUP_ID' => 1,
'PRICE' => 1990,
'CURRENCY' => 'RUB',
]);
}
Обязательная проверка на существующую запись перед добавлением —
CPrice::Add не заменяет значение автоматически,
а создаёт новую запись, из-за чего у товара может появиться
несколько цен одного типа, если проверку пропустить.
Остаток товара через CCatalogProduct
<?php
CCatalogProduct::Update($productId, [
'QUANTITY' => 42,
'QUANTITY_TRACE' => 'Y', // включён учёт остатков
]);
$product = CCatalogProduct::GetByID($productId);
echo $product['QUANTITY'];
CCatalogProduct отвечает за параметры, не связанные
с ценой напрямую: остаток на складе, признак товара/услуги,
и другие торговые настройки. Если учёт остатков включён
(QUANTITY_TRACE = 'Y'), товар с нулевым остатком
считается недоступным для заказа в зависимости от общих настроек
каталога.
Массовое изменение цен через агента
<?php
function updatePricesForCategory(int $sectionId, float $percent): string
{
Loader::includeModule('iblock');
Loader::includeModule('catalog');
$result = CIBlockElement::GetList(
[], ['SECTION_ID' => $sectionId, 'ACTIVE' => 'Y'], false, false, ['ID']
);
while ($el = $result->GetNext()) {
$price = CPrice::GetList([], ['PRODUCT_ID' => $el['ID'], 'CATALOG_GROUP_ID' => 1])->Fetch();
if ($price) {
$newPrice = round($price['PRICE'] * (1 + $percent / 100), 2);
CPrice::Update($price['ID'], ['PRICE' => $newPrice]);
}
}
return 'updatePricesForCategory(' . $sectionId . ', ' . $percent . ');';
}
Массовую переоценку удобно запускать агентом — функция возвращает саму себя строкой, что нужно только если планируется повторный запуск по расписанию; для разового пересчёта достаточно вызвать функцию напрямую в консоли или обработчике события.
Работа с валютой
Значение PRICE в CPrice хранится строго
в валюте, указанной в поле CURRENCY той же записи.
Конвертация между валютами при выводе цены на сайте выполняется
модулем currency по текущему курсу и не меняет само
хранимое значение — стоит избегать ручного пересчёта цены на лету
в обход штатного механизма валют, чтобы не рассинхронизировать
отображаемую и хранимую цену.
Частые ошибки
- CPrice::Add без проверки существующей записи. У товара появляется несколько цен одного типа вместо обновления существующей.
- Попытка изменить цену через CIBlockElement::Update. Цена не хранится в элементе инфоблока — метод её просто не увидит.
- Не подключён модуль catalog перед вызовом классов. Классы CPrice и CCatalogProduct недоступны без явного Loader::includeModule.
- Ручной пересчёт валюты в обход модуля currency. Хранимая и отображаемая цена расходятся при изменении курса.
Итог
Цена и остаток товара — отдельная сущность от элемента инфоблока,
за которую отвечают CPrice и
CCatalogProduct модуля "Каталог". Перед изменением цены
через CPrice::Add нужно проверить, нет ли уже записи
для этого товара и типа цены, иначе вместо обновления появится
дубликат.
