Переход на поштучный (помарочный) учет в ЕГАИС версии 3.0 и выше принес разработчикам немало сложных задач. Одной из главных проблем стало изменение формата обмена XML-документами. В накладных (ТТН) появилась иерархическая структура — так называемое «дерево упаковок», в котором акцизные марки упакованы в коробки (групповую тару), а коробки могут быть объединены в паллеты.
Особенно остро эта проблема встала перед разработчиками, поддерживающими исторические конфигурации на платформе 1С:Предприятие 7.7 («клюшки»), где стандартные средства парсинга XML часто дают сбой при работе со сложными пространствами имен (Namespaces). Ниже мы подробно разберем структуру реального файла выгрузки ТТН v3 и рассмотрим практические способы обхода этой структуры в 1С.
Проанализируем структуру блока MarkInfo, который находится внутри товарной позиции Position в XML-файле накладной. Именно здесь описывается связь между групповой тарой и марками.
<wb:InformF2>
<ce:F2RegId>TEST-FB-000000036674379</ce:F2RegId>
<ce:MarkInfo>
<ce:boxpos>
<ce:boxnumber>128500</ce:boxnumber>
<ce:amclist>
<ce:amc>22N0000030JBSAJA55608I760419003000377ICTHRFMEX71GUEWYVHJGZEV9CNFEVOP</ce:amc>
</ce:amclist>
</ce:boxpos>
</ce:MarkInfo>
</wb:InformF2>
Разберем назначение ключевых тегов этой структуры:
ce:MarkInfo — корневой узел с информацией о маркировке для данной позиции справки 2 (регистрационного номера РФУ2).ce:boxpos — описание конкретной коробки (упаковки). Сколько коробок в партии — столько и будет таких блоков.ce:boxnumber — уникальный идентификатор групповой тары (штрихкод коробки или паллеты в формате GS1/SSCC).ce:amclist — список марок, вложенных в данную коробку.ce:amc — уникальный код акцизной или федеральной специальной марки (длиной 68 или 150 символов).Полный рабочий пример структуры XML-документа WayBill_v3 выглядит следующим образом:
<ns:Documents xmlns:c="http://fsrar.ru/WEGAIS/Common" xmlns:ce="http://fsrar.ru/WEGAIS/CommonV3" xmlns:ns="http://fsrar.ru/WEGAIS/WB_DOC_SINGLE_01" xmlns:oref="http://fsrar.ru/WEGAIS/ClientRef_v2" xmlns:pref="http://fsrar.ru/WEGAIS/ProductRef_v2" xmlns:wb="http://fsrar.ru/WEGAIS/TTNSingle_v3" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ns:Owner>
<ns:FSRAR_ID>010060692222</ns:FSRAR_ID>
</ns:Owner>
<ns:Document>
<ns:WayBill_v3>
<wb:Identity>881732a5-1109-11e8-8639-0013ce8a85e4</wb:Identity>
<wb:Header>
<wb:Type>WBInvoiceFromMe</wb:Type>
<wb:NUMBER>A0000000001</wb:NUMBER>
<wb:Date>2018-02-15</wb:Date>
<wb:ShippingDate>2018-02-14</wb:ShippingDate>
<wb:Transport>
<wb:TRAN_TYPE>413</wb:TRAN_TYPE>
<wb:TRAN_COMPANY/>
</wb:Transport>
<wb:Shipper>
<oref:UL>
<oref:ClientRegId>010060692222</oref:ClientRegId>
<oref:FullName>ООО Поставщик</oref:FullName>
<oref:ShortName>Поставщик</oref:ShortName>
<oref:INN>222222222</oref:INN>
<oref:KPP>22222222</oref:KPP>
<oref:address>
<oref:Country>643</oref:Country>
<oref:RegionCode>50</oref:RegionCode>
<oref:description>Адрес поставщика</oref:description>
</oref:address>
</oref:UL>
</wb:Shipper>
<wb:Consignee>
<oref:UL>
<oref:ClientRegId>010060691111</oref:ClientRegId>
<oref:FullName>ООО Получатель</oref:FullName>
<oref:ShortName>Получатель</oref:ShortName>
<oref:INN>111111111</oref:INN>
<oref:KPP>11111111</oref:KPP>
<oref:address>
<oref:Country>643</oref:Country>
<oref:RegionCode>50</oref:RegionCode>
<oref:description>Адрес получателя</oref:description>
</oref:address>
</oref:UL>
</wb:Consignee>
</wb:Header>
<wb:Content>
<wb:Position>
<wb:Quantity>1</wb:Quantity>
<wb:Price>100</wb:Price>
<wb:Party>1</wb:Party>
<wb:Identity>1</wb:Identity>
<wb:Product>
<pref:UnitType>Packed</pref:UnitType>
<pref:Type>АП</pref:Type>
<pref:FullName>Бальзам Керамика</pref:FullName>
<pref:AlcCode>0011023000001188746</pref:AlcCode>
<pref:ShortName>Бальзам Керамика</pref:ShortName>
<pref:Capacity>0.5</pref:Capacity>
<pref:AlcVolume>43</pref:AlcVolume>
<pref:Producer>
<oref:UL>
<oref:ClientRegId>010000000210</oref:ClientRegId>
<oref:FullName>ООО Производитель</oref:FullName>
<oref:ShortName>Производитель</oref:ShortName>
<oref:INN>1012003576</oref:INN>
<oref:KPP>101201001</oref:KPP>
<oref:address>
<oref:Country>643</oref:Country>
<oref:RegionCode>10</oref:RegionCode>
<oref:description>Адрес завода</oref:description>
</oref:address>
</oref:UL>
</pref:Producer>
<pref:ProductVCode>212</pref:ProductVCode>
</wb:Product>
<wb:FARegId>TEST-FA-000000001704789</wb:FARegId>
<wb:InformF2>
<ce:F2RegId>TEST-FB-000000036674379</ce:F2RegId>
<ce:MarkInfo>
<ce:boxpos>
<ce:boxnumber>128500</ce:boxnumber>
<ce:amclist>
<ce:amc>22N0000030JBSAJA55608I760419003000377ICTHRFMEX71GUEWYVHJGZEV9CNFEVOP</ce:amc>
</ce:amclist>
</ce:boxpos>
</ce:MarkInfo>
</wb:InformF2>
</wb:Position>
</wb:Content>
</ns:WayBill_v3>
</ns:Document>
</ns:Documents>
При использовании стандартной библиотеки v7plus.dll или парсера XMLParser в среде 1С 7.7 разработчики часто сталкиваются со следующей проблемой. Метод КоличествоПодчиненных() на узле ce:boxpos возвращает значение «1», видя только узел ce:boxnumber, и наотрез «отказывается» видеть соседний узел списка марок ce:amclist.
Выясним причину этой ошибки. Она кроется в некорректной работе старых парсеров с пространствами имен XML (Namespaces), обозначенными префиксами вроде ce: или wb:. Парсер путает префиксы с именами тегов или спотыкается на невидимых символах переноса строк внутри структуры.
Разберем два проверенных решения этой технической проблемы.
Самый простой и надежный «костыль», который с успехом применяли разработчики на платформе 1С 7.7. Перед тем как передать XML-файл в объект парсера, мы считываем его как текстовый файл и вырезаем все префиксы пространств имен с помощью функции СтрЗаменить.
Реализуем этот метод по шагам:
// Создаем объект для работы с текстом
ТекстФайла = СоздатьОбъект("Текст");
ТекстФайла.Открыть(ПутьКФайлуТТН);
// Собираем весь файл в одну текстовую строку
СодержимоеXML = "";
Для НомСтроки = 1 По ТекстФайла.КоличествоСтрок() Цикл
СодержимоеXML = СодержимоеXML + ТекстФайла.ПолучитьСтроку(НомСтроки) + РазделительСтрок;
КонецЦикла;
// Удаляем префиксы пространств имен
СодержимоеXML = СтрЗаменить(СодержимоеXML, "ce:", "");
СодержимоеXML = СтрЗаменить(СодержимоеXML, "wb:", "");
СодержимоеXML = СтрЗаменить(СодержимоеXML, "ns:", "");
СодержимоеXML = СтрЗаменить(СодержимоеXML, "oref:", "");
СодержимоеXML = СтрЗаменить(СодержимоеXML, "pref:", "");
// Записываем очищенный текст во временный файл
ИмяВременногоФайла = КаталогВременныхФайлов() + "clean_ttn.xml";
ТекстОчищенный = СоздатьОбъект("Текст");
ТекстОчищенный.ДобавитьСтроку(СодержимоеXML);
ТекстОчищенный.Записать(ИмяВременногоФайла);
// Теперь загружаем файл в XML-анализатор
Анализатор = СоздатьОбъект("AddIn.XMLParser");
ДокXML = Анализатор.СоздатьДокумент();
ДокXML.Загрузить(ИмяВременногоФайла);
// Теперь методы КоличествоПодчиненных() и ПолучитьПодчиненныйПоИмени() отработают идеально!
После удаления префиксов структура превращается в стандартный XML без двоеточий, и 1С 7.7 с легкостью находит узлы boxnumber и amclist как обычные подчиненные элементы.
Если мы не хотим модифицировать исходный файл, откажемся от нестабильной библиотеки v7plus.dll. Вместо этого обратимся напрямую к возможностям операционной системы Windows и задействуем COM-объект MSXML2.DOMDocument. Этот объект поддерживает мощный язык запросов XPath, который позволяет выбирать нужные узлы напрямую, игнорируя иерархические проблемы обхода дерева.
Проанализируем пример кода с использованием XPath:
// Создаем COM-объект парсера MSXML
XMLДок = СоздатьОбъект("MSXML2.DOMDocument.6.0");
XMLДок.async = 0;
XMLДок.resolveExternals = 0;
XMLДок.load(ПутьКФайлуТТН);
// Важно: настраиваем соответствие пространств имен для выполнения XPath-запросов
XMLДок.setProperty("SelectionNamespaces", "xmlns:ce='http://fsrar.ru/WEGAIS/CommonV3' xmlns:wb='http://fsrar.ru/WEGAIS/TTNSingle_v3'");
// Напрямую выбираем все узлы коробок в документе
Коробки = XMLДок.selectNodes("//ce:boxpos");
Для Ном = 0 По Коробки.length - 1 Цикл
УзелКоробки = Коробки.item(Ном);
// Получаем номер коробки
НомерКоробки = УзелКоробки.selectSingleNode("ce:boxnumber").text;
Сообщить("Найдена коробка №: " + НомерКоробки);
// Получаем список всех марок в этой коробке
СписокМарок = УзелКоробки.selectNodes("ce:amclist/ce:amc");
Для НомМарки = 0 По СписокМарок.length - 1 Цикл
КодМарки = СписокМарок.item(НомМарки).text;
Сообщить("--- Акцизная марка: " + КодМарки);
КонецЦикла;
КонецЦикла;
Использование XPath решает проблему на корню: нам больше не требуется рекурсивно перебирать уровни вложенности и беспокоиться о количестве подчиненных узлов. Мы получаем коллекции нужных элементов напрямую по их путям.
Если вы работаете на современной платформе 1С:Предприятие 8.3, то ручной разбор XML-строк уходит в прошлое. Для чтения документов ЕГАИС используется механизм XDTO-пакетов (XML Data Transfer Objects) или потоковое чтение с помощью встроенного объекта ЧтениеXML, который отлично справляется с любыми пространствами имен.
Посмотрим на пример быстрого чтения марок и коробок на платформе 1С 8.3:
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл(ПутьКФайлуТТН);
ТекущийНомерКоробки = "";
Пока ЧтениеXML.Прочитать() Цикл
Если ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
ЛокальноеИмя = ЧтениеXML.ЛокальноеИмя;
Если ЛокальноеИмя = "boxnumber" Тогда
// Читаем значение номера коробки
ЧтениеXML.Прочитать();
ТекущийНомерКоробки = ЧтениеXML.Значение;
СтрокаСообщения = СтрШаблон("Упаковка: %1", ТекущийНомерКоробки);
Сообщить(СтрокаСообщения);
ИначеЕсли ЛокальноеИмя = "amc" Тогда
// Читаем значение марки внутри текущей коробки
ЧтениеXML.Прочитать();
КодМарки = ЧтениеXML.Значение;
СтрокаСообщения = СтрШаблон(" -> Марка: %1", КодМарки);
Сообщить(СтрокаСообщения);
КонецЕсли;
КонецЕсли;
КонецЦикла;
ЧтениеXML.Закрыть();
Использование метода ЛокальноеИмя позволяет игнорировать любые префиксы пространств имен (будь то ce:, wb: или любые другие), получая чистое имя тега для анализа. Это делает код кроссплатформенным и устойчивым к изменениям версий схем ЕГАИС.
Интеграция поштучного учета ЕГАИС требует от разработчика особого внимания к структуре XML. При проектировании архитектуры обмена рекомендуем придерживаться следующих правил: