При автоматизации розничной торговли и обработке бонусных программ (например, списании баллов «Спасибо» или фиксированных денежных скидок) разработчики часто сталкиваются с проблемой: сумма скидки распределяется по строкам чека некорректно. Из-за округления цен за единицу товара возникает расхождение в 1–2 копейки. В результате фискальный регистратор или драйвер ККМ выдает ошибку вида «Сумма всех типов оплаты меньше итога чека».
Разберем основные причины возникновения этой проблемы, проанализируем ошибки в пользовательских алгоритмах и рассмотрим правильные способы решения задачи в 1С 7.7.
Выясним, почему стандартные попытки подгона копеек «в лоб» через циклы приводят к зависанию программы или ошибкам:
Цена1 * Количество <> Сумма - Скидка при использовании чисел с плавающей точкой дает погрешность в микродолях, вызывая бесконечный цикл Пока ... Цикл.Окр(Цена, 2) * Количество. Если скидка распределена от общей суммы, то итоговая цена за штуку со скидкой может содержать дробные копейки, которые отбрасываются при округлении, создавая дисбаланс с итоговой суммой документа.В типовых конфигурациях (например, в «1С:Бухгалтерии 7.7») уже предусмотрена встроенная функция для пропорционального распределения сумм по табличным частям или таблицам значений — глРаспределитьПропорционально.
Рассмотрим по шагам, как применить этот механизм:
глРаспределитьПропорционально, передавая общую сумму скидки, таблицу значений, имя колонки-базы (по которой распределяем) и имя целевой колонки.Посмотрим на пример реализации кода:
// Создаем или выгружаем таблицу значений с товарами
ТЗ = СоздатьОбъект("ТаблицаЗначений");
Док.ВыгрузитьТабличнуюЧасть(ТЗ, "Номенклатура,Количество,Цена,Сумма");
// Добавляем колонку для распределенной скидки
ТЗ.НоваяКолонка("СуммаСкидкиПоТовару", "Число", 15, 2);
// Распределяем общую сумму скидки пропорционально сумме строк
глРаспределитьПропорционально(СуммаСкидки, ТЗ, "Сумма", "СуммаСкидкиПоТовару");
Преимущество метода заключается в том, что процедура автоматически рассчитывает пропорции и корректно относит возникающий копеечный остаток округления на одну из строк, гарантируя равенство итоговой суммы частей исходному значению.
Если требуется написать собственный алгоритм распределения, важно рассчитывать скидки от суммы позиций, а не пересчитывать каждый раз цену за единицу внутри цикла. Разберем правильный алгоритм распределения с заносом остатка:
Окр().Проанализируем пример кода с ручной обработкой остатка:
Перем ОбщаяСуммаЧека, РаспределеноСкидки, НераспределенныйОстаток;
ОбщаяСуммаЧека = ТЗ.Итог("Сумма");
РаспределеноСкидки = 0;
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл
Если ОбщаяСуммаЧека > 0 Тогда
// Расчет скидки на текущую строку
ТЗ.Скидка = Окр((ВсегоБонусов * ТЗ.Сумма) / ОбщаяСуммаЧека, 2);
Иначе
ТЗ.Скидка = 0;
КонецЕсли;
РаспределеноСкидки = РаспределеноСкидки + ТЗ.Скидка;
КонецЦикла;
// Устраняем погрешность округления
НераспределенныйОстаток = ВсегоБонусов - РаспределеноСкидки;
Если (НераспределенныйОстаток <> 0) И (ТЗ.КоличествоСтрок() > 0) Тогда
// Относим остаток на последнюю строку
ТЗ.ПолучитьСтрокуПоНомеру(ТЗ.КоличествоСтрок());
ТЗ.Скидка = ТЗ.Скидка + НераспределенныйОстаток;
КонецЕсли;
Важный момент: Никогда не используйте условие равенства Пока Число1 <> Число2 Цикл над результатами деления и умножения с плавающей точкой без предварительного применения функции Окр() с фиксированной точностью. Это убережет вашу программу от бесконечных циклов и зависаний кассового модуля.