С введением обязательной маркировки табачной продукции перед многими владельцами розничных торговых точек и автоматизаторами встал вопрос: как соответствовать новым требованиям законодательства, если учет ведется на проверенной временем, но устаревшей платформе 1С:Предприятие 7.7? Переход на «восьмерку» для небольших магазинов часто экономически нецелесообразен, а покупка платных лицензий на работу через API у крупных операторов ЭДО бьет по карману мелкой розницы.
В этой статье мы подробно разберем, как обойти дорогостоящие интеграции и реализовать эффективный механизм «псевдо-интеграции». Мы научимся бесплатно скачивать XML-файлы универсальных передаточных документов (УПД) через WEB-интерфейс любого оператора ЭДО (Диадок, СБИС, ЭДО Лайт) и загружать их в конфигурацию «1С 7.7» (на примере типовой «Торговля и Склад 9.2») абсолютно бесплатно, используя встроенные инструменты разбора XML-структур и регулярные выражения.
Суть нашего подхода заключается в отказе от платного API-интерфейса коммерческих операторов ЭДО. Вместо этого мы будем использовать стандартный веб-браузер. Оператор торговой точки заходит в личный кабинет ЭДО по своей электронной подписи, находит входящую накладную от поставщика табака (например, «Мегаполис» или «СНС») и скачивает ее в формате XML на локальный диск компьютера. Наша задача в 1С 7.7 — автоматизировать разбор этого файла, сопоставить контрагентов, номенклатуру по штрихкодам и загрузить данные в документ поступления, включая коды маркировки блоков и пачек.
Для корректной работы этого механизма нам понадобятся:
OpenFileDialogsManager.dll для обхода ограничений платформы 1С 7.7 на работу с длинными именами файлов (файлы УПД из ЭДО часто имеют очень длинные регламентированные имена).Прежде всего, настроим систему для работы с XML-документами и регулярными выражениями. В самом начале глобального модуля объявим экспортную переменную для работы с регулярными выражениями RegExp.
Перем RegExp Экспорт;
Теперь в процедуру ПриНачалеРаботыСистемы() добавим блок инициализации парсера MSXML DOM и объекта регулярных выражений VBScript. Это позволит нам быстро обрабатывать текстовые массивы и XML-структуры.
Процедура ПриНачалеРаботыСистемы()
// Инициализация XML-парсера для работы с маркировкой и ЭДО
Попытка
XML_DOM = CreateObject("MSXML2.DOMDocument.6.0");
Исключение
Сообщить("Для работы с ЭДО и маркировкой табака установите Microsoft XML Core Services!");
СтатусВозврата(0);
Возврат;
КонецПопытки;
// Инициализация объекта регулярных выражений
RegExp = CreateObject("VBScript.RegExp");
КонецПроцедуры
В конец глобального модуля добавим вспомогательные экспортные функции. Функция ЗагрузитьDOMизФайла будет отвечать за инициализацию структуры DOM из скачанного XML-файла УПД, а функции ВыделитьКПП и НайтиКонтрагентаПоИННбезКПП помогут нам сопоставить поставщика из файла с базой данных 1С.
Функция ЗагрузитьDOMизФайла(ИмяФайла) Экспорт
XML_DOM = CreateObject("MSXML2.DOMDocument.6.0");
XML_DOM.async = 0; // Отключаем асинхронную загрузку
XML_DOM.Load(ИмяФайла);
Возврат XML_DOM;
КонецФункции
Функция ВыделитьКПП(ИННКПП) Экспорт
Если СтрДлина(СокрЛП(ИННКПП)) = 12 Тогда
КПП = ""; // Физическое лицо или ИП (КПП отсутствует)
ИначеЕсли СтрДлина(СокрЛП(ИННКПП)) < 10 Тогда
КПП = ""; // Иностранная организация
ИначеЕсли СтрДлина(СокрЛП(ИННКПП)) > 12 Тогда
КПП = Сред(ИННКПП, 12); // Вырезаем КПП из объединенной строки ИНН/КПП
ИначеЕсли СтрДлина(СокрЛП(ИННКПП)) = 10 Тогда
КПП = ""; // Юрлицо РФ, КПП не указан в строке
КонецЕсли;
Возврат КПП;
КонецФункции
Разберем детально функцию поиска контрагента. Очень часто поставщики в электронных накладных указывают ИНН и КПП в одной строке через разделитель или без него. Чтобы сопоставление работало безотказно, напишем интеллектуальный алгоритм поиска по подчиненному справочнику ЮрЛица (в типовой ТиС 9.2 юридические лица подчинены справочнику Контрагенты):
Функция НайтиКонтрагентаПоИННбезКПП(ИННКПП) Экспорт
Перем ТекИНН, КПП, Спр, ТЗ, ИНН;
ИНН = СокрЛП(ИННКПП);
// Отрезаем КПП, если он передан в составе строки ИНН
Если СтрДлина(ИНН) > 10 Тогда
ИНН = Лев(ИНН, 10);
КонецЕсли;
Спр = СоздатьОбъект("Справочник.ЮрЛица");
ТЗ = СоздатьОбъект("ТаблицаЗначений");
ТЗ.НоваяКолонка("КодКонтрагента");
ТЗ.НоваяКолонка("ЮЛ", "Справочник.ЮрЛица");
ТЗ.НоваяКолонка("Контрагент", "Справочник.Контрагенты");
ТЗ.НоваяКолонка("ИНН");
ТЗ.НоваяКолонка("КПП");
ТЗ.НоваяКолонка("КодГруппыКонтр");
Спр.ВыбратьЭлементы(0);
Пока Спр.ПолучитьЭлемент() = 1 Цикл
Если Спр.ЭтоГруппа() = 1 Тогда
Продолжить;
КонецЕсли;
Если СокрЛП(Спр.ИНН) = "" Тогда
Продолжить;
КонецЕсли;
// Очищаем ИНН контрагента от лишних символов
ТекИНН = СокрЛП(Спр.ИНН);
Если СтрДлина(ТекИНН) > 10 Тогда
ТекИНН = Лев(ТекИНН, 10);
КонецЕсли;
Если ТекИНН = ИНН Тогда
ТЗ.НоваяСтрока();
ТЗ.КодКонтрагента = СокрЛП(Спр.Владелец.Код);
ТЗ.ЮЛ = Спр.ТекущийЭлемент();
ТЗ.Контрагент = Спр.Владелец;
ТЗ.ИНН = ТекИНН;
ТЗ.КПП = ВыделитьКПП(СокрЛП(Спр.ИНН));
ТЗ.КодГруппыКонтр = СокрЛП(Спр.Владелец.Parent.Код);
КонецЕсли;
КонецЦикла;
Если ТЗ.КоличествоСтрок() = 0 Тогда
Возврат 0; // Контрагент не найден в базе
КонецЕсли;
// Приоритет отдаем группе поставщиков, отсекая случайных производителей
ТЗ.Сортировать("КодКонтрагента");
Контр = ТЗ.ПолучитьЗначение(1, "Контрагент");
Возврат Контр;
КонецФункции
Рассмотрим структуру XML-документа формата УПД по приказу ФНС № 820. Для того чтобы документ обрабатывался ГИС МТ «Честный ЗНАК» как содержащий маркированные товары, его имя обязательно должно содержать признак MARK (например, ON_NSCHFDOPPRMARK_...).
Сведения о кодах маркировки табака передаются в структуре тега НомСредИдентТов, который находится внутри сведений о товаре ДопСведТов. Посмотрим на пример разметки в реальном УПД:
<ДопСведТов НаимЕдИзм="798 Тыс.шт" АртикулТов="">
<НомСредИдентТов>
<НомУпак>0114601653029368213440175934120436240021736918005009500</НомУпак>
<НомУпак>0114601653029368213440175934120553240021736918005009500</НомУпак>
</НомСредИдентТов>
</ДопСведТов>
Обратите внимание на структуру тегов:
НомУпак содержит код маркировки групповой упаковки — блока сигарет (длина кода составляет 29 символов для потребительской пачки и 46/47 символов для блока в зависимости от наличия криптохвоста и МРЦ).КИЗ используется для штучных изделий (пачек), если они отгружаются не в блоках.ИдентТрансУпак содержит код транспортной упаковки (короба или палеты).Важное предупреждение: При загрузке и парсинге XML обязательно проверяйте экранирование спецсимволов. Если в кодах маркировки или наименованиях содержатся спецсимволы вроде &, <, >, они должны быть корректно экранированы (например, &), иначе MSXML DOM выдаст ошибку разбора структуры файла.
Напишем фрагмент кода внешней обработки, который открывает XML-файл, считывает строки табличной части документа и вытягивает коды маркировки блоков (тег НомУпак) в список значений для дальнейшего сопоставления.
Процедура ЗагрузитьУПД()
Перем ИмяФайла, ПутьФайла;
// Используем компоненту OpenFileDialogsManager для предотвращения падения при длинных путях
Попытка
ЗагрузитьВнешнююКомпоненту("OpenFileDialogsManager.dll");
Диалог = СоздатьОбъект("AddIn.OpenFileDialogsManager");
Исключение
Диалог = "";
КонецПопытки;
Если ФС.ВыбратьФайл(0, ИмяФайла, ПутьФайла, "Выберите XML-файл УПД", "Файлы XML (*.xml)|*.xml", , ) = 0 Тогда
Возврат;
КонецЕсли;
ПолноеИмяФайла = ПутьФайла + ИмяФайла;
DOM = ЗагрузитьDOMизФайла(ПолноеИмяФайла);
Если ПустогоЗначения(DOM) = 1 Тогда
Предупреждение("Не удалось загрузить XML. Проверьте формат файла!");
Возврат;
КонецЕсли;
// Получаем список товаров из XML (тег ТаблСчФакт -> СведТов)
СписокТоваровXML = DOM.getElementsByTagName("СведТов");
Для Инл = 0 По СписокТоваровXML.length - 1 Цикл
ТоварNode = СписокТоваровXML.item(Инл);
НазваниеТовара = ТоварNode.getAttribute("НаимТов");
Количество = Число(ТоварNode.getAttribute("КолТов"));
Цена = Число(ТоварNode.getAttribute("ЦенаТов"));
Сумма = Число(ТоварNode.getAttribute("СтТовБезНДС"));
Сообщить("Загружаем позицию: " + НазваниеТовара + " в количестве: " + Количество);
// Извлекаем коды маркировки для данной строки
СписокМаркировок = СоздатьОбъект("СписокЗначений");
НомСредИдентNodes = ТоварNode.getElementsByTagName("НомСредИдентТов");
Если НомСредИдентNodes.length > 0 Тогда
НомСредИдентNode = НомСредИдентNodes.item(0);
// Ищем блоки (тег НомУпак)
БлокиNodes = НомСредИдентNode.getElementsByTagName("НомУпак");
Для ИндекБлока = 0 По БлокиNodes.length - 1 Цикл
КодБлока = БлокиNodes.item(ИндекБлока).text;
СписокМаркировок.ДобавитьЗначение(КодБлока);
Сообщить("--- Найден код блока: " + КодБлока);
КонецЦикла;
// Ищем пачки (тег КИЗ)
ПачкиNodes = НомСредИдентNode.getElementsByTagName("КИЗ");
Для ИндекПачки = 0 По ПачкиNodes.length - 1 Цикл
КодПачки = ПачкиNodes.item(ИндекПачки).text;
СписокМаркировок.ДобавитьЗначение(КодПачки);
Сообщить("--- Найдена пачка: " + КодПачки);
КонецЦикла;
КонецЕсли;
// Здесь вызываем процедуру сопоставления номенклатуры по GTIN (штрихкоду)
// и заполнения табличной части документа ПоступлениеТоваров
КонецЦикла;
КонецПроцедуры
Для сопоставления номенклатуры нам необходимо извлечь штрихкод стандарта EAN (GTIN) из самого кода маркировки (DataMatrix). Проанализируем структуру кодов маркировки табака:
Потребительская упаковка (пачка): содержит 29 символов и состоит из 4 групп данных:
Групповая упаковка (блок): содержит код формата GS1-DataMatrix. Он включает идентификаторы применения (AI):
01 — код товара (14 символов GTIN блока);21 — индивидуальный серийный номер блока (7 символов);8005 — сумма максимальных розничных цен вложенных пачек (6 символов);93 — код проверки (4 символа).Напишем функцию извлечения GTIN из кода маркировки блока или пачки для сопоставления с базой штрихкодов в 1С:
Функция ПолучитьGTINИзМарки(СтрокаМарки) Экспорт
Перем ЧистаяМарка, ИдентификаторПрименения;
ЧистаяМарка = СокрЛП(СтрокаМарки);
// Если марка начинается с идентификатора применения GS1 "(01)" или "01"
Если Найти(ЧистаяМарка, "01") = 1 Тогда
// Отрезаем префикс "01", получаем 14 символов GTIN
Возврат Сред(ЧистаяМарка, 3, 14);
ИначеЕсли СтрДлина(ЧистаяМарка) = 29 Тогда
// Для пачки первые 14 символов — это всегда GTIN
Возврат Лев(ЧистаяМарка, 14);
КонецЕсли;
// Возвращаем как есть, если не удалось распознать формат
Возврат Лев(ЧистаяМарка, 14);
КонецФункции
При приемке маркированных сигарет розничный магазин часто сталкивается с ситуацией, когда поставщик отгружает товар коробами или блоками, а продавать в торговом зале его будут пачками. Нам необходимо знать коды маркировки всех вложенных пачек, чтобы ККМ на кассе могла успешно провести проверку и отправить чек в ОФД.
В ГИС МТ «Честный ЗНАК» существует True API для работы с кодами. Метод получения состава агрегата возвращает всю иерархическую структуру вложенности (дерево КМ от короба до блоков и от блоков до пачек). Посмотрим на ключевые особенности работы с ним:
/cises/info позволяет получить полную информацию о статусе кодов маркировки и их иерархических связях./cises/info без авторизации более не поддерживаются из соображений безопасности. Вам обязательно потребуется получить JWT-токен авторизации в ГИС МТ с использованием УКЭП (усиленной квалифицированной электронной подписи).WinHttp.WinHttpRequest.5.1 для отправки POST- и GET-запросов на серверы Честного Знака.Для минимизации издержек малого бизнеса оператор ЦРПТ запустил встроенный бесплатный сервис ЭДО Лайт. Он позволяет принимать накладные с кодами маркировки от поставщиков абсолютно бесплатно непосредственно в личном кабинете Честного Знака.
Проанализируем преимущества интеграции 1С 7.7 с ЭДО Лайт:
Таким образом, реализовав разбор XML-файлов УПД силами встроенных механизмов 1С 7.7, мы полностью закрываем потребности розничной точки в получении и сопоставлении маркированных товаров. Нам больше не нужно тратить деньги на ежегодное продление дорогостоящих API-интерфейсов сторонних операторов ЭДО. Система работает стабильно, прозрачно и позволяет полностью контролировать оборот табачной продукции в рамках требований законодательства РФ.