ФФД 1.2
В эту статью буду записывать информацию о нестандартном внедрении ФФД 1.2 в части изменений связанных с передачей в ККТ кодов маркировки. Статья будет дополняться и изменяться. Видимо объем информации получится большой. Но это ни в коем случае, не инструкция. Надеюсь, что читатели найдут тут какие-нибудь полезные объяснения и идеи...
Буду описывать конкретный кейс одного из моих проектов. Вид маркируемой продукции: Одежда. Используется кассовая техника Штрих-М, в качестве торгового программного обеспечения 1С:Розница.
Сначала, пару слов как внедрять стандартное решение (позже напишу подробнее):
- Обновить 1С:Розницу до последней версии
- Обновить драйвер ККТ до версии 5.16.0.884 (32-bit) - у нас сейчас такая
- Настроить драйвер на работу с кодами маркировки
- Из поставки драйвера ККТ взять компоненту драйвера торгового оборудования 1С и установить его в 1С:Розницу, настроить как любое другое торговое оборудование
- Должно все заработать
Почему статья о нестандартном внедрении... Я подключился к проекту в апреле 2021 года. Здесь используется сильно переписанная 1С:Розница с большим количеством Legacy-кода. Поэтому обновление на последнюю версию трудоемко, требует много времени, кроме того, пока механизмы ФФД 1.2 будут дорабатываться, возможно потребовалось бы несколько обновлений. Поэтому было принято решение попробовать реализовать необходимые изменения в существующей конфигурации, чтобы заработало, а большое обновление запланировать на весну 2022 года.
- ПО - программное обеспечение
- ФФД - формат фискальных документов
- ОФД - оператор фискальных данных
- ККТ - Контрольно-кассовая техника (кассовый аппарат)
- ККМ - Контрольно-кассовая машина, по сути синоним ККТ
- ЧЗ - Честный знак - государственная система маркировки товаров
- КМ - Код маркировки нанесенный на товары в виде двумерного кода DataMatrix (не путать с QR-кодом)
В чем собственно проблема, зачем понадобилось менять ФФД... Дело в том, что ЧЗ принял не очень логичную систему кодирования. Среди прочего, о чем в отдельной статье, используются большие и маленькие символы английского алфавита, что может приводить к разным результатам сканирования, в зависимости от нажатия клавиши CapsLock. То есть КМ будет корректным, но неправильным и при продаже соответствующий правильный КМ не будет выведен из оборота. То есть перед продажей КМ необходимо проверить. Но ЧЗ при online проверке требует токен сгенерированный с помощью электронной подписи со сроком жизни 10 часов. То есть в каждый магазин торговой сети необходимо устанавливать электронную подпись, а если это невозможно, например по соображениям безопасности, то проверка не производится и вывод КМ из оборота при продаже происходит на удачу. Понятно, что средние и крупные компании разработали собственные web-сервисы проверки КМ, которые генерируют токены, получают от магазинных баз КМ, отправляют запрос в ЧЗ и возвращают ответ обратно в магазины. В такой схеме все прекрасно работает. Но регулятор решил, чтобы проверка работала у всех. А как идентифицировать отправителя запроса без электронной подписи? Правильно с помощью ККТ, которая зарегистрирована на соответствующее юр. лицо. Поэтому в ФФД 1.2 заложены процедуры проверки КМ средствами кассы и соответственно возникает целое дерево ситуаций, которые должны корректно отрабатываться торговым и кассовым ПО. Но самое печальное, что эта схема не закрывает весь бизнес-процесс. Регулятор не уточняет как например проводить инвентаризацию с проверкой КМ, ну не кассовым же аппаратом. В итоге имеем решение по эффективности сравнимое с удалением гланд с другой стороны, но тем не менее законы нужно соблюдать.
Итак, проверка у нас производится через свой web-сервис, поэтому в ККТ мы отдаем заведомо правильные КМ, зачем их проверять еще раз...
Главная официальная информация которую я использовал, чтобы решить задачу: https://its.1c.ru/db/metod8dev/content/4829/hdoc#chapter270, а техподдержка штриха натолкнула на мысль, раз проверка не нужна, попробовать сразу сделать фискализацию. В представленной ссылке сразу идем к пункту 5 и читаем фразу: "Необходимо заполнить для фискальной строки атрибут «MarkingCode» - полной КМ в BASE64". Уже теплее. В прошлой версии ОФД, КМ отправлялся тоже в BASE64, но укороченный, без криптохвоста. Еще момент, в криптохвосте обязательно должны присутствовать символы разделители GS (символы с кодом 29), а сканер должен быть настроен так, чтобы эти символы не пропускал. Для одежды формат полного КМ выглядит так:
КМбезКриптохвостаВсего31символ<GS>91xxxx<GS>92ОстальныеСимволы
Смотреть КМ с символами GS можно в текстовом редакторе Notepad++
Теперь собственно о фискализации. Дело в том, что 1С не заполняет цифровые теги кассового чека, а формирует некий XML c читабельными английскими XML-тегами. Эти данные передаются компоненте (драйверу торгового оборудования), который отдает это драйверу ККТ, а тот в свою очередь отдает это кассе. На одном из этих этапов, не знаю каком, и происходит магия преобразования информации в чековые теги.
<?xml version="1.0" encoding="UTF-8"?> <CheckPackage> <Parameters CashierName="ФИО продавца" OperationType="1" TaxationSystem="0" SaleLocation="Название магазина" CustomerEmail="" CustomerPhone=""> <AgentData/> <VendorData/> </Parameters> <Positions> <FiscalString Name="Наименование товара" Quantity="1" PriceWithDiscount="299" AmountWithDiscount="299" DiscountAmount="0" Department="1" VATRate="20" VATAmount="49.83" PaymentMethod="4" CalculationSubject="1" MeasurementUnit=""> <AgentData/> <VendorData/> </FiscalString> </Positions> <Payments Cash="299" ElectronicPayment="0" PrePayment="0" PostPayment="0" Barter="0"/> </CheckPackage>
Пример XML фискализации с кодом маркировки в прошлой версии ФФД
<?xml version="1.0" encoding="UTF-8"?> <CheckPackage> <Parameters CashierName="ФИО продавца" OperationType="1" TaxationSystem="0" SaleLocation="Название магазина" CustomerEmail="" CustomerPhone=""> <AgentData/> <VendorData/> </Parameters> <Positions> <FiscalString Name="Наименование товара" Quantity="1" PriceWithDiscount="199" AmountWithDiscount="199" DiscountAmount="0" Department="1" VATRate="10" VATAmount="18.09" PaymentMethod="4" CalculationSubject="1" MeasurementUnit=""> <GoodCodeData MarkingCode="Кракозябры_КМ_Без_Хвоста_в_Base64"/> <AgentData/> <VendorData/> </FiscalString> </Positions> <Payments Cash="199" ElectronicPayment="0" PrePayment="0" PostPayment="0" Barter="0"/> </CheckPackage>
А теперь следите за руками, XML фискализации с кодом маркировки в ФФД 1.2
<?xml version="1.0" encoding="UTF-8"?> <CheckPackage> <Parameters CashierName="ФИО продавца" OperationType="1" TaxationSystem="0" SaleLocation="Название магазина" CustomerEmail="" CustomerPhone=""> <AgentData/> <VendorData/> </Parameters> <Positions> <FiscalString Name="Наименование товара" MarkingCode="Кракозябры_КМ_с_Хвостом_в_Base64" Quantity="1" PriceWithDiscount="199" AmountWithDiscount="199" DiscountAmount="0" Department="1" VATRate="10" VATAmount="18.09" PaymentMethod="4" CalculationSubject="1" MeasurementUnit=""> <AgentData/> <VendorData/> </FiscalString> </Positions> <Payments Cash="199" ElectronicPayment="0" PrePayment="0" PostPayment="0" Barter="0"/> </CheckPackage>
То есть атрибут MarkingCode неожиданно перепрыгнул из тега <GoodCodeData> в обычный тег товарной строки <FiscalString> и все.
Смотреть отладчиком сформированный XML фискализации в моей версии розницы надо в модуле МенеджерОборудованияВызовСервера в конце функции ПодготовитьДанныеФискализацииЧека. Доработку делать необходимо в процедуре СформироватьXMLПакетДляФискализацияЧека, там где формируется строка товарной позиции. Отдельная сложность получить в этом месте длинный КМ, потому что в старых версиях 1С:Розницы он обрезан. Чтобы не переписывать кучу кода в глубине стека вызовов, я при сканировании записываю соответствие коротких и длинных КМ в массив структур и передаю это через параметр сеанса.
По почте попросили уточнить этот момент. Попробую, но универсального копипастинга из этого не получится...
// Сам параметр сеанса - строка неограниченной длины. // Хранится там сериализованный массив структур. // Понятно, что так себе решение, но мне надо было быстро, ну и оно работает. Функция ПолучитьВременныеДанныеДлинныхКодовМаркировки() Экспорт Возврат ЗначениеИзСтрокиВнутр(ПараметрыСеанса.ДлинныеКодыМаркировки); КонецФункции Процедура УстановитьВременныеДанныеДлинныхКодовМаркировки(НовыеДанные) Экспорт ПараметрыСеанса.ДлинныеКодыМаркировки = ЗначениеВСтрокуВнутр(НовыеДанные); КонецПроцедуры
Обработка РМКУправляемыйРежим - Модуль формы:
// На самом деле доработка конечно же в расширении конфигурации, // еще имеется код обращения к веб-сервису проверки. // Здесь же представлена только суть, которую смогут использовать программисты &НаКлиенте Процедура ВнешнееСобытие(Источник, Событие, Данные) Если (Событие = "Штрихкод" Или Событие = "ПолученШтрихкод") Тогда Если СтрДлина(Данные) > 30 Тогда // У нас одежда и обувь, у других видов длина КМ и положение GS возможно другие (техподдержка ЧЗ не ответила мне на этот вопрос) КороткийКМ = Лев(Данные, 31); // Здесь в данных со сканера пока нет скобок, поэтому расставляем их КороткийКМ = "(" + Лев(КороткийКМ, 2) + ")" + Сред(КороткийКМ, 3, 14) + "(" + Сред(КороткийКМ, 17, 2) + ")" + Сред(КороткийКМ, 19); ДлинныйКМ = Данные; НайденGS = Найти(Данные, Символ(29)); Если Не НайденGS Тогда // Добавляем в нужные позиции символ GS, если сканер сканирует без него Если Сред(ДлинныйКМ, 32, 2) = "91" и Сред(ДлинныйКМ, 38, 2) = "92" Тогда ДлинныйКМ = Лев(ДлинныйКМ, 31) + Символ(29) + Сред(ДлинныйКМ, 32, 6) + Символ(29) + Сред(ДлинныйКМ, 38); КонецЕсли; Данные = ДлинныйКМ; КонецЕсли; ДлинныйЗакодированныйКМ = ШтрихкодированиеИСКлиентСервер.ШтрихкодВBase64(ДлинныйКМ); МассивКМ = ОбщийМодуль_ВызовСервера.ПолучитьВременныеДанныеДлинныхКодовМаркировки(); МассивКМ.Добавить(Новый Структура("КороткийКМ, ДлинныйЗакодированныйКМ", КороткийКМ, ДлинныйЗакодированныйКМ)); ОбщийМодуль_ВызовСервера.УстановитьВременныеДанныеДлинныхКодовМаркировки(МассивКМ); КонецЕсли; КонецЕсли; КонецЕсли; КонецЕсли; ... КонецПроцедуры
Модуль МенеджерОборудованияВызовСервера:
Процедура СформироватьXMLПакетДляФискализацияЧека(ОбщиеПараметры, ПараметрыФискализации) ... МассивКМ = ОбщийМодуль_ВызовСервера.ПолучитьВременныеДанныеДлинныхКодовМаркировки(); Для Каждого ЭлементМассиваКМ из МассивКМ Цикл Если ЭлементМассиваКМ.КороткийКМ = ДанныеКодаТовара.ПредставлениеШтрихкода Тогда ЗаписьXML.ЗаписатьАтрибут("MarkingCode", XMLСтрока(ЭлементМассиваКМ.ДлинныйЗакодированныйКМ)); Прервать; КонецЕсли; КонецЦикла; ... КонецПроцедуры
Самое интересное, что при такой доработке без явных проверок средствами ККТ, КМ успешно выводится из оборота, а в ОФД у чека стоит признак "М+", видимо если штриху отдать стразу информацию о фискализации с КМ, он его проверяет неявно. Напоминает конечно костыль, но для временного решения вполне пойдет.
В заключение еще раз, эта статья не является инструкцией, она просто описывает основные методические моменты фискализации чеков и способ отправки ранее проверенных КМ в кассу без команд явной проверки. При написании статьи мог допустить кучу ляпов. Любая критика приветствуется.