В розничной и оптовой торговле часто возникает задача: покупателю предоставляется фиксированная скидка на весь чек (например, 50 рублей на корзину из товаров стоимостью 200 и 150 рублей) или списываются бонусные баллы, а учетная система должна корректно распределить эту сумму построчно. Разберем, как реализовать такой расчет, какие типовые инструменты платформы 1С и БСП для этого существуют, а также выясним, как избежать ошибок при округлении копеек при взаимодействии с онлайн-кассами (ККТ) по 54-ФЗ.
Согласно форматам фискальных данных (ФФД 1.05, 1.1, 1.2), в фискальный накопитель и ОФД не передается отдельный реквизит общей скидки на чек. Кассовый аппарат принимает каждую товарную позицию с уже рассчитанной стоимостью с учетом скидки (тег 1043). При этом драйвер ККТ и фискальный накопитель строго контролируют два правила:
Если распределять скидку простым умножением на процент и округлять каждую строку по отдельности, возникает погрешность. Например, при сумме скидки 500 рублей на три одинаковых товара по 1000 рублей каждый товар получит скидку 166,67 рубля (итого 500,01 руб.) или 166,66 рубля (итого 499,98 руб.). Возникающая копейка приводит к блокировке пробития чека на ККМ из-за несовпадения итогов.
В типовых конфигурациях 1С (ERP, КА, УТ, Розница, УНФ) на базе «Библиотеки стандартных подсистем» (БСП) есть готовый и протестированный метод, исключающий проблемы с погрешностями округления. Рассмотрим процедуру РаспределитьСуммуПропорциональноКоэффициентам общего модуля ОбщегоНазначенияКлиентСервер или ОбщегоНазначения.
Разберем пример кода:
// Подготовим массив сумм позиций документа
МассивСумм = Новый Массив;
Для Каждого Стр Из ТаблицаТоваров Цикл
МассивСумм.Добавить(Стр.СуммаБезСкидки);
КонецЦикла;
// Вызываем типовое распределение БСП
МассивСкидок = ОбщегоНазначенияКлиентСервер.РаспределитьСуммуПропорциональноКоэффициентам(
СуммаСкидкиКРаспределению,
МассивСумм,
2 // Округление до 2 знаков (копеек)
);
// Записываем результат обратно в строки
Для Индекс = 0 По ТаблицаТоваров.Количество() - 1 Цикл
Стр = ТаблицаТоваров[Индекс];
Стр.СуммаСкидки = МассивСкидок[Индекс];
Стр.Сумма = Стр.СуммаБезСкидки - Стр.СуммаСкидки;
КонецЦикла;
Этот метод БСП внутри себя использует алгоритм наибольших остатков (метод Гамильтона), благодаря которому нераспределенные копейки аккуратно прибавляются к позициям с наибольшей дробной частью, а итоговая сумма всегда сходится с исходной до копейки.
Если конфигурация самописная или не использует БСП, применим алгоритм распределения в цикле с динамическим уменьшением оставшейся базы и скидки. В таком варианте на каждую следующую позицию рассчитывается доля от оставшейся нераспределенной суммы, а на последнюю строку автоматически падает точный остаток.
Посмотрим на пример реализации:
ОстатокСкидки = СуммаСкидкиКРаспределению;
ОстатокБазы = СуммаЧекаБезСкидки;
ВсегоСтрок = ТаблицаТоваров.Количество();
Для НомерСтроки = 1 По ВсегоСтрок Цикл
Стр = ТаблицаТоваров[НомерСтроки - 1];
Если НомерСтроки = ВсегоСтрок Тогда
// На последнюю строку относим оставшийся хвост
СкидкаПоСтроке = ОстатокСкидки;
Иначе
Если ОстатокБазы <> 0 Тогда
СкидкаПоСтроке = Окр(Стр.СуммаБезСкидки * (ОстатокСкидки / ОстатокБазы), 2);
Иначе
СкидкаПоСтроке = 0;
КонецЕсли;
КонецЕсли;
Стр.СуммаСкидки = СкидкаПоСтроке;
Стр.Сумма = Стр.СуммаБезСкидки - СкидкаПоСтроке;
// Уменьшаем остатки для следующей итерации
ОстатокСкидки = ОстатокСкидки - СкидкаПоСтроке;
ОстатокБазы = ОстатокБазы - Стр.СуммаБезСкидки;
КонецЦикла;
При реализации собственного распределения скидок важно учитывать специфику законодательства и торговых операций:
0.335 кг товара, то при уменьшении суммы строки цена за единицу Сумма / Количество может получить бесконечную дробную часть. Для чеков ККТ в таких ситуациях применяется алгоритм расщепления строки на две позиции с целочисленными копейками в цене.