При разработке на управляемых формах в 1С:Предприятие часто возникает задача: сформировать отчет или печатную форму на сервере (где есть доступ к базе данных) и показать его пользователю на клиенте. Начинающие разработчики часто совершают ошибку, пытаясь сохранить результат в файл на сервере, ожидая, что клиентское приложение сможет его открыть. Давайте разберемся, почему так делать не стоит, и рассмотрим правильные способы решения этой задачи.
В исходном коде мы видим, что автор пытается выполнить запрос на сервере, поместить данные в табличный документ, а затем сохранить этот документ во временный каталог:
ТП.Записать(КаталогВременныхФайлов +"Запрос.XLSX", ТипФайлаТабличногоДокумента.XLSX);
Затем возникает вопрос: "Могу ли я теперь получить этот документ с клиента?". Ответ: Нет, напрямую не можете. Давайте проанализируем причины:
&НаСервере, работает на компьютере-сервере (в случае клиент-серверного варианта работы). Функция КаталогВременныхФайлов() вернет путь к папке Temp именно на сервере (например, C:\Windows\Temp\...).Поэтому сохранять файл на диск сервера для передачи клиенту — это тупиковый путь, если не использовать механизмы передачи файлов через временное хранилище. Однако для передачи именно объекта ТабличныйДокумент существуют гораздо более простые и изящные способы.
Как правильно заметили участники обсуждения, объект ТабличныйДокумент в платформе 1С является сериализуемым (XDTO-сериализация). Это означает, что он может "путешествовать" между сервером и клиентом напрямую через возвращаемое значение функции. Платформа сама упакует его в XML/JSON, передаст по сети и распакует на клиенте.
Давайте перепишем логику автора, превратив процедуру в функцию. Это самый простой способ, если отчет не весит сотни мегабайт.
Шаг 1. Создаем серверную функцию
Вместо процедуры &НаСервере, которая меняет данные формы, мы создадим функцию &НаСервереБезКонтекста (или &НаСервере, если нужен доступ к реквизитам формы), которая вернет готовый документ.
&НаСервере
Функция СформироватьОтчетНаСервере(НачалоПериода, КонецПериода)
// 1. Создаем и заполняем ТабличныйДокумент
ТабДок = Новый ТабличныйДокумент;
// 2. Формируем запрос (используя консоль запросов)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| Товары.Номенклатура КАК Номенклатура,
| Док.Дата КАК Дата,
| Штрихкоды.Штрихкод КАК Штрихкод
|ИЗ
| Документ.ПриобретениеТоваровУслуг.Товары КАК Товары
| ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПриобретениеТоваровУслуг КАК Док
| ПО Товары.Ссылка = Док.Ссылка
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ШтрихкодыНоменклатуры КАК Штрихкоды
| ПО Товары.Номенклатура = Штрихкоды.Номенклатура
|ГДЕ
| Док.Дата МЕЖДУ &ПериодНачало И &ПериодКонец";
Запрос.УстановитьПараметр("ПериодНачало", НачалоПериода);
Запрос.УстановитьПараметр("ПериодКонец", КонецПериода);
// 3. Используем ПостроительОтчета (как в примере автора)
// Хотя в современных решениях лучше использовать СКД или ручной вывод областей
Построитель = Новый ПостроительОтчета;
Построитель.ИсточникДанных = Новый ОписаниеИсточникаДанных(Запрос.Выполнить().Выгрузить());
// Выводим результат в наш ТабДок
Построитель.Вывести(ТабДок);
// 4. Возвращаем готовый объект на клиент
Возврат ТабДок;
КонецФункции
Шаг 2. Вызываем функцию с клиента
Теперь на клиенте нам достаточно получить результат выполнения функции и показать его пользователю.
&НаКлиенте
Процедура Сформировать(Команда)
// Вызываем серверную функцию и получаем результат в переменную
ПолученныйТабДок = СформироватьОтчетНаСервере(Объект.НачалоПериода, Объект.КонецПериода);
// Отображаем документ пользователю (для анализа структуры поможет консоль отчетов)
ПолученныйТабДок.Показать("Результат запроса");
КонецПроцедуры
Этот метод идеально подходит для задач, где результат выборки не слишком велик. Мы избегаем работы с файловой системой и передаем данные в оперативной памяти.
Если отчет получается очень большим, передача его напрямую через возврат функции может "подвесить" интерфейс на время передачи данных, так как объем передаваемого трафика возрастет. В таких случаях, а также если нужно передать именно файл (например, Excel), используют механизм Временного хранилища.
Рассмотрим, как это работает:
ПоместитьВоВременноеХранилище.Пример реализации через временное хранилище:
&НаСервере
Функция ПоместитьОтчетВХранилище(НачалоПериода, КонецПериода)
// ... (код формирования ТабДок аналогичен предыдущему примеру) ...
ТабДок = Новый ТабличныйДокумент;
// ... заполнение ТабДок ...
// Помещаем ТабличныйДокумент во временное хранилище
// УникальныйИдентификатор формы передаем, чтобы хранилище жило пока открыта форма
АдресХранилища = ПоместитьВоВременноеХранилище(ТабДок, УникальныйИдентификатор);
Возврат АдресХранилища;
КонецФункции
&НаКлиенте
Процедура Сформировать(Команда)
Адрес = ПоместитьОтчетВХранилище(Объект.НачалоПериода, Объект.КонецПериода);
// Получаем документ из хранилища уже на клиенте
// Важно: ПолучитьИзВременногоХранилища работает и на клиенте для простых типов,
// но для ТабДокумента надежнее использовать отображение через URL или методы работы с файлами.
// Вариант А: Просто показать (если это ТабДок)
// К сожалению, на клиенте ПолучитьИзВременногоХранилища имеет ограничения.
// Поэтому для ТабДока чаще используется Решение №1.
// Вариант Б: Если мы сохранили ТабДок как Excel во временное хранилище (как двоичные данные)
// Тогда мы можем предложить пользователю скачать его.
КонецПроцедуры
Важное замечание: ТабличныйДокумент — это особый объект 1С. Метод с прямой передачей (Решение №1) работает лучше всего, так как платформа оптимизирована для этого. Метод с временным хранилищем чаще используется для передачи файлов (картинок, внешних отчетов, экспорта в изображения, Excel-файлов, сформированных как ДвоичныеДанные).
Давайте посмотрим на фрагменты кода из темы, чтобы понять, чего делать не стоит:
Избыточность действий:
результат = запрос.Выполнить.Выбрать;
пока результат.Следующий Цикл
// заполнение ТЧ
КонецЦикла;
РезЗапроса = запрос.Выполнить.Выгрузить;
Автор сначала перебирает выборку, чтобы заполнить табличную часть Объект.товары, а затем снова выполняет или выгружает запрос для ПостроительОтчета. Двойное обращение к результату запроса может быть ресурсоемким. Лучше выгрузить результат в Таблицу Значений один раз и использовать её как источник.
Использование Построителя Отчета:
ПостроительОтчета = Новый ПостроительОтчета;
Объект ПостроительОтчета считается устаревшим механизмом (legacy), пришедшим из обычных форм. В управляемом приложении стандартом де-факто является Система Компоновки Данных (СКД). Она позволяет формировать отчеты с гораздо меньшим количеством кода и лучшей гибкостью настроек. Однако для простых "плоских" выводов можно использовать и прямой перебор выборки с выводом областей в макет.
Попытка сохранить в XLSX на сервере:
ТП.Записать(КаталогВременныхФайлов +"Запрос.XLSX", ...);
Если ваша цель — отдать пользователю именно Excel-файл (или выполнить обратную операцию — загрузку из Excel), то алгоритм должен быть таким:
ТабличныйДокумент на сервере.ДвоичныеДанные.ДвоичныеДанные в ПоместитьВоВременноеХранилище.НачатьПолучениеФайла (или ПолучитьФайл), передав туда адрес.Если вам нужно просто показать данные в 1С:
&НаСервере.ТабличныйДокумент.Возврат..Показать().Это самый быстрый и надежный способ без лишних операций с файловой системой.