При разработке внешних обработок для интеграции часто возникает задача выгрузить выбранные пользователем документы в файл формата XML. Нередко разработчики сталкиваются с ситуацией: процедура отрабатывает без видимых ошибок, результирующий XML-файл успешно создается на диске, однако внутри него присутствуют только заголовки и корневые узлы, а секция с табличной частью остается абсолютно пустой. Разберем по шагам, почему возникает эта ошибка, как исправить код выборки и какие нюансы работы с платформой 1С:Предприятие 8.3 необходимо учитывать.
Проанализируем фрагмент исходного обработчика, в котором происходил сбой при выгрузке данных:
Для Каждого Док Из ТаблицаРеализаций Цикл
Если НЕ Док.Выгружать Тогда
Продолжить;
КонецЕсли;
Для каждого Строка из ТаблицаРеализаций Цикл
Выборка = Документы.ПеремещениеТоваров.ПолучитьСсылку.Товары;
Для каждого Строка из Выборка Цикл
ХМЛ.ЗаписатьНачалоЭлемента("Товар");
// Запись атрибутов...
ХМЛ.ЗаписатьКонецЭлемента();
КонецЦикла;
КонецЦикла;
КонецЦикла;
Выясним причину, по которой строки товаров не попадали в итоговый документ. Здесь допущены сразу две грубые архитектурные ошибки:
Документы.ПеремещениеТоваров.ПолучитьСсылку.Товары пытается прочитать табличную часть у пустой ссылки (ссылочного идентификатора, не привязанного к конкретному существующему документу в информационной базе). У объекта менеджера метод ПолучитьСсылку() возвращает пустую ссылку или генерирует новый УникальныйИдентификатор, не существующий в базе. Естественно, табличная часть такого пустого значения всегда пуста, и вложенный цикл просто не выполняет ни одной итерации.ТаблицаРеализаций с проверкой флага выгрузки, но сразу же внутри запускается повторный цикл по той же самой таблице: Для каждого Строка из ТаблицаРеализаций Цикл. Кроме того, переменная итератора Строка перезаписывается во внутреннем цикле, что приводит к полной потере контекста текущей строки.Если в таблице на форме (или в таблице значений) хранится колонка с ссылкой на конкретный документ (например, колонка Расходная, содержащая документ вида ДокументСсылка.ПеремещениеТоваров), мы должны обращаться к табличной части непосредственно через полученную ссылку. Уберем дублирующий цикл и исправим обращение к данным.
Посмотрим на исправленный вариант серверной процедуры:
&НаСервере
Процедура ВыполнитьНаСервере(ИмяФайла)
ХМЛ = Новый ЗаписьXML;
ХМЛ.ОткрытьФайл(ИмяФайла, "UTF-8");
ХМЛ.ЗаписатьОбъявлениеXML();
ХМЛ.ЗаписатьНачалоЭлемента("Товары");
ХМЛ.ЗаписатьНачалоЭлемента("ТабличнаяЧасть");
Для Каждого СтрТаблицы Из ТаблицаРеализаций Цикл
Если НЕ СтрТаблицы.Выгружать Тогда
Продолжить;
КонецЕсли;
// Обращаемся к табличной части конкретного документа через колонку Расходная
Для Каждого СтрТЧ Из СтрТаблицы.Расходная.Товары Цикл
Номенклатура = СтрТЧ.Номенклатура;
ХМЛ.ЗаписатьНачалоЭлемента("Товар");
ХМЛ.ЗаписатьАтрибут("Номенклатура", XMLСтрока(Номенклатура.Наименование));
ХМЛ.ЗаписатьАтрибут("Количество", XMLСтрока(СтрТЧ.Количество));
ХМЛ.ЗаписатьАтрибут("Цена", XMLСтрока(СтрТЧ.Цена));
ХМЛ.ЗаписатьАтрибут("Артикул", XMLСтрока(Номенклатура.Артикул));
ЕдИзм = "";
Если ЗначениеЗаполнено(Номенклатура) И ЗначениеЗаполнено(Номенклатура.ЕдиницаИзмерения) Тогда
ЕдИзм = Номенклатура.ЕдиницаИзмерения.Наименование;
КонецЕсли;
ХМЛ.ЗаписатьАтрибут("ЕдиницаИзмерения", XMLСтрока(ЕдИзм));
ХМЛ.ЗаписатьКонецЭлемента(); // Товар
КонецЦикла;
КонецЦикла;
ХМЛ.ЗаписатьКонецЭлемента(); // ТабличнаяЧасть
ХМЛ.ЗаписатьКонецЭлемента(); // Товары
ХМЛ.Закрыть();
Сообщить("Выгрузка успешно завершена!");
КонецПроцедуры
Обратим внимание на форматирование чисел и строковых представлений. В исходном коде использовалась системная функция Строка(СтрТЧ.Количество). Использовать её при формировании XML крайне не рекомендуется.
Функция Строка() преобразует числовые данные в соответствии с региональными установками операционной системы. Например, число 1500 превратится в строку с неразрывным пробелом в качестве разделителя групп: "1 500", а дробная часть отделится запятой ("1 500,00"). Для XML-парсеров сторонних систем такое значение является синтаксической ошибкой (стандарт требует точку и отсутствие разделителей групп).
Для корректной сериализации значений в XML в платформе 1С предусмотрена функция XMLСтрока(). Она автоматически преобразует числа, даты и булевы значения в канонический формат стандарта XML Schema.
Конструкция СтрТаблицы.Расходная.Товары и последующее обращение СтрТЧ.Номенклатура.Артикул в цикле порождают проблему неявных запросов (проблема N+1). При каждой итерации цикла платформа отправляет в СУБД отдельные запросы за значениями реквизитов каждого ссылочного объекта. Если выгружается десяток документов по сотне строк в каждом, операция займет длительное время.
Рассмотрим профессиональное решение задачи с предварительной выборкой данных единым запросом:
&НаСервере
Процедура ВыгрузитьДокументыОптимально(ИмяФайла)
// Собираем массив документов, отмеченных для выгрузки
МассивДокументов = Новый Массив;
Для Каждого Стр Из ТаблицаРеализаций Цикл
Если Стр.Выгружать И ЗначениеЗаполнено(Стр.Расходная) Тогда
МассивДокументов.Добавить(Стр.Расходная);
КонецЕсли;
КонецЦикла;
Если МассивДокументов.Количество() = 0 Тогда
ВызватьИсключение "Не выбрано ни одного документа для выгрузки!";
КонецЕсли;
// Получаем все данные одним запросом к базе
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТЧ.Ссылка КАК ДокументСсылка,
| ТЧ.Номенклатура.Наименование КАК НоменклатураНаименование,
| ТЧ.Номенклатура.Артикул КАК Артикул,
| ТЧ.Номенклатура.ЕдиницаИзмерения.Наименование КАК ЕдиницаИзмерения,
| ТЧ.Количество КАК Количество,
| ТЧ.Цена КАК Цена
|ИЗ
| Документ.ПеремещениеТоваров.Товары КАК ТЧ
|ГДЕ
| ТЧ.Ссылка В(&МассивДокументов)
|ИТОГИ ПО
| ДокументСсылка";
Запрос.УстановитьПараметр("МассивДокументов", МассивДокументов);
РезультатЗапроса = Запрос.Выполнить();
ХМЛ = Новый ЗаписьXML;
ХМЛ.ОткрытьФайл(ИмяФайла, "UTF-8");
ХМЛ.ЗаписатьОбъявлениеXML();
ХМЛ.ЗаписатьНачалоЭлемента("Товары");
ХМЛ.ЗаписатьНачалоЭлемента("ТабличнаяЧасть");
ВыборкаДокументов = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаДокументов.Следующий() Цикл
ВыборкаСтрок = ВыборкаДокументов.Выбрать();
Пока ВыборкаСтрок.Следующий() Цикл
ХМЛ.ЗаписатьНачалоЭлемента("Товар");
ХМЛ.ЗаписатьАтрибут("Номенклатура", XMLСтрока(ВыборкаСтрок.НоменклатураНаименование));
ХМЛ.ЗаписатьАтрибут("Количество", XMLСтрока(ВыборкаСтрок.Количество));
ХМЛ.ЗаписатьАтрибут("Цена", XMLСтрока(ВыборкаСтрок.Цена));
ХМЛ.ЗаписатьАтрибут("Артикул", XMLСтрока(ВыборкаСтрок.Артикул));
ХМЛ.ЗаписатьАтрибут("ЕдиницаИзмерения", XMLСтрока(ВыборкаСтрок.ЕдиницаИзмерения));
ХМЛ.ЗаписатьКонецЭлемента();
КонецЦикла;
КонецЦикла;
ХМЛ.ЗаписатьКонецЭлемента();
ХМЛ.ЗаписатьКонецЭлемента();
ХМЛ.Закрыть();
КонецПроцедуры
Разберем еще один важный нюанс клиент-серверного взаимодействия. В коде автора выбор пути к файлу происходит на клиенте через ДиалогВыбораФайла, после чего этот путь передается параметром в процедуру &НаСервере, где вызывается ХМЛ.ОткрытьФайл(ИмяФайла).
В файловой информационной базе такой код отработает корректно. Однако в клиент-серверном режиме (SQL) метод ЗаписьXML.ОткрытьФайл() пытается создать файл в файловой системе сервера 1С, а не на компьютере пользователя. Если на сервере нет пути, аналогичного клиентскому (например, C:\Users\Иван\Desktop\...), запись завершится системной ошибкой доступа либо файл запишется на диске сервера, а пользователь его не найдет.
Для правильной реализации клиент-серверной выгрузки рекомендуется:
ПолучитьИмяВременногоФайла("xml") либо в оперативную память через метод ХМЛ.УстановитьСтроку().ПоместитьВоВременноеХранилище().ПолучитьФайлИзВременногоХранилища() или НачатьПолучениеФайлаССервера().