Товар продаётся коробками по двенадцать штук. В карточке — 120 рублей. Покупатель нажимает «в корзину», получает двенадцать штук и сумму 1440. Менеджер говорит, что коробка стоит 1200, и просит «убрать наценку».
Наценки нет. Есть три числа — цена, единица измерения и кратность, — которые приехали из 1С по разным маршрутам и не сошлись. Разбираться в этой связке приходится редко, зато каждый раз в режиме «клиенты платят не ту сумму».
Что такое кратность и единица измерения
Кратность — это шаг, которым покупатель меняет количество товара в корзине: при кратности 12 товар добавляется по двенадцать единиц, и кнопки «плюс» и «минус» двигают количество на двенадцать. Единица измерения — это то, в чём товар считается: штуки, метры, килограммы, литры. В Битриксе она хранится ссылкой на справочник, где у каждой единицы есть код по классификатору ОКЕИ: 796 — штука, 006 — метр, 166 — килограмм.
Два поля отвечают на два разных вопроса — «какими порциями продаём» и «в чём считаем», — и путаница между ними даёт большинство ошибок в этой области.
Эти вопросы независимы. Товар может измеряться в штуках и продаваться коробками по двенадцать: единица — штука, кратность — 12.
| Товар | Единица | Кратность | Что видит покупатель |
|---|---|---|---|
| Смеситель | шт | 1 | Кнопки меняют количество по 1 |
| Плитка | м² | 1,44 | Количество кратно площади одной коробки |
| Саморезы | шт | 100 | Добавляется упаковками по 100 |
| Кабель | м | 1 | Любое число метров |
Цена всегда хранится за базовую единицу. Это главное, что нужно запомнить: кратность не меняет цену, она меняет количество. Товар с ценой 120 за штуку и кратностью 12 даёт в корзине 12 штук за 1440 — и это правильное поведение Битрикса, а не ошибка. Если бизнес считает, что коробка стоит 1200, значит, в 1С цена задана за упаковку, и чинить надо выгрузку.
Как это приезжает из 1С
Здесь и кроется корень проблемы: значения приезжают двумя независимыми путями, и Битрикс не сверяет их между собой.
Базовая единица — в товаре, в import.xml:
<Товар>
<Ид>f4e2c1a9-...</Ид>
<Наименование>Саморез универсальный 3.5×25</Наименование>
<БазоваяЕдиница Код="796" НаименованиеПолное="Штука">шт</БазоваяЕдиница>
</Товар>Единица и коэффициент цены — в предложении, в offers.xml:
<Предложение>
<Ид>f4e2c1a9-...</Ид>
<Цены>
<Цена>
<ИдТипаЦены>b2d4...</ИдТипаЦены>
<ЦенаЗаЕдиницу>1.20</ЦенаЗаЕдиницу>
<Валюта>RUB</Валюта>
<Единица>шт</Единица>
<Коэффициент>100</Коэффициент>
</Цена>
</Цены>
<Количество>4800</Количество>
</Предложение>Прочитаем эту выгрузку буквально: саморез стоит 1,20 рубля за штуку, продаётся упаковками по 100, на складе 4800 штук. Всё согласовано.
А теперь типичная поломка: в 1С менеджер поставил цену 120 — за упаковку, «так удобнее». Коэффициент остался 100. Битрикс честно посчитает 100 × 120 = 12 000 рублей за упаковку самореза.
Битрикс не проверяет согласованность цены и коэффициента, и не может её проверить. Из чисел «цена 120, коэффициент 100» невозможно вывести, что имелось в виду. Обе трактовки арифметически корректны, разница в сто раз, и обнаруживается она только тогда, когда кто-то посмотрит на итог в корзине. Никакой настройки, которая бы это ловила, не существует — контроль возможен только на стороне 1С или отдельной проверкой на сайте.
Что лежит в базе
Проверка занимает один запрос через D7-ORM:
<?php
use Bitrix\Catalog\ProductTable;
use Bitrix\Catalog\MeasureTable;
use Bitrix\Main\Loader;
Loader::includeModule('catalog');
// Справочник единиц измерения: что вообще заведено
$measures = MeasureTable::getList([
'select' => ['ID', 'CODE', 'MEASURE_TITLE', 'SYMBOL_RUS', 'IS_DEFAULT'],
])->fetchAll();
foreach ($measures as $m) {
printf(
"%3d ОКЕИ %-5s %-16s %s\n",
$m['ID'],
$m['CODE'],
$m['MEASURE_TITLE'],
$m['IS_DEFAULT'] === 'Y' ? '(по умолчанию)' : ''
);
}<?php
// Товары с подозрительной кратностью — то, что стоит смотреть после каждого обмена
$rows = ProductTable::getList([
'select' => ['ID', 'QUANTITY', 'MEASURE', 'MEASURE_RATIO'],
'filter' => [
'LOGIC' => 'OR',
['=MEASURE_RATIO' => 0],
['=MEASURE_RATIO' => null],
['>MEASURE_RATIO' => 1000],
],
'limit' => 50,
])->fetchAll();
printf("Товаров с некорректной кратностью: %d\n", count($rows));Кратность может храниться отдельно от карточки товара. В Битриксе есть таблица коэффициентов, где кратность задаётся записью на товар, а поле в карточке товара показывает текущее значение. На разных версиях и при разных способах заполнения источник истины может отличаться, поэтому при расхождении между админкой и корзиной смотрите оба места, а не одно.
Три сценария, которые ломаются
Цена за упаковку вместо цены за единицу
Разобран выше. Симптом — сумма в корзине кратно больше ожидаемой, ровно во столько раз, какова кратность.
Проверка на конкретном товаре:
# Что приехало из 1С
grep -A 15 "f4e2c1a9" /home/site/www/upload/1c_catalog/offers.xmlСверяем ЦенаЗаЕдиницу с ценой в базе и Коэффициент с MEASURE_RATIO. Совпало — вопрос к тому, что имелось в виду в 1С. Не совпало — вопрос к сопоставлению типов цен, разобранному в статье про цены из 1С.
Дробная кратность
Плитка продаётся коробками по 1,44 м². Кратность дробная, и на этом ломаются округления: покупатель набирает 10 коробок, получает 14,4 м², а витрина показывает 14 — или 14,40, или 14,4000 в зависимости от настроек вывода.
Дробная кратность работает, но требует внимания к точности во всех трёх местах: в корзине, в заказе и в документах для 1С. Расхождение в сотых долях на большом заказе даёт разницу в деньгах, которую бухгалтерия заметит.
Кратность ноль
Появляется из выгрузок, где коэффициент не заполнен. Поведение корзины при нулевой кратности непредсказуемо: товар либо не добавляется вовсе, либо добавляется по одной штуке в обход настройки.
Это то, что стоит проверять регулярно — запрос выше находит такие товары за секунду. Заведите его в чек-лист после обмена, потому что появляются они внезапно: один товар в 1С завели без коэффициента, и он один ведёт себя не так, как все.
Как встроить проверку в обмен
Ручная сверка не масштабируется. Разумнее повесить контроль на завершение импорта — там, где положено делать всё тяжёлое, как разобрано в статье про производительность импорта:
<?php
use Bitrix\Catalog\ProductTable;
use Bitrix\Main\Loader;
/**
* Проверка после обмена: ищем товары, у которых кратность выглядит
* несогласованной с ценой. Пороги подбираются под свой ассортимент —
* универсальных здесь нет.
*/
function auditMeasureRatio(int $iblockId): array
{
Loader::includeModule('catalog');
$suspicious = [];
$rows = ProductTable::getList([
'select' => ['ID', 'MEASURE_RATIO'],
'filter' => [
'LOGIC' => 'OR',
['=MEASURE_RATIO' => 0],
['=MEASURE_RATIO' => null],
],
]);
while ($row = $rows->fetch()) {
$suspicious[] = (int)$row['ID'];
}
return $suspicious;
}Дальше список уходит письмом или в мессенджер. Смысл не в автоматическом исправлении — исправлять нужно в 1С, — а в том, чтобы узнать о проблеме раньше покупателя.
Что показывать покупателю
Отдельный вопрос, который решается не в базе, а в шаблоне. Покупатель не должен догадываться, почему количество прыгает по двенадцать.
Работающий минимум:
- подпись у поля количества: «упаковка 12 шт»;
- цена за единицу и цена за упаковку рядом, а не одна вместо другой;
- в корзине — и количество упаковок, и суммарное количество единиц.
Отсутствие этих подписей даёт стабильный поток обращений «почему я не могу купить одну штуку» и брошенные корзины у тех, кто не стал спрашивать.
Итоги
Единица измерения и кратность — разные поля, отвечающие на разные вопросы. «В чём считаем» и «какими порциями продаём». Их путают чаще всего.
Цена хранится за базовую единицу, всегда. Умножение на кратность — правильное поведение. Если бизнес ждёт другого, менять надо выгрузку в 1С.
Из 1С приезжают два независимых значения, и Битрикс не может проверить их согласованность: обе трактовки арифметически корректны.
Нулевая кратность — регулярно появляющийся мусор. Ищется одним запросом, проверять стоит после каждого обмена.
Дробная кратность работает, но требует контроля округлений во всех местах, где число превращается в деньги.
И наблюдение, которое экономит время в разговоре с бизнесом: жалоба «сайт считает неправильно» в этой области почти никогда не про сайт. Сайт считает ровно то, что ему передали. Разговор стоит начинать не с кода, а с вопроса «за что именно поставлена цена в 1С — за штуку или за упаковку», и в половине случаев на этом он и заканчивается.
Частые вопросы
Что такое кратность товара в Битриксе?
Это поле MEASURE_RATIO в карточке товара: шаг, которым покупатель меняет количество в корзине. Кратность 12 означает, что товар добавляется по 12 единиц и кнопки «плюс» и «минус» двигают количество на 12. Кратность не меняет цену — она остаётся ценой за базовую единицу измерения.
Чем кратность отличается от единицы измерения?
Единица измерения отвечает на вопрос «в чём считаем» — штуки, метры, килограммы, и хранится ссылкой на справочник с кодами ОКЕИ. Кратность отвечает на вопрос «какими порциями продаём». Товар может измеряться в штуках и продаваться коробками по 12: единица — штука, кратность — 12.
Почему цена в корзине не совпадает с ценой в карточке?
Чаще всего потому, что цена выгружается из 1С за упаковку, а Битрикс трактует её как цену за базовую единицу и умножает на кратность. В карточке видна цена за единицу, в корзине — умноженная на 12 или 24. Проверяется сверкой ЦеныЗаЕдиницу и Коэффициента в offers.xml с полями MEASURE_RATIO и ценой в базе.
Как единицы измерения приезжают из 1С?
Базовая единица товара идёт в import.xml и попадает в служебное свойство CML2_BASE_UNIT, а в блоке цены предложения отдельно приезжают Единица и Коэффициент. Это два независимых источника, и они не обязаны совпадать: в 1С товар может учитываться в штуках, а цена задаваться за упаковку. Битрикс не сверяет их между собой.
Что будет, если у товара кратность 0 или пустая?
Кратность 0 — это некорректное состояние, при котором поведение корзины непредсказуемо: товар может не добавляться вообще либо добавляться по одной единице в обход настройки. Пустое значение обычно трактуется как 1. Товары с нулевой кратностью стоит искать отдельным запросом после каждого обмена — они появляются из выгрузок, где коэффициент не заполнен.