При подготовке печатных форм ценников часто возникает задача отобразить две цены: регулярную цену продажи и акционную цену с учетом скидки (например, 2% или 5%). Нередко маркетинг требует округлять цену со скидкой строго вниз с определенным шагом — например, до 50 копеек (чтобы в копейках оставалось только 00 или 50), а в самом макете ценника рубли и копейки должны располагаться в разных ячейках с разным размером шрифта.
Разберем типичные ошибки при реализации такого алгоритма в платформе 1С:Предприятие 7.7 и рассмотрим правильные способы вычислений как на уровне математических формул, так и на уровне архитектуры печатной формы.
Попытки решить задачу «на скорую руку» часто приводят к нерабочему коду или некорректным ценам. Выясним причину основных проблем:
?(((Число(ПечРубли)...) % 1) * 100 >= 50, "50", "00") прямо в свойства ячеек макета приводит к ошибкам округления, путанице с типами данных и невозможности отладки. Логику расчетов необходимо выносить в программный модуль.глОкруглить. В типовых конфигурациях 1С 7.7 (в частности, в «Торговле и Склад 7.7») функция глОкруглить(Числ, ПорядокОкругления) жестко запрограммирована на округление вверх (к верхней границе диапазона). Использовать её для скидочной цены с округлением в меньшую сторону нельзя.РежимОкругления.Окр15как10 или РежимОкругления.Вниз отсутствуют в платформе 7.7. Встроенная функция Окр(Число, Разрядность, Способ) в «семерке» принимает третьим параметром только 0 или 1 и округляет только математически до степеней десяти (0.1, 0.01 и т.д.), но не умеет округлять до произвольного шага вроде 0.5.глФРМ в математических расчетах. Функция глФРМ() возвращает форматированную строку (с неразрывными пробелами и валютой). Передача её результата в операцию деления или функцию глОкруглить вызовет исключение «Операция деления не определена для строковых величин».Рассмотрим, как правильно выполнить округление вниз (отсечение) с произвольным шагом. Общая формула округления к меньшему выглядит так:
Результат = Цел(ИсходноеЧисло / Шаг) * Шаг
Для шага 50 копеек (0.5 рубля) деление на 0.5 равносильно умножению на 2. Соответственно, алгоритм выглядит следующим образом:
ЦенаСоСкидкой = Цел(ИсходнаяЦена * 2) / 2
Посмотрим на практические примеры:
Цел(100.49 * 2) / 2 = Цел(200.98) / 2 = 200 / 2 = 100.00 руб.Цел(100.50 * 2) / 2 = Цел(201.00) / 2 = 201 / 2 = 100.50 руб.Цел(100.99 * 2) / 2 = Цел(201.98) / 2 = 201 / 2 = 100.50 руб.Важное замечание о точности чисел с плавающей точкой: при расчете процента скидки (например, умножении на 0.98 при скидке 2%) в памяти может возникнуть погрешность представления вида 10.49999999999999. Если сразу применить функцию Цел(), число ошибочно округлится не до 50 копеек, а до нуля. Поэтому перед шаговым округлением промежуточную цену со скидкой обязательно нужно нормализовать до сотых с помощью стандартной функции Окр(..., 2).
// ВремЦена - исходная цена из документа (число)
ПромежуточнаяЦена = Окр(ВремЦена * 0.98, 2);
СкидкаЦена = Цел(ПромежуточнаяЦена * 2) / 2;
После получения точного числового значения скидочной цены разберем ее на две составляющие для раздельного вывода в печатную форму:
// Получаем целую часть (рубли)
СкидкаРубли = Цел(СкидкаЦена);
// Получаем копейки в виде двух символов ("00" или "50")
СкидкаКопейки = ?((СкидкаЦена - СкидкаРубли) > 0, "50", "00");
Проанализируем структуру типовой обработки печати ценников. В ней печать обычно выполняется через процедуру Печать(), которая в цикле вызывает процедуру формирования отдельной секции ДобавитьЦенник().
Если мы создадим локальные переменные внутри Печать(), они не будут видны в ячейках таблицы внутри ДобавитьЦенник(), что приведет к ошибке «Переменная не определена». Чтобы переменные были доступны секции макета, объявим их на уровне модуля через директиву Перем либо передадим параметрами.
Разберем рабочий вариант с объявлением переменных на уровне модуля:
// В самом начале модуля обработки (до процедур) объявляем переменные:
Перем ПечФирма;
Перем ПечРубли, ПечКопейки;
Перем СкидкаРубли, СкидкаКопейки; // Новые переменные для акционной цены
Теперь модифицируем цикл выборки строк в процедуре Печать():
Докум.ВыбратьСтроки();
Пока Докум.ПолучитьСтроку() = 1 Цикл
Если ДокумВид = "ПереоценкаРозница" Тогда
Если Докум.ЦенаСтарая = Докум.ЦенаНовая Тогда
Продолжить;
КонецЕсли;
КонецЕсли;
ВремЦена = Докум.ПолучитьАтрибут(ИдРознЦена);
// 1. Формируем основную цену (без скидки)
ПечРубли = Цел(ВремЦена);
ПечКопейки = Прав("00" + ((ВремЦена * 100) % 100), 2);
// 2. Рассчитываем цену со скидкой 2% с округлением вниз до 50 коп.
ЦенаСоСкидкой = Цел(Окр(ВремЦена * 0.98, 2) * 2) / 2;
СкидкаРубли = Цел(ЦенаСоСкидкой);
СкидкаКопейки = ?((ЦенаСоСкидкой - СкидкаРубли) > 0, "50", "00");
ПечРозн = глФРМ(ВремЦена);
ПечНаименованиеТМЦ = СокрЛП(Докум.Номенклатура.ПолнНаименование);
ДобавитьЦенник(Таб, Докум.Фирма, ПечНаименованиеТМЦ, Докум.Единица, ПечРозн, Столбик, Ряд, СокрЛП(Докум.Номенклатура.СтранаПроисхождения));
глОживить(1);
КонецЦикла;
Завершающий шаг — правильная привязка переменных к ячейкам макета ценника:
ПечРубли, в ячейке копеек — ПечКопейки.СкидкаРубли.СкидкаКопейки.Такой подход полностью устраняет ошибки вычислений, гарантирует корректное округление строго вниз с шагом в 50 копеек и сохраняет исходный код чистым и легко читаемым.