Цена — единственные данные обмена, ошибку в которых замечают в тот же день. Пропавшее описание товара может висеть месяц, лишний пробел в артикуле не заметит никто, а цена на копейку ниже закупочной становится известна бухгалтерии к обеду.
При этом ломается она чаще остального. Причина в том, что цена — не одно число, а связка из четырёх сущностей: тип цены, валюта, налог и правило округления. Каждая из них сопоставляется между 1С и сайтом отдельно, и любая может отвалиться независимо от других — тихо, без ошибок в журнале.
Разберём все четыре: где сопоставляются, как ломаются и как проверить, что цена, которую видит покупатель, — та же, что в учётной системе.
Что такое тип цены
Тип цены — это именованный набор цен для всех товаров каталога: розница, опт, закупка, цена для партнёров. Один товар имеет столько цен, сколько типов заведено, и каждая живёт своей жизнью.
Связь между типами цен 1С и Битрикса устроена ровно так же, как связь товаров и свойств, разобранная в статье про маппинг полей из 1С: по полю XML_ID. В нём хранится GUID типа цены из 1С, и обмен ищет соответствие только по нему. Числовой ID Битрикса в обмене не участвует.
<Предложение>
<Ид>f4e2...#9a71...</Ид>
<Цены>
<Цена>
<ИдТипаЦены>b2d4c7a1-...</ИдТипаЦены>
<ЦенаЗаЕдиницу>4990.00</ЦенаЗаЕдиницу>
<Валюта>RUB</Валюта>
<Единица>шт</Единица>
<Коэффициент>1</Коэффициент>
</Цена>
</Цены>
<Количество>17</Количество>
</Предложение>Главная ловушка: молчаливое игнорирование
Вот поведение, которое стоит запомнить до того, как оно случится на вашем проекте.
Если ИдТипаЦены не совпал ни с одним XML_ID на сайте, обмен отбрасывает цену и продолжает работу. Не падает, не пишет предупреждение, не помечает товар. В журнале — ничего. Обмен завершается со статусом success, потому что с его точки зрения ошибки не было: пришли данные, которые сайту не нужны.
Со стороны бизнеса это выглядит так: «обмен зелёный, а цены старые». Разработчик идёт искать поломку в обмене, а обмена там нет — есть несопоставленный справочник.
Сопоставление типов цен проверяется до первого боевого обмена, а не после жалобы. Особенно после любого изменения на стороне 1С: новый тип цены, переименование, перенос базы в другую информационную базу — всё это меняет GUID. Тип цены с прежним названием, но новым GUID для обмена не существует.
Как проверить сопоставление
Сначала смотрим, какие GUID реально приезжают:
cd /home/site/www/upload/1c_catalog/
# Уникальные типы цен в выгрузке
grep -o "<ИдТипаЦены>[^<]*</ИдТипаЦены>" offers.xml \
| sort -uЗатем — что заведено на сайте:
<?php
use Bitrix\Catalog\GroupTable;
use Bitrix\Main\Loader;
Loader::includeModule('catalog');
$rows = GroupTable::getList([
'select' => ['ID', 'NAME', 'NAME_LANG', 'XML_ID', 'BASE'],
'order' => ['ID' => 'ASC'],
])->fetchAll();
foreach ($rows as $row) {
printf(
"%3d %-20s base=%s xml_id=%s\n",
$row['ID'],
$row['NAME'],
$row['BASE'],
$row['XML_ID'] ?: '— НЕ СОПОСТАВЛЕН —'
);
}Два списка должны сойтись. Каждый GUID из выгрузки обязан найтись в колонке xml_id — иначе эти цены на сайт не попадают.
Базовая цена в Битриксе одна. Тип цены с флагом BASE = Y участвует в расчёте скидок и в большинстве типовых сценариев. Если 1С выгружает несколько типов, а базовым на сайте отмечен не тот, витрина будет показывать не ту цену, хотя в базе лежат все — и это ещё одна поломка, не оставляющая следов в журнале.
Валюты
Битрикс хранит цену в той валюте, в которой она приехала, и пересчитывает при показе, если валюта вывода другая. Отсюда два обязательных условия для мультивалютного каталога.
Валюта заведена в модуле валют. Код из выгрузки (RUB, USD, EUR) должен существовать в справочнике валют сайта.
Для валюты есть актуальный курс. Пересчёт делается по курсу на текущую дату. Курса нет — пересчёта нет, и результат зависит от того, какой курс окажется последним доступным.
<?php
use Bitrix\Currency\CurrencyTable;
use Bitrix\Main\Loader;
Loader::includeModule('currency');
$rows = CurrencyTable::getList([
'select' => ['CURRENCY', 'BASE', 'AMOUNT', 'DATE_UPDATE'],
])->fetchAll();
foreach ($rows as $row) {
printf(
"%s base=%s обновлено: %s\n",
$row['CURRENCY'],
$row['BASE'],
$row['DATE_UPDATE'] ? $row['DATE_UPDATE']->format('d.m.Y') : 'никогда'
);
}Строка «обновлено: никогда» у валюты, в которой приезжают цены, — это уже инцидент, даже если жалоб пока нет. Автоматическая загрузка курсов в Битриксе делается агентом, а агенты, как разобрано в статье про расписание обмена, без cron ночью не выполняются. Классическая связка: курс не обновился, потому что не было посетителей, а цены пересчитались по прошлогоднему.
НДС
Здесь ломается тоньше всего, потому что налог описывается двумя независимыми фактами, и рассинхронизация между ними даёт правдоподобно выглядящий, но неверный результат.
Первый факт — ставка. Приезжает в товаре:
<Товар>
<Ид>f4e2...</Ид>
<СтавкиНалогов>
<СтавкаНалога>
<Наименование>НДС</Наименование>
<Ставка>20</Ставка>
</СтавкаНалога>
</СтавкиНалогов>
</Товар>Второй — включён ли налог в переданное число. Цена 4990 может означать и «4990 с НДС», и «4990 плюс НДС сверху». Разница — 998 рублей, и никакой алгоритм не выведет её из самого числа.
На стороне Битрикса этим двум фактам соответствуют ставка НДС товара и флаг «цена включает НДС». Три сценария поломки:
| Что в 1С | Что на сайте | Результат |
|---|---|---|
| Цена с НДС | Флаг «включает НДС» снят | НДС начислится повторно, цена вырастет на 20% |
| Цена без НДС | Флаг «включает НДС» стоит | Налог потеряется, цена занижена |
| Ставка не сопоставлена | — | Товар считается без НДС, чек уйдёт с неверной ставкой |
Третий случай опаснее первых двух: на витрине всё выглядит правильно, а расхождение всплывает в фискальных документах — то есть там, где ошибка стоит дороже всего.
Проверяйте НДС на товаре с некруглой ценой. На цене 1000 рублей ошибка в ставке даёт 1200 — заметно. На цене 4990 — 5988, и число выглядит достаточно правдоподобно, чтобы пройти беглую проверку. Для сверки берите товар с ценой вроде 4990,50 и считайте вручную.
Округление
Расхождение «на сайте 4990, в 1С 4990,17» — самая частая претензия к обмену и почти всегда ложная тревога. В базу цена приезжает с полной точностью. Округление применяется при показе, по правилам, которые настраиваются отдельно для каждого типа цены в модуле торгового каталога.
Отсюда порядок разбирательства, который экономит час:
<?php
use Bitrix\Catalog\PriceTable;
use Bitrix\Main\Loader;
Loader::includeModule('catalog');
// Что реально лежит в базе для товара — без округления и пересчётов
$rows = PriceTable::getList([
'select' => ['PRODUCT_ID', 'CATALOG_GROUP_ID', 'PRICE', 'CURRENCY'],
'filter' => ['=PRODUCT_ID' => 4821],
])->fetchAll();
print_r($rows);Совпало с 1С — обмен работает, вопрос к настройкам округления или к валюте вывода. Не совпало — вот теперь имеет смысл смотреть обмен.
Второй источник копеечных расхождений — конвертация валют. Цена в долларах, пересчитанная в рубли и округлённая, при обратном пересчёте не даст исходное число. Это свойство арифметики, а не ошибка: для мультивалютных каталогов сверять нужно цену в валюте хранения.
Отладка: сверка цены за три шага
Порядок тот же, что в общем разборе отладки обмена — от источника к витрине.
# 1. Что прислала 1С для конкретного товара
grep -A 20 "f4e2c1a9" /home/site/www/upload/1c_catalog/offers.xml<?php
// 2. Что лежит в базе (код выше) — сравниваем с XML3. Что показывает витрина — сравниваем с базой.
Разошлось между 1 и 2 → обмен, сопоставление типа цены.
Разошлось между 2 и 3 → округление, валюта, кэш или скидка.Третий пункт стоит развернуть. Расхождение между базой и витриной чаще всего означает не цену, а кэш каталога: данные обновились, а компонент отдаёт сохранённый результат. Как устроены сроки жизни кэша каталога и что сбрасывать после обмена, разобрано в статье про политику кэширования каталога. Отдельный сорт той же проблемы — залипание цен при обновлении через CPrice, ему посвящён разбор конкретного бага.
Итоги
Тип цены сопоставляется по XML_ID и молча игнорируется, если не сопоставлен. Это главный источник проблем с ценами, и он не оставляет следов в журнале. Проверять — до обмена, а не после.
Любое изменение в 1С меняет GUID. Новая информационная база, новый тип цены, миграция — сопоставление нужно перепроверить. Совпадение названий ничего не значит.
Валюта без актуального курса пересчитывается непредсказуемо. Проверьте дату последнего обновления курсов — и заодно то, что агент обновления вообще выполняется.
НДС — это два независимых факта. Ставка и признак «включён в цену». Ошибка в любом из них даёт правдоподобное число.
Расхождение в копейках — почти всегда округление при показе. Сверяйте значение в базе, а не то, что нарисовано в карточке.
И наблюдение, ради которого стоит завести привычку. Заведите на проекте один тестовый товар с некруглой ценой в каждом типе цен и сверяйте его после каждого изменения в настройках обмена — минута работы. Практически все описанные здесь поломки на нём видны сразу, тогда как на реальном ассортименте они прячутся среди десятков тысяч позиций, пока кто-нибудь не купит товар по неправильной цене.
Частые вопросы
Почему цены из 1С не обновляются на сайте?
Три причины в порядке частоты. Тип цены не сопоставлен: ИдТипаЦены из offers.xml не совпал ни с одним XML_ID типов цен на сайте, и данные отброшены без записи в журнал. Обмен вообще не дошёл до offers.xml — видно по логу веб-сервера. Цены обновились в базе, но витрина отдаёт кэш каталога, который никто не сбрасывал после обмена.
Как сопоставить тип цены из 1С с типом цены в Битриксе?
Найти GUID типа цены в offers.xml (тег ИдТипаЦены или блок ТипыЦен) и записать его в поле XML_ID нужного типа цены на сайте — в справочнике типов цен модуля торгового каталога. После этого обмен найдёт соответствие сам. Числовой ID Битрикса при этом не используется вообще: связь держится только на GUID.
Цена на сайте отличается от 1С на копейку. Где ошибка?
Как правило, нигде: в базу цена приехала точно такой, какой её отправила 1С, а расхождение возникает при показе. На это работают два механизма — правила округления цен, настраиваемые отдельно для каждого типа цены, и пересчёт по курсу, если валюта цены отличается от валюты вывода. Сверять нужно значение в базе, а не то, что нарисовано в карточке товара.
Как передаётся НДС при обмене с 1С?
Ставка налога приезжает в товаре блоком СтавкиНалогов, а признак того, включён ли налог в переданную цену, — атрибутом самой цены. На стороне Битрикса товару соответствуют ставка НДС и флаг «цена включает НДС». Рассинхронизация этих двух настроек и есть источник классической ошибки, когда НДС оказывается посчитан дважды или потерян целиком.
Что делать, если 1С выгружает цены в валюте, отличной от валюты сайта?
Убедиться, что валюта из выгрузки заведена в модуле валют и для неё есть актуальный курс. Битрикс хранит цену в той валюте, в которой она приехала, и пересчитывает её при показе по курсу на текущую дату. Без загруженного курса пересчёт даст либо ноль, либо цену по курсу годичной давности — и то и другое произойдёт молча.