В розничной торговле часто возникает потребность в «психологическом» ценообразовании. Одной из таких специфических задач является округление итоговой цены не до целого числа, а до определенных цифр в конце — например, чтобы цена всегда заканчивалась на 0, 3, 5 или 8 рублей и всегда округлялась в большую сторону. Рассмотрим подробнее, как реализовать такую логику в системе 1С, проанализируем типичные ошибки и разберем готовые примеры кода.
Прежде чем приступать к написанию кода, разберем логику, по которой должно происходить округление. Нам необходимо проанализировать последнюю цифру числа (остаток от деления на 10) и, исходя из этого, «подтянуть» цену вверх до ближайшего разрешенного значения из набора {0, 3, 5, 8}.
Алгоритм перехода должен выглядеть следующим образом:
Если мы пишем обработку или расширяем модуль ценообразования в конфигураторе, удобнее всего использовать многострочную конструкцию Если...КонецЕсли. Посмотрим на пример кода, который учитывает проверку остатка, чтобы избежать ошибки «лишней наценки» (например, когда 100 рублей превращаются в 103).
Проанализируем ситуацию: нам нужно сначала избавиться от копеек, так как работа идет с целыми рублями, а затем вычислить остаток.
// Предположим, переменная Цена уже содержит розничную цену с наценкой
Цена = Цел(Цена); // Округляем до целых рублей в меньшую сторону или используем Окр(Цена, 0, 1)
ОстатокОтДеления = Цена % 10;
Если ОстатокОтДеления = 0 Тогда
// Оставляем как есть, цена уже кратна 10
ИначеЕсли ОстатокОтДеления <= 3 Тогда
Цена = Цена - ОстатокОтДеления + 3;
ИначеЕсли ОстатокОтДеления <= 5 Тогда
Цена = Цена - ОстатокОтДеления + 5;
ИначеЕсли ОстатокОтДеления <= 8 Тогда
Цена = Цена - ОстатокОтДеления + 8;
Иначе
Цена = Цена - ОстатокОтДеления + 10;
КонецЕсли;
В данном примере мы используем оператор % (остаток от деления), который позволяет нам четко определить последнюю цифру числа. Вычитая этот остаток из основной цены, мы получаем число, кратное 10, а затем прибавляем нужную нам «красивую» цифру.
В типовых конфигурациях, таких как 1С:Управление торговлей 11 или 1С:Розница 2, часто используется «Конструктор формул». Особенность этого инструмента в том, что он не принимает многострочный код с Если. Здесь нам на помощь приходит тернарный оператор ?(). Разберем по шагам, как упаковать нашу логику в одну строку.
Рассмотрим пример формулы (где БазоваяЦена — это цена после применения процента наценки):
?( (Цел(БазоваяЦена) % 10) = 0, Цел(БазоваяЦена),
?( (Цел(БазоваяЦена) % 10) <= 3, Цел(БазоваяЦена/10)*10 + 3,
?( (Цел(БазоваяЦена) % 10) <= 5, Цел(БазоваяЦена/10)*10 + 5,
?( (Цел(БазоваяЦена) % 10) <= 8, Цел(БазоваяЦена/10)*10 + 8,
Цел(БазоваяЦена/10)*10 + 10))))
Важный нюанс: Обратите внимание на использование функции Цел(). Если ваша входная цена имеет копейки (например, 100.50), то без предварительного округления до целого остаток от деления на 10 будет равен 0.5, что может привести к неверному срабатыванию условий. Поэтому сначала мы приводим число к целому виду.
Выясним причину, по которой простые формулы часто работают некорректно. Основная проблема — это отсутствие проверки на «нулевой остаток». Рассмотрим ситуацию из практики пользователей форума:
Если использовать упрощенную формулу вида Цена = Цел(Цена/10)*10 + 3 (если остаток меньше 3), то число 100 преобразуется в 103. Это происходит потому, что 100 делится на 10 без остатка, остаток 0 удовлетворяет условию «меньше или равно 3», и программа прибавляет тройку. Чтобы этого избежать, всегда нужно выделять условие Остаток = 0 в отдельную ветку или использовать строгие неравенства.
Для разработчиков, стремящихся к гибкости кода, рассмотрим метод сопоставления. Представим, что завтра правила изменятся и собственник захочет округлять до цифр 2, 4, 7 и 9. Чтобы не переписывать гору условий Если, можно использовать массив целевых значений.
Функция ОкруглитьДоСпецЗначений(ВхЦена)
ТекЦена = Цел(ВхЦена);
Остаток = ТекЦена % 10;
Если Остаток = 0 Тогда
Возврат ТекЦена;
КонецЕсли;
// Массив целевых цифр
ЦелевыеЦифры = Новый Массив;
ЦелевыеЦифры.Добавить(3);
ЦелевыеЦифры.Добавить(5);
ЦелевыеЦифры.Добавить(8);
ЦелевыеЦифры.Добавить(10);
Для Каждого ЗначениеИзСписка Из ЦелевыеЦифры Цикл
Если Остаток <= ЗначениеИзСписка Тогда
Возврат (ТекЦена - Остаток) + ЗначениеИзСписка;
КонецЕсли;
КонецЦикла;
КонецФункции
Такой подход делает код более читаемым и легким в поддержке. Мы просто ищем первое число в массиве, которое больше или равно нашему остатку, и используем его для финального формирования цены.
Иногда требуется рассчитать такие цены прямо в отчете или при динамическом формировании прайс-листа. В языке запросов 1С нет функции Цел() или оператора % в чистом виде (в зависимости от СУБД), но есть конструкция ВЫБОР КОГДА... ТОГДА.
Посмотрим на пример логики в запросе:
ВЫБОР
КОГДА (Цена - ВЫРАЗИТЬ(Цена / 10 - 0.5 КАК ЧИСЛО(15, 0)) * 10) = 0
ТОГДА Цена
КОГДА (Цена - ВЫРАЗИТЬ(Цена / 10 - 0.5 КАК ЧИСЛО(15, 0)) * 10) <= 3
ТОГДА ВЫРАЗИТЬ(Цена / 10 - 0.5 КАК ЧИСЛО(15, 0)) * 10 + 3
// ... и так далее для 5, 8 и 10
ИНАЧЕ
ВЫРАЗИТЬ(Цена / 10 - 0.5 КАК ЧИСЛО(15, 0)) * 10 + 10
КОНЕЦ
Примечание: В запросах 1С конструкция ВЫРАЗИТЬ(Число - 0.5 КАК ЧИСЛО(15, 0)) часто используется как аналог функции «взять целую часть», однако стоит быть осторожным с отрицательными числами и особенностями округления разных СУБД.
Мы разобрали несколько способов реализации специфического округления цен до цифр 0, 3, 5, 8. Наиболее надежным методом для программного кода является использование остатка от деления на 10 с предварительным приведением цены к целому числу. Для автоматических правил в «Конструкторе цен» используйте вложенные тернарные операторы, не забывая про проверку нулевого остатка, чтобы избежать искусственного завышения цен на товары с «круглыми» закупочными стоимостями.