Как реализовать загрузку и разбор электронных накладных (УПД) под маркировку табачной продукции в 1С 7.7

Программист 1С v7.7 Управленческий учет Торговля и дистрибуция
← На главную

С введением обязательной маркировки табачной продукции перед многими владельцами розничных торговых точек и автоматизаторами встал вопрос: как соответствовать новым требованиям законодательства, если учет ведется на проверенной временем, но устаревшей платформе 1С:Предприятие 7.7? Переход на «восьмерку» для небольших магазинов часто экономически нецелесообразен, а покупка платных лицензий на работу через API у крупных операторов ЭДО бьет по карману мелкой розницы.

В этой статье мы подробно разберем, как обойти дорогостоящие интеграции и реализовать эффективный механизм «псевдо-интеграции». Мы научимся бесплатно скачивать XML-файлы универсальных передаточных документов (УПД) через WEB-интерфейс любого оператора ЭДО (Диадок, СБИС, ЭДО Лайт) и загружать их в конфигурацию «1С 7.7» (на примере типовой «Торговля и Склад 9.2») абсолютно бесплатно, используя встроенные инструменты разбора XML-структур и регулярные выражения.

Постановка задачи и архитектура решения

Суть нашего подхода заключается в отказе от платного API-интерфейса коммерческих операторов ЭДО. Вместо этого мы будем использовать стандартный веб-браузер. Оператор торговой точки заходит в личный кабинет ЭДО по своей электронной подписи, находит входящую накладную от поставщика табака (например, «Мегаполис» или «СНС») и скачивает ее в формате XML на локальный диск компьютера. Наша задача в 1С 7.7 — автоматизировать разбор этого файла, сопоставить контрагентов, номенклатуру по штрихкодам и загрузить данные в документ поступления, включая коды маркировки блоков и пачек.

Для корректной работы этого механизма нам понадобятся:

  1. Установленная в операционной системе библиотека MSXML 6.0 (Microsoft XML Core Services).
  2. Внешняя компонента OpenFileDialogsManager.dll для обхода ограничений платформы 1С 7.7 на работу с длинными именами файлов (файлы УПД из ЭДО часто имеют очень длинные регламентированные имена).
  3. Специализированные функции глобального модуля для работы с XML и сопоставления ИНН/КПП контрагентов.

Шаг 1. Подготовка глобального модуля 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, "Контрагент");
    Возврат Контр;
КонецФункции

Шаг 2. Разбор структуры XML маркированного УПД

Рассмотрим структуру XML-документа формата УПД по приказу ФНС № 820. Для того чтобы документ обрабатывался ГИС МТ «Честный ЗНАК» как содержащий маркированные товары, его имя обязательно должно содержать признак MARK (например, ON_NSCHFDOPPRMARK_...).

Сведения о кодах маркировки табака передаются в структуре тега НомСредИдентТов, который находится внутри сведений о товаре ДопСведТов. Посмотрим на пример разметки в реальном УПД:


<ДопСведТов НаимЕдИзм="798 Тыс.шт" АртикулТов="">
  <НомСредИдентТов>
    <НомУпак>0114601653029368213440175934120436240021736918005009500</НомУпак>
    <НомУпак>0114601653029368213440175934120553240021736918005009500</НомУпак>
  </НомСредИдентТов>
</ДопСведТов>

Обратите внимание на структуру тегов:

Важное предупреждение: При загрузке и парсинге XML обязательно проверяйте экранирование спецсимволов. Если в кодах маркировки или наименованиях содержатся спецсимволы вроде &, <, >, они должны быть корректно экранированы (например, &), иначе MSXML DOM выдаст ошибку разбора структуры файла.

Шаг 3. Извлечение кодов маркировки в обработке 1С 7.7

Напишем фрагмент кода внешней обработки, который открывает 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 (штрихкоду)
        // и заполнения табличной части документа ПоступлениеТоваров
    КонецЦикла;
КонецПроцедуры

Шаг 4. Разбор структуры табачной марки (DataMatrix)

