Как исправить ошибку "Переменная не определена" при сохранении печатной формы в 1С?

Программист 1С v8.3 (Управляемые формы)
← На главную

При разработке в 1С часто возникает задача создать кнопку для формирования и сохранения печатной формы документа в файл, например, в формате Excel. Стандартный сценарий: на форме документа размещается кнопка, по нажатию на которую на сервере формируется табличный документ, а затем на клиенте открывается диалог для его сохранения. Однако на этом пути начинающие разработчики часто сталкиваются с ошибкой "Переменная не определена", когда пытаются передать в серверный код данные текущего документа.

Давайте подробно разберем, почему возникает эта ошибка и как ее правильно исправить. Проанализируем типичный пример кода и пошагово приведем его в рабочее состояние, а также рассмотрим дополнительные улучшения для надежности и универсальности решения.

Анализ исходной проблемы: почему "Ссылка" не определена?

Рассмотрим ситуацию. У нас есть клиентская процедура, которая вызывает серверную функцию для создания табличного документа. В этой серверной функции выполняется запрос, который должен получить данные по конкретному документу. Проблема возникает в строке, где мы пытаемся установить параметр запроса:


&НаСервере
Функция СформироватьТабДок()
	...
	Запрос = Новый Запрос;
	Запрос.Текст =
	"ВЫБРАТЬ
	| ...
	|ИЗ
	|	Документ.Меню КАК Меню
	|ГДЕ
	|	Меню.Ссылка В (&Ссылка)";
	// Здесь возникает ошибка, т.к. переменная "Ссылка" нигде не объявлена
	Запрос.Параметры.Вставить("Ссылка", Ссылка);
	...
КонецФункции

Ошибка "Переменная не определена (Ссылка)" возникает потому, что в контексте серверной функции СформироватьТабДок() действительно не существует переменной с именем Ссылка. Она не была ни объявлена, ни передана в качестве параметра функции. Платформа 1С не может "догадаться", что мы имеем в виду ссылку на тот документ, который открыт в форме на клиенте.

Основная причина кроется в непонимании контекста выполнения кода. Когда мы с клиента (&НаКлиенте) вызываем серверную процедуру (&НаСервере) в модуле формы, на сервере создается специальный контекст. В этом контексте нам доступны данные формы, но обращаться к ним нужно через специальные свойства.

Решение: правильное получение данных формы на сервере

Для решения проблемы нам нужно выполнить два ключевых шага: правильно получить ссылку на документ на сервере и корректно вернуть результат из серверной функции на клиент.

Шаг 1. Используем реквизит "Объект" для доступа к данным документа

В модуле формы документа при вызове сервера нам доступен основной реквизит формы, который по умолчанию называется Объект. Этот реквизит содержит все данные открытого документа: его реквизиты шапки и табличных частей. Чтобы получить ссылку на сам документ, нужно обратиться к свойству Ссылка этого объекта.

Таким образом, правильный способ передать ссылку в параметр запроса выглядит так:


Запрос.Параметры.Вставить("Ссылка", Объект.Ссылка);

Давайте исправим серверную функцию с учетом этого знания. Также обратим внимание, что если мы получаем данные для одного документа, логичнее использовать в запросе оператор равенства = вместо В.


&НаСервере
Функция СформироватьТабДокНаСервере()
	...
	Запрос = Новый Запрос;
	Запрос.Текст =
	"ВЫБРАТЬ
	| ...
	|ИЗ
	|	Документ.Меню КАК Меню
	|ГДЕ
	|	Меню.Ссылка = &Ссылка"; // Используем "=", т.к. ссылка одна
	
	// Правильно получаем ссылку из основного реквизита формы "Объект"
	Запрос.Параметры.Вставить("Ссылка", Объект.Ссылка);
	
	Выборка = Запрос.Выполнить().Выбрать();
	...
КонецФункции

Теперь запрос будет выполняться корректно, используя для отбора ссылку на текущий документ, открытый в форме.

Шаг 2. Возвращаем результат из функции

Вторая распространенная ошибка в исходном коде — функция объявлена как Функция, но ничего не возвращает. Функция по своему определению должна возвращать результат своей работы. В нашем случае это сформированный табличный документ.

Для этого в конце функции необходимо добавить оператор Возврат.


&НаСервере
Функция СформироватьТабДокНаСервере()
	...
	// Код формирования табличного документа ТабДок
	...
	Пока ВыборкаТабличнаяЧасть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);
		КонецЦикла;
	КонецЕсли;

	Возврат ТабДок;
	
КонецФункции

Дополнительные рекомендации и работа в веб-клиенте

Предложенное решение отлично работает в тонком и толстом клиентах. Однако при работе в веб-клиенте прямой доступ к файловой системе пользователя запрещен из соображений безопасности. Метод ТабДок.Записать() не сработает.

Для сохранения файла в веб-клиенте используется другой механизм:

  1. На сервере сформированный табличный документ помещается во временное хранилище с помощью функции ПоместитьВоВременноеХранилище(). Эта функция возвращает уникальный адрес данных.
  2. Этот адрес возвращается на клиент.
  3. На клиенте вызывается метод ПолучитьФайл(), которому передается адрес из временного хранилища, имя файла и интерактивный флаг. Этот метод инициирует стандартное скачивание файла средствами браузера.

Также хорошей практикой является вынос логики формирования печатной формы из модуля формы в модуль объекта документа. Это позволяет повторно использовать код печати как для интерактивных кнопок, так и для стандартных команд печати, избегая дублирования кода.

Следуя этим шагам, вы сможете не только исправить ошибку "Переменная не определена", но и создать надежный и универсальный механизм для работы с печатными формами в вашей конфигурации.

← На главную