«Честный знак» видит нарушение, а в чеке всё правильно: как типовая раздаёт коды маркировки не тем строкам
Симптом
Магазин торгует сигаретами. В личном кабинете «Честного знака» появляются отклонения по чекам: на одной позиции превышение МРЦ, на другой уменьшение. Продавец смотрит этот чек в 1С и не понимает претензии. Цены в документе верные, марки в документе верные, сумма сошлась.
Претензия при этом законная. Просто она не про документ, а про то, что ушло в ККТ.
У табака максимальная розничная цена зашита в код маркировки. Пачка с МРЦ 278 и пачка с МРЦ 298 несут разные коды. В данных магазина у одной карточки номенклатуры пачки с разной МРЦ имели и разные GTIN, то есть смена МРЦ там приводила к новому GTIN; документом ЦРПТ я это не сверял. «Честный знак» сверяет цену позиции фискального чека с МРЦ из кода этой же позиции. Если коды поменялись местами, одна позиция оказывается проданной дороже своей МРЦ, и это уже нарушение, зафиксированное в государственной системе.
Вот как выглядел чек на четыре пачки одной марки (номера кодов условные, данные магазина обезличены):
|
Строка |
Цена в документе |
Марка в документе (МРЦ кода) |
Что ушло в ККТ (МРЦ кода) |
|
1 |
298 |
код А (298) |
код Б (278) |
|
2 |
278 |
код Б (278) |
код В (298) |
|
3 |
298 |
код В (298) |
код Г (298) |
|
4 |
298 |
код Г (298) |
код А (298) |
Коды сдвинулись по кругу на одну позицию. Трём строкам из четырёх это безразлично: у них одинаковая цена и одинаковая МРЦ, перестановка внутри такой группы ничего не меняет. А строка с ценой 278 получила код с МРЦ 298, и строка с ценой 298 получила код с МРЦ 278. Первое даёт отклонение «уменьшение МРЦ», второе «превышение».
Подтверждение не из рассуждений, а из фискального XML, который прислал магазин: позиция с PriceWithDiscount="298" несёт MarkingCode, который декодируется в код с МРЦ 278.
Пока все пачки в чеке одной цены, дефекта не существует. Он просыпается ровно тогда, когда в одном чеке встречаются пачки одного товара с разной МРЦ, то есть в период смены МРЦ, когда старые пачки допродаются рядом с новыми.
Есть и второе условие: строки должны быть разведены по цене. Если программа сложила обе пачки в одну строку с одной ценой, переставлять нечего, но тогда и цена на обе пачки одна. В «Рознице» строки разводит штатная подстановка МРЦ при сканировании, в УТ их приходится разводить самому. То есть разведение цен не создаёт дефект, а делает видимым тот, что уже есть.
Где рвётся связь в «Рознице» 2.3
Путь чека от формы РМК до кассы:
ФормаРМК
-> ОбщегоНазначенияРМК.ДанныеДляПробитияЧека
-> Документы.ЧекККМ.ПодготовитьДанныеДляПробитияЧека
-> ПродажиСервер.ДанныеДляПробитияЧекаККМ
-> РаспределитьШтрихкодыПоТаблицеТоваров
-> ШтрихкодированиеИС.РаспределениеШтрихкодовУпаковокПоТоварам
Внутри последней функции (РаспределениеШтрихкодовУпаковокПоТоварам, строка 2386 выгрузки 2.3.25.23) товары и штрихкоды сортируются двумя независимыми вызовами, после чего спариваются по порядку следования:
ТаблицаТовары.Сортировать("Приоритет Убыв, Количество Убыв");
ТаблицаШтрихкоды.Сортировать("Приоритет Убыв, Количество Убыв");
Первое, чего в этом коде нет: КлючСвязи. В документе именно он держит связь марки со строкой, а в раздаче кодов не участвует ни разу, я проверил поиском по всему телу функции.
Две строки по одной пачке, с одинаковым приоритетом и одинаковым количеством, дают неустойчивый порядок. Какой код ляжет в какую строку, зависит от того, как легли таблицы, а не от того, какая марка была отсканирована в эту строку.
Дальше важная оговорка, без которой картина будет неверной.
Учёт МРЦ в типовой есть, но он не везде включён
Было бы неправдой сказать, что типовая про МРЦ не знает. В той же функции есть поиск строки товара с учётом цены:
ТребуетсяПоискТоваровПриРаспределенииСУчетомЦены = ТребуетсяПоискТоваровПриРаспределенииСУчетомЦены(
СтрокаШтрихкод, ПараметрыСканирования);
Если ТребуетсяПоискТоваровПриРаспределенииСУчетомЦены Тогда
СтруктураПоиска.Вставить("Цена", СтрокаШтрихкод.МРЦ);
КонецЕсли;
Условие включения требует сразу четырёх вещей: продукция МОТП, заполненная МРЦ у строки штрихкода, вид упаковки потребительская или групповая, и флаг ПараметрыСканирования.ИспользуетсяРаспределениеКодовПоТоварамСУчетомМРЦ.
С этим флагом в двух конфигурациях по-разному. В «Рознице» 2.3.25.23 он объявлен со значением Ложь в ШтрихкодированиеОбщегоНазначенияИСМПКлиентСервер и включается в ИнтеграцияИСРТКлиентСервер (строки 814 и 815). В «Управлении торговлей» 11.5.22.182 он объявлен со значением Ложь в ШтрихкодированиеОбщегоНазначенияМОТПКлиентСервер (строка 71), и включения я в выгрузке не нашёл.
При этом на копии базы магазина, «Розница» 2.3.26.13, коды в ККТ всё равно уходили переставленными: четыре контрольных чека из четырёх, плюс фискальный XML самого магазина. Почему штатный механизм там не отработал, я не доразобрался. Условие требует заполненной МРЦ у строки штрихкода, и это первое, что я стал бы проверять на месте читателя.
И ещё одно наблюдение из базы приёмов: просто включить флаг в УТ нельзя. У него нет отката. Если цена строки не равна МРЦ кода, а это обычная продажа со скидкой, код не распределится вовсе, и чек не пробьётся.
Как проверить свои чеки, не трогая кассу
Живой ККТ у меня не было ни на стенде, ни у магазина в момент разбора. Проверять на живом чеке означало бы ждать следующей продажи и потом читать отклонения в «Честном знаке», то есть ставить опыт на покупателях.
Касса для проверки и не нужна. Вся интересующая нас цепочка это серверный код, и она прогоняется на копии базы по уже пробитым чекам:
СтруктураДанных = РозничныеПродажиКлиентСервер.СтруктураДанныхДляПробитияЧека();
СтруктураДанных.ЧекККМСсылка = СсылкаЧека;
ОбщиеПараметры = РозничныеПродажиСервер.ПодготовитьДанныеДляПробитияЧека(СтруктураДанных);
Для Каждого Поз Из ОбщиеПараметры.ПозицииЧека Цикл
// код позиции: КонтрольнаяМарка, при пустом значении ШтрихкодBase64
// цена позиции: ЦенаСоСкидками, при пустом значении Цена
КонецЦикла;
Остаётся разобрать код маркировки каждой позиции, достать МРЦ и сравнить с ценой позиции. Найденное расхождение это будущее отклонение в «Честном знаке», увиденное до того, как оно туда попало.
Одна тонкость при сравнении: у блока МРЦ в коде указана на весь блок, а цена в строке за пачку. Делите МРЦ на вложенность, иначе получите ложное расхождение вида «цена 285 при МРЦ 2850».
Мои прогоны на копии базы магазина, релиз «Розницы» 2.3.26.13: 154 чека доведены до фискальных строк, 289 позиций несут код маркировки, 90 из них с распознаваемой МРЦ. На контрольных чеках со смешанными МРЦ перестановка воспроизвелась в четырёх случаях из четырёх. Отдельно прогнаны 302 последних табачных чека и 40 возвратов, чтобы видеть, что исправление не трогает обычные продажи.
Если у вас розница с табаком, этот прогон стоит сделать до того, как придут отклонения. База при этом не меняется, доступ к ГИС МТ не нужен.
Ловушка первая: я правил не то поле
Первые две версии исправления переставляли поле Штрихкод в строке таблицы товаров. Проверка показывала ноль расхождений. Магазин сообщал, что на кассе всё по-прежнему.
Причина в том, что в ККТ уходит не Штрихкод, а ПолныйКодМаркировки из структуры РезультатРаспределенияШтрихкодов. Его кладёт в позицию чека Документы.ЧекККМ.ДополнитьТоварамиПараметрыПриПробитииЧека, в поля ШтрихкодBase64 и КонтрольнаяМарка. Поле Штрихкод я исправно переставлял, а в кассу ехала исходная неверная пара.
Почему проверка этого не поймала: она сверяла с ценой ровно то поле, которое расширение только что переписало. Проверка подтверждала сама себя и выдавала ноль расхождений при живом сбое на кассе.
Правило, которое я из этого вынес: мерить надо то, что потребляет внешняя система, а не то, что правишь. Если результат уходит в ККТ, проверка читает структуру для ККТ, а не промежуточную таблицу.
Исправление свелось к тому, чтобы переставлять структуру результата распределения целиком: ПолныйКодМаркировки лежит внутри неё и переезжает вместе с кодом.
Ловушка вторая: МРЦ нельзя доставать из позиций с 22 по 25
В коде табачной пачки МРЦ лежит в известном месте, и соблазн разобрать её самому велик: четыре символа и простая арифметика. Я так и сделал, после чего на одном из чеков получил МРЦ 4 482,58 у товара, который к табаку отношения не имеет. На этих позициях у него лежал криптохвост.
Если к такому декодированию привязана блокировка чека, касса встанет на обычной продаже обычного товара, а причина будет выглядеть мистической.
Правильный путь: просить разбор у платформы через СоставКодаМаркировки, а собственное декодирование держать запасным вариантом и только для табачной потребительской пачки. На реальных данных два источника МРЦ разошлись ноль раз на сотне с лишним позиций, но эта цифра получена как раз потому, что запасной путь почти не работает.
«Управление торговлей» 11.5: то же самое в другом месте
В УТ природа дефекта та же, а точка другая: коды раздаются по предметам расчёта при формировании фискального чека. Добавляется вторая особенность, которой в «Рознице» нет: пачки одного товара с разной МРЦ склеиваются в одну строку, если не разводить их принудительно. Про подстановку цены из марки в РМК УТ я писал отдельно (//infostart.ru/1c/articles/2770145/), там же про параметр «Учитывать МРЦ».
Цифры прогона на копии базы, релиз 11.5.22.182: 5 864 табачных чека за месяц для проверки, что правка не трогает обычные продажи; 58 табачных позиций в предметах расчёта на контрольной выборке, расхождений МРЦ кода и цены позиции ноль; архивирование 60 уже пробитых чеков без единой ложной остановки.
Отдельная история с ключом группировки. Когда переставляешь коды внутри группы одинаковых позиций, группу надо определить правильно. Ключ «номенклатура плюс характеристика плюс серия» кажется достаточным, пока в чеке не встретятся блок из десяти пачек и отдельная пачка того же товара: количество в строке разное, и без него группа не разводится. Попытка добавить в ключ GTIN ломается на смене МРЦ, потому что у пачек разной МРЦ GTIN разные, и одинаковые по смыслу строки перестают собираться вместе. Рабочим оказался ключ «номенклатура, характеристика, серия, количество в строке».
Ловушка третья: релиз 11.5.27 ломает встраивание целиком
Эта история уже про сопровождение, а не про сам дефект, но знать её полезно.
В УТ 11.5.27 у типовой функции ЧекККМЛокализация.ПредметыРасчетовПоДокументу появился второй параметр. Расширение, которое встраивалось в неё через &Вместо с прежним списком параметров, на новом релизе не применяется, и вместе с ним не применяется весь модуль расширения со всеми остальными перехватами. В журнале регистрации это выглядит так:
Ошибка применения модуля "ОбщийМодуль.ЧекККМЛокализация.Модуль".
Список параметров метода "...ПредметыРасчетовПоДокументуВместо"
не соответствует методу "ПредметыРасчетовПоДокументу".
Лечится переносом встраивания в точку, сигнатура которой одинакова в обоих релизах. У меня это ФормированиеПараметровФискальногоЧекаСерверПереопределяемый.ПараметрыФискальныхСтрокЧека: через неё проходят те же предметы расчёта до превращения в позиции чека, и туда сходятся все пути на ККТ.
Рядом нашлась вещь, которая экономит вечер отладки: ПроверитьВозможностьПрименения не проверяет модули, закрытые паролем. Если вы закрываете модули расширения перед поставкой, применимость смотрите на незащищённой сборке, а защищённую проверяйте журналом регистрации после установки.
Что делает исправление
Внутри группы одинаковых позиций коды раскладываются так, чтобы МРЦ кода совпала с ценой строки: не по порядку следования, а по смыслу. Переставляется структура результата распределения целиком, вместе с полным кодом маркировки. Правка применяется только если набор марок после перестановки совпадает с набором марок документа, то есть это действительно перестановка, а не подмена; строки с частичным выбытием и групповыми упаковками не трогаются. Документ при этом не меняется, правится только то, что уходит в ККТ.
К перестановке имеет смысл добавить отсечку: не пускать на кассу чек, где цена строки выше МРЦ её пачки. Здесь я тоже сначала сделал неверно. Первая версия блокировки срабатывала на любое несовпадение цены и МРЦ, в обе стороны, то есть запрещала законную продажу со скидкой. МРЦ это максимальная цена, продавать дешевле можно, поэтому условие должно быть строго «цена больше МРЦ». Возвраты из блокировки надо исключать: иначе покупатель не сможет вернуть пачку, купленную до смены МРЦ. В моих данных нашёлся возврат с ценой 210 при коде с МРЦ 200, на котором это видно.
Чего я не проверил
Живой кассы не было. Цепочка прогнана до структуры параметров фискальной операции; вызов драйвера ККТ, ответ ОФД и ответ «Честного знака» не воспроизводились.
Состояние «до» на уровне фискальной строки снято из протокола самого расширения и из фискального XML магазина, а не чистым прогоном без расширения на том же релизе.
Проверял на «Рознице» 2.3.26.13 и на УТ 11.5.22.182 и 11.5.27.88. На других релизах типовые процедуры могут отличаться, и в 11.5.27 это уже случилось. Старый РМК «Розницы» в управляемом режиме на продаже я не проверял.
Что с этим делать на практике
Если у вас розница с табаком, проверьте себя, не дожидаясь отклонений. Нужен один прогон по своим чекам за последний месяц: довести чек до фискальных строк, разобрать код каждой позиции, сравнить МРЦ из кода с ценой позиции. Ноль расхождений означает, что пачки одного товара с разной МРЦ в одном чеке вам просто пока не попадались. Расхождения означают, что они уже уехали в «Честный знак».
Дальше выбор простой. Чинить самому, зная три ловушки выше, или взять готовое: я собрал исправление расширением для «Розницы» 2.3 и УТ 11.5, оно возвращает коды на свои строки, ставит в строку чека МРЦ из отсканированной пачки и не даёт пробить чек дороже МРЦ, конфигурацию при этом не меняет.
Что до самой типовой, то поведение, о котором идёт речь, выглядит разумным ровно до появления товара, у которого цена лежит внутри кода. Табак такой товар. Возможно, на этом месте в типовой когда-нибудь появится учёт МРЦ при раздаче кодов, и расширение станет не нужно. Проверить, есть ли проблема у вас, стоит в любом случае.
Вступайте в нашу телеграмм-группу Инфостарт
Вы можете заказать платную адаптацию этой статьи под ваши задачи на «Бирже заказов».
- 0% комиссии — оплата напрямую исполнителю;
- Исполнители любого масштаба — от отдельных специалистов до команд под проект;
- Прямой обмен контактами между заказчиком и исполнителем;
- Безопасная сделка — при необходимости;
- Рейтинги, кейсы и прозрачная система откликов.