Как правильно распределить скидку по строкам чека ККМ в 1С 7.7 и избежать расхождения в копейках?

Программист 1С v7.7 1C:Бухгалтерия Торговля и дистрибуция
← На главную

При автоматизации розничной торговли и обработке бонусных программ (например, списании баллов «Спасибо» или фиксированных денежных скидок) разработчики часто сталкиваются с проблемой: сумма скидки распределяется по строкам чека некорректно. Из-за округления цен за единицу товара возникает расхождение в 1–2 копейки. В результате фискальный регистратор или драйвер ККМ выдает ошибку вида «Сумма всех типов оплаты меньше итога чека».

Разберем основные причины возникновения этой проблемы, проанализируем ошибки в пользовательских алгоритмах и рассмотрим правильные способы решения задачи в 1С 7.7.

Причины возникновения бесконечных циклов и расхождений

Выясним, почему стандартные попытки подгона копеек «в лоб» через циклы приводят к зависанию программы или ошибкам:

  1. Некорректное сравнение дробных чисел: При сравнении значений с разной разрядностью или без явного округления условие равенства может никогда не выполниться. Например, выражение вида Цена1 * Количество <> Сумма - Скидка при использовании чисел с плавающей точкой дает погрешность в микродолях, вызывая бесконечный цикл Пока ... Цикл.
  2. Отсутствие изменения переменных внутри цикла: Если внутри условия цикла переменные, участвующие в проверке, не пересчитываются, цикл превращается в зацикливание.
  3. Особенности алгоритмов драйверов ККМ: Фискальный регистратор пересчитывает стоимость строки как Окр(Цена, 2) * Количество. Если скидка распределена от общей суммы, то итоговая цена за штуку со скидкой может содержать дробные копейки, которые отбрасываются при округлении, создавая дисбаланс с итоговой суммой документа.

Решение 1. Использование типовой процедуры пропорционального распределения

В типовых конфигурациях (например, в «1С:Бухгалтерии 7.7») уже предусмотрена встроенная функция для пропорционального распределения сумм по табличным частям или таблицам значений — глРаспределитьПропорционально.

Рассмотрим по шагам, как применить этот механизм:

  1. Выгружаем табличную часть документа во временную таблицу значений.
  2. Добавляем новую числовую колонку для хранения рассчитанной скидки.
  3. Вызываем процедуру глРаспределитьПропорционально, передавая общую сумму скидки, таблицу значений, имя колонки-базы (по которой распределяем) и имя целевой колонки.

Посмотрим на пример реализации кода:


// Создаем или выгружаем таблицу значений с товарами
ТЗ = СоздатьОбъект("ТаблицаЗначений");
Док.ВыгрузитьТабличнуюЧасть(ТЗ, "Номенклатура,Количество,Цена,Сумма");

// Добавляем колонку для распределенной скидки
ТЗ.НоваяКолонка("СуммаСкидкиПоТовару", "Число", 15, 2);

// Распределяем общую сумму скидки пропорционально сумме строк
глРаспределитьПропорционально(СуммаСкидки, ТЗ, "Сумма", "СуммаСкидкиПоТовару");

Преимущество метода заключается в том, что процедура автоматически рассчитывает пропорции и корректно относит возникающий копеечный остаток округления на одну из строк, гарантируя равенство итоговой суммы частей исходному значению.

Решение 2. Корректировка алгоритма расчета «от суммы» и относительный занос остатка

Если требуется написать собственный алгоритм распределения, важно рассчитывать скидки от суммы позиций, а не пересчитывать каждый раз цену за единицу внутри цикла. Разберем правильный алгоритм распределения с заносом остатка:

  1. Вычисляем общую сумму товаров, на которые разрешено распространять скидку (исключаем номенклатуру без скидок).
  2. Обходим строки таблицы и рассчитываем скидку для каждой позиции пропорционально ее доле в общей сумме чека.
  3. Округляем значение скидки по каждой строке до 2 знаков с помощью функции Окр().
  4. Суммируем полученные скидки по строкам и находим разницу с первоначально заданной общей скидкой.
  5. Остаток (обычно 1–2 копейки) прибавляем к скидке последней строки чека или позиции с наибольшей стоимостью.

Проанализируем пример кода с ручной обработкой остатка:


Перем ОбщаяСуммаЧека, РаспределеноСкидки, НераспределенныйОстаток;

ОбщаяСуммаЧека = ТЗ.Итог("Сумма");
РаспределеноСкидки = 0;

ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл
    Если ОбщаяСуммаЧека > 0 Тогда
        // Расчет скидки на текущую строку
        ТЗ.Скидка = Окр((ВсегоБонусов * ТЗ.Сумма) / ОбщаяСуммаЧека, 2);
    Иначе
        ТЗ.Скидка = 0;
    КонецЕсли;
    
    РаспределеноСкидки = РаспределеноСкидки + ТЗ.Скидка;
КонецЦикла;

// Устраняем погрешность округления
НераспределенныйОстаток = ВсегоБонусов - РаспределеноСкидки;

Если (НераспределенныйОстаток <> 0) И (ТЗ.КоличествоСтрок() > 0) Тогда
    // Относим остаток на последнюю строку
    ТЗ.ПолучитьСтрокуПоНомеру(ТЗ.КоличествоСтрок());
    ТЗ.Скидка = ТЗ.Скидка + НераспределенныйОстаток;
КонецЕсли;

Важный момент: Никогда не используйте условие равенства Пока Число1 <> Число2 Цикл над результатами деления и умножения с плавающей точкой без предварительного применения функции Окр() с фиксированной точностью. Это убережет вашу программу от бесконечных циклов и зависаний кассового модуля.

← На главную