Для сопоставления номенклатуры нам необходимо извлечь штрихкод стандарта EAN (GTIN) из самого кода маркировки (DataMatrix). Проанализируем структуру кодов маркировки табака:

Потребительская упаковка (пачка): содержит 29 символов и состоит из 4 групп данных:

  1. Первые 14 символов — код товара (GTIN). Это и есть штрихкод пачки, по которому мы будем искать товар в нашей базе 1С 7.7.
  2. Следующие 7 символов — индивидуальный серийный номер пачки.
  3. Следующие 4 символа — закодированная максимальная розничная цена (МРЦ).
  4. Последние 4 символа — криптохвост (код проверки).

Групповая упаковка (блок): содержит код формата GS1-DataMatrix. Он включает идентификаторы применения (AI):

Напишем функцию извлечения GTIN из кода маркировки блока или пачки для сопоставления с базой штрихкодов в 1С:


Функция ПолучитьGTINИзМарки(СтрокаМарки) Экспорт
    Перем ЧистаяМарка, ИдентификаторПрименения;
    ЧистаяМарка = СокрЛП(СтрокаМарки);
    
    // Если марка начинается с идентификатора применения GS1 "(01)" или "01"
    Если Найти(ЧистаяМарка, "01") = 1 Тогда
        // Отрезаем префикс "01", получаем 14 символов GTIN
        Возврат Сред(ЧистаяМарка, 3, 14);
    ИначеЕсли СтрДлина(ЧистаяМарка) = 29 Тогда
        // Для пачки первые 14 символов — это всегда GTIN
        Возврат Лев(ЧистаяМарка, 14);
    КонецЕсли;
    
    // Возвращаем как есть, если не удалось распознать формат
    Возврат Лев(ЧистаяМарка, 14);
КонецФункции

Шаг 5. Разогрегация упаковок и использование API Честного Знака

При приемке маркированных сигарет розничный магазин часто сталкивается с ситуацией, когда поставщик отгружает товар коробами или блоками, а продавать в торговом зале его будут пачками. Нам необходимо знать коды маркировки всех вложенных пачек, чтобы ККМ на кассе могла успешно провести проверку и отправить чек в ОФД.

В ГИС МТ «Честный ЗНАК» существует True API для работы с кодами. Метод получения состава агрегата возвращает всю иерархическую структуру вложенности (дерево КМ от короба до блоков и от блоков до пачек). Посмотрим на ключевые особенности работы с ним:

Шаг 6. Переход на бесплатный ЭДО Лайт и разрешительный режим

Для минимизации издержек малого бизнеса оператор ЦРПТ запустил встроенный бесплатный сервис ЭДО Лайт. Он позволяет принимать накладные с кодами маркировки от поставщиков абсолютно бесплатно непосредственно в личном кабинете Честного Знака.

Проанализируем преимущества интеграции 1С 7.7 с ЭДО Лайт:

  1. Отсутствие абонентской платы. Для входящих документов нет никаких ограничений по количеству. Вы можете бесплатно скачивать входящие УПД прямо из личного кабинета ГИС МТ.
  2. Разрешительный режим на кассе. С 2024 года вводится обязательная онлайн-проверка кодов маркировки на ККМ при продаже. При сканировании марки кассовое ПО обязано мгновенно отправить запрос в ГИС МТ для подтверждения легальности пачки. Реализация этого механизма в 1С 7.7 требует интеграции актуальных драйверов ККТ (например, АТОЛ 10.х) и обработки ответа от серверов маркировки перед формированием чека. Если код маркировки не прошел проверку (принадлежит другому владельцу, списан или заблокирован), касса должна заблокировать продажу.

Таким образом, реализовав разбор XML-файлов УПД силами встроенных механизмов 1С 7.7, мы полностью закрываем потребности розничной точки в получении и сопоставлении маркированных товаров. Нам больше не нужно тратить деньги на ежегодное продление дорогостоящих API-интерфейсов сторонних операторов ЭДО. Система работает стабильно, прозрачно и позволяет полностью контролировать оборот табачной продукции в рамках требований законодательства РФ.

← На главную