January 14, 2022

ФФД 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 фискализации:

<?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Строка(ЭлементМассиваКМ.ДлинныйЗакодированныйКМ));
			Прервать;
		КонецЕсли;
	КонецЦикла;
	...
КонецПроцедуры

Самое интересное, что при такой доработке без явных проверок средствами ККТ, КМ успешно выводится из оборота, а в ОФД у чека стоит признак "М+", видимо если штриху отдать стразу информацию о фискализации с КМ, он его проверяет неявно. Напоминает конечно костыль, но для временного решения вполне пойдет.

В заключение еще раз, эта статья не является инструкцией, она просто описывает основные методические моменты фискализации чеков и способ отправки ранее проверенных КМ в кассу без команд явной проверки. При написании статьи мог допустить кучу ляпов. Любая критика приветствуется.

←11 | заметка 12 | 13→