При автоматизации розничной торговли одной из самых неприятных проблем являются ошибки при печати чеков, которые возникают внезапно, например, только при применении скидок. Ошибка «4Dh. Вносимая безналичной оплатой сумма больше суммы чека» на устройствах Штрих-М — это классический пример расхождения математических округлений в учетной системе (1С) и внутренней логике контрольно-кассовой техники (ККТ).
В этой статье мы подробно разберем, почему возникает эта ситуация, рассмотрим настройки драйвера и приведем конкретные примеры программного кода, которые помогут навсегда забыть об этой проблеме. Давайте приступим к совместному решению этой задачи.
Разберем природу проблемы. В протоколе работы ККТ Штрих-М существует строгое разделение типов оплаты. Для наличного расчета (Тип оплаты 1) касса допускает внесение суммы, превышающей итог чека — в этом случае просто рассчитывается сдача. Однако для безналичного расчета (Тип оплаты 2, карта) протокол требует строгого соответствия: сумма оплаты должна быть «копейка в копейку» равна сумме всех позиций чека.
Когда в 1С применяется ручная или автоматическая скидка, возникают дробные числа. Например, товар стоит 152 рубля, скидка составляет 7%. Математический итог — 141,36 рублей. Но если 1С передает в ККТ цену за единицу и количество, а ККТ внутри себя пересчитывает общую сумму строки, может возникнуть разница в 0.01 руб. из-за правил округления. Для кассы это означает, что клиент «переплачивает» по безналу, что запрещено протоколом.
Прежде чем приступать к правке кода, проанализируем настройки самого фискального регистратора. Часто проблему можно решить без программирования.
Если в этом поле установлено значение 0, ККТ сама суммирует позиции и сравнивает итог с переданной оплатой. Рекомендуется переключить этот параметр в значение 1. В этом режиме ККТ будет брать итоговую сумму из данных, переданных 1С, что минимизирует риск возникновения ошибки 4Dh при расхождениях в копейку.
Также проверим настройку «Работа со скидками» в параметрах устройства. Если ККТ настроена на автоматическое распределение скидки, а 1С уже передает цену с учетом скидки, возникает конфликт. Убедимся, что внутренние механизмы скидок в драйвере отключены.
Если настройки драйвера не помогают, придется вмешаться в логику формирования данных, которые 1С отправляет на кассу. Рассмотрим подробнее процедуру ПолучитьXMLПакетДляФискализацияЧека в общем модуле МенеджерОборудованияВызовСервера.
Проанализируем первый вариант решения, предложенный сообществом. Его суть заключается в том, что если у позиции есть скидка (дробная часть), мы принудительно устанавливаем количество в значение «1», а в качестве цены передаем итоговую сумму строки. Это исключает пересчет «цена * количество» внутри кассы.
// Фрагмент модификации в общем модуле
Если ТекущаяПозиция.Свойство("ФискальнаяСтрока") Тогда
// Вычисляем наличие дробной части
ЗначениеСкидки = ТекущаяПозиция.Количество * ТекущаяПозиция.Цена - Цел(ТекущаяПозиция.Количество * ТекущаяПозиция.Цена);
ЗаписьXML.ЗаписатьНачалоЭлемента("FiscalString");
ЗаписьXML.ЗаписатьАтрибут("Name" , XMLСтрока(ТекущаяПозиция.Наименование));
Если ЗначениеСкидки <> 0 Тогда
// Передаем как одну штуку с итоговой ценой
ЗаписьXML.ЗаписатьАтрибут("Quantity", XMLСтрока("1"));
ЗаписьXML.ЗаписатьАтрибут("Price" , XMLСтрока(ТекущаяПозиция.Сумма));
ЗаписьXML.ЗаписатьАтрибут("Amount" , XMLСтрока(ТекущаяПозиция.Сумма));
Иначе
// Обычная печать без корректировок
ЗаписьXML.ЗаписатьАтрибут("Quantity", XMLСтрока(ТекущаяПозиция.Количество));
ЗаписьXML.ЗаписатьАтрибут("Price" , XMLСтрока(ТекущаяПозиция.Цена));
ЗаписьXML.ЗаписатьАтрибут("Amount" , XMLСтрока(ТекущаяПозиция.Сумма));
КонецЕсли;
ЗаписьXML.ЗаписатьАтрибут("Department", XMLСтрока(ТекущаяПозиция.НомерСекции));
ЗаписьXML.ЗаписатьАтрибут("Tax", "none"); // Указываем ставку налога (например, без НДС)
ЗаписьXML.ЗаписатьКонецЭлемента();
КонецЕсли;
Этот метод эффективен, но имеет недостаток: в чеке покупатель увидит количество «1» вместо реального (например, 3 шт.), что может вызвать вопросы у дотошных клиентов.
Рассмотрим более продвинутый способ. Если возникает отклонение в копейку, мы можем разделить товарную позицию на две строки. В первой строке будет Количество - 1 по исходной цене, а во второй строке — 1 единица по скорректированной цене, которая «закроет» разницу округления.
Выясним причину и применим следующий алгоритм в коде:
Отклонение = ТекущаяПозиция.Количество * ТекущаяПозиция.Цена - ТекущаяПозиция.Сумма;
Если Отклонение <> 0 Тогда
// Печатаем основную часть товара
КолОсновное = ТекущаяПозиция.Количество - 1;
СуммаОсновная = КолОсновное * ТекущаяПозиция.Цена;
ЗаписьXML.ЗаписатьНачалоЭлемента("FiscalString");
ЗаписьXML.ЗаписатьАтрибут("Name" , XMLСтрока(ТекущаяПозиция.Наименование));
ЗаписьXML.ЗаписатьАтрибут("Quantity", XMLСтрока(КолОсновное));
ЗаписьXML.ЗаписатьАтрибут("Price" , XMLСтрока(ТекущаяПозиция.Цена));
ЗаписьXML.ЗаписатьАтрибут("Amount" , XMLСтрока(СуммаОсновная));
ЗаписьXML.ЗаписатьАтрибут("Department", XMLСтрока(ТекущаяПозиция.НомерСекции));
ЗаписьXML.ЗаписатьАтрибут("Tax" , "none");
ЗаписьXML.ЗаписатьКонецЭлемента();
// Печатаем корректирующую строку (1 шт.)
ЗаписьXML.ЗаписатьНачалоЭлемента("FiscalString");
ЗаписьXML.ЗаписатьАтрибут("Name" , XMLСтрока(ТекущаяПозиция.Наименование));
ЗаписьXML.ЗаписатьАтрибут("Quantity", XMLСтрока(1));
ОстСуммы = ТекущаяПозиция.Сумма - СуммаОсновная;
ЗаписьXML.ЗаписатьАтрибут("Price" , XMLСтрока(ОстСуммы));
ЗаписьXML.ЗаписатьАтрибут("Amount" , XMLСтрока(ОстСуммы));
ЗаписьXML.ЗаписатьАтрибут("Department", XMLСтрока(ТекущаяПозиция.НомерСекции));
ЗаписьXML.ЗаписатьАтрибут("Tax" , "none");
ЗаписьXML.ЗаписатьКонецЭлемента();
Иначе
// Если отклонения нет, выводим стандартно
// ... стандартный код вывода FiscalString ...
КонецЕсли;
Посмотрим на настройки самой программы. В 1С:Розница есть механизмы, позволяющие минимизировать расхождения еще до передачи данных в драйвер:
Важный момент: После любого изменения программного кода или обновления библиотек AddIn.DRVFR.dll, обязательно выполняйте перерегистрацию компоненты в системе и перезагружайте рабочее место кассира (РМК).
Проанализировав ситуацию и применив один из предложенных методов, вы сможете обеспечить стабильную работу кассового узла и избежать задержек в обслуживании покупателей из-за технических сбоев оборудования.