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