В практике сопровождения розничной торговли на базе конфигурации 1С:Управление торговлей 10.3 иногда возникает редкая, но критическая ошибка: фискальный регистратор (например, ШТРИХ-М-ФР-К) печатает чек, в котором отсутствует одна или несколько позиций из табличной части документа. При этом 1С не выдает никаких сообщений об ошибках, чек считается успешно закрытым, но итоговая сумма на бумажной ленте и в Z-отчете оказывается меньше, чем в проведенном документе 1С. Разберем по шагам, почему возникает эта проблема, почему касса не аннулирует чек при расхождении сумм и как гарантированно устранить этот сбой.
Для понимания сути проблемы проанализируем механизм взаимодействия 1С с фискальным регистратором через драйвер торгового оборудования. Существует две основные группы причин: аппаратные сбои интерфейса передачи данных и логические особенности обработки команд.
В большинстве случаев потеря строки происходит по следующим причинам:
Sale) отправляются в порт одна за другой с высокой скоростью. Микроконтроллер кассового аппарата не успевает обрабатывать входящие данные одновременно с физическим протягиванием чековой ленты и печатью. Если в настройках виртуального или физического COM-порта не включено аппаратное управление потоком (RTS/CTS), буфер переполняется, и байты команды фиксации строки теряются.Но почему в таком случае чек не аннулируется? Проанализируем логику работы драйвера DrvFR.dll: фискальный регистратор отменяет чек только в том случае, если переданная сумма оплаты меньше накопленного внутри кассы подытога. Если в обработке обслуживания 1С сумма закрытия берется не из реквизита документа, а запрашивается как текущий подытог регистратора (метод GetSubtotal) либо команда закрытия вызывается без проверки сдачи, касса просто считает, что покупателю пробили меньше товаров, и успешно закрывает чек на фактически принятую сумму.
Поскольку ситуация повторяется редко, отловить ее стандартными средствами отладки тяжело. Начнем с включения детальных логов драйвера и фиксации данных на стороне 1С.
Включим журналирование драйвера ШТРИХ-М. Для этого перейдем в форму настройки торгового оборудования (или утилиту «Тест драйвера ФР»), откроем окно «Свойства» и в разделе «Дополнительно» включим ведение лог-файла. В лог будут построчно записываться все байты команд, ответы фискального накопителя и статусы ResultCode.
Параллельно настроим фиксацию отправки строк непосредственно в коде 1С, чтобы при возникновении расхождений сопоставить лог драйвера с данными документа:
// В модуле обработки обслуживания перед циклом печати чека
ЗаписьЖурналаРегистрации("ПечатьЧека", УровеньЖурналаРегистрации.Информация, ,
ДокументСсылка, "Начало печати чека по документу " + Строка(ДокументСсылка) + ", строк: " + Строка(ТаблицаТоваров.Количество()));
Для Каждого Стр Из ТаблицаТоваров Цикл
ЗаписьЖурналаРегистрации("ПечатьЧека", УровеньЖурналаРегистрации.Информация, ,
ДокументСсылка, "Отправка строки " + Строка(Стр.НомерСтроки) + ": " + Стр.Номенклатура.Наименование + ", Сумма: " + Строка(Стр.Сумма));
КонецЦикла;
Рассмотрим физический уровень передачи данных. Если кассовый аппарат подключен через физический или виртуальный COM-порт, выполним следующие настройки:
19200 бод вместо 115200. Скорость печати термопринтера все равно значительно ниже скорости потока данных, поэтому кассир не заметит замедления, однако риск переполнения буфера контроллера аппарата снизится до минимума.1000–1500 мс. Это позволит драйверу дождаться ответа от регистратора, если тот занят отрезкой бумаги или продвижением ленты.Разберем программное решение проблемы. Главная ошибка типовых алгоритмов — игнорирование свойств возврата драйвера ResultCode после выполнения каждой фискальной операции. Если регистратор вернул ненулевой код (ошибка формата, занятость устройства), цикл 1С должен немедленно прерваться, а открытый чек — аннулироваться.
Посмотрим на пример правильной организации цикла регистрации строк:
Для Каждого Стр Из ТаблицаТоваров Цикл
// Очищаем наименование от непечатных управляющих символов и обрезаем по длине
ОчищенноеНаименование = СокрЛП(Стр.Номенклатура.Наименование);
ОчищенноеНаименование = СтрЗаменить(ОчищенноеНаименование, Символы.ПС, " ");
ОчищенноеНаименование = СтрЗаменить(ОчищенноеНаименование, Символы.ВК, " ");
ОчищенноеНаименование = СтрЗаменить(ОчищенноеНаименование, Символы.Таб, " ");
ОчищенноеНаименование = Лев(ОчищенноеНаименование, 36); // Ограничение буфера наименования ККТ
// Заполняем свойства драйвера
Драйвер.ItemName = ОчищенноеНаименование;
Драйвер.Price = Стр.Цена;
Драйвер.Quantity = Стр.Количество;
Драйвер.Summ = Стр.Сумма;
// Выполняем регистрацию продажи
Драйвер.Sale();
// Обязательно проверяем код завершения команды
Если Драйвер.ResultCode <> 0 Тогда
ТекстОшибки = "Ошибка регистрации строки " + Строка(Стр.НомерСтроки) + ": " + Драйвер.ResultCodeDescription;
ЗаписьЖурналаРегистрации("ПечатьЧека", УровеньЖурналаРегистрации.Ошибка, , ДокументСсылка, ТекстОшибки);
// Прерываем выполнение и аннулируем частично пробитый чек
Драйвер.CancelCheck();
ВызватьИсключение ТекстОшибки;
КонецЕсли;
// Небольшая задержка для синхронизации буфера (throttling) при интенсивной печати
// Даем процессору кассы завершить позиционирование ленты
// При необходимости можно использовать паузу через внешние компоненты или WScript.Sleep
КонецЦикла;
Чтобы исключить любую возможность закрытия чека с неполной суммой, перед вызовом финализирующей команды оплаты добавим контрольную сверку. Запросим у регистратора накопленный промежуточный итог и сравним его с итоговой суммой документа в 1С:
// Запрашиваем подытог у фискального аппарата
Драйвер.GetSubtotal();
Если Драйвер.ResultCode = 0 Тогда
СуммаВКассе = Драйвер.Summ;
Если СуммаВКассе <> ДокументСсылка.СуммаДокумента Тогда
// Суммы не совпали - чек закрывать категорически нельзя
Драйвер.CancelCheck();
ТекстРасхождения = "Сумма по чеку в кассе (" + Строка(СуммаВКассе) +
") не совпадает с суммой документа (" + Строка(ДокументСсылка.СуммаДокумента) + ")! Чек аннулирован.";
ЗаписьЖурналаРегистрации("ПечатьЧека", УровеньЖурналаРегистрации.Ошибка, , ДокументСсылка, ТекстРасхождения);
ВызватьИсключение ТекстРасхождения;
КонецЕсли;
Иначе
Драйвер.CancelCheck();
ВызватьИсключение "Не удалось получить подытог чека: " + Драйвер.ResultCodeDescription;
КонецЕсли;
Если программные проверки не выявили поврежденных строк, а таймауты и скорости скорректированы, обратим внимание на физическое состояние оборудования:
Реализация жесткой проверки ResultCode каждой товарной строки и валидация GetSubtotal() гарантируют, что касса никогда не пробьет чек с отсутствующей строкой, а расхождения между отчетами и фактической выручкой будут полностью исключены.