В процессе разработки в среде 1С:Предприятие 8 программисты часто сталкиваются с задачей, когда при формировании печатной формы или отчета из модуля менеджера необходимо использовать значение, которое находится непосредственно в реквизите формы документа. Например, это может быть признак «Печатать с подписью», выбранный пользователем на форме принтер, или временный комментарий, не сохраняемый в базе данных.
Попытка обратиться напрямую к элементам или реквизитам формы из модуля менеджера неизбежно вызовет ошибку компиляции или выполнения. В этой статье мы подробно разберем, почему так происходит, изучим клиент-серверное взаимодействие в 1С и рассмотрим четыре практических способа решения этой задачи в зависимости от архитектуры вашей конфигурации.
Для того чтобы понять природу этой проблемы, давайте проанализируем архитектурную концепцию платформы «1С:Предприятие 8». Модуль менеджера выполняется строго на сервере и является внеконтекстным. Это значит, что он описывает общие методы класса метаданных (например, конкретного документа или справочника) и «не знает», из какого именно места программы его вызвали. Он может быть вызван программно из фонового задания, из обработки, из формы списка или из формы конкретного документа.
В то же время реквизиты формы (не путать с реквизитами самого объекта базы данных) существуют только в оперативной памяти во время работы конкретного сеанса пользователя, открывшего эту форму. Как только форма закрывается, эти данные уничтожаются. Модуль менеджера физически не имеет доступа к объекту ЭтаФорма или переменной Объект, открытой у клиента.
Рассмотрим, какими путями мы можем безопасно и красиво передать эти данные на сервер в модуль менеджера.
---
Если вы вызываете функцию формирования печатной формы или отчета напрямую из модуля формы (например, в самописной конфигурации без использования БСП), проще всего явно передать значение реквизита в качестве входящего параметра экспортной процедуры модуля менеджера.
Давайте разберем по шагам, как это реализовать на практике.
Во-первых, в модуле менеджера вашего документа (или отчета) определим экспортную функцию, которая принимает нужный нам параметр:
Функция СформироватьПечатнуюФорму(СсылкаНаДокумент, ЗначениеРеквизитаФормы) Экспорт
ТабДок = Новый ТабличныйДокумент;
// Используем полученный параметр при заполнении макета
Если ЗначениеРеквизитаФормы = Истина Тогда
// Например, выводим дополнительную секцию с факсимиле
ОбластьПодпись = Макет.ПолучитьОбласть("Подпись");
ТабДок.Вывести(ОбластьПодпись);
КонецЕсли;
Возврат ТабДок;
КонецФункции
Во-вторых, в модуле формы документа вызовем эту функцию, передав в нее реквизит формы напрямую:
&НаКлиенте
Процедура ВыполнитьПечать(Команда)
// Вызываем серверный метод формы для работы с контекстом
ТабличныйДокумент = ПолучитьПечатнуюФормуНаСервере();
ТабличныйДокумент.Показать("Печатная форма документа");
КонецПроцедуры
&НаСервере
Функция ПолучитьПечатнуюФормуНаСервере()
// Передаем ссылку на текущий документ и значение реквизита формы
Возврат Документы.РеализацияТоваровУслуг.СформироватьПечатнуюФорму(
Объект.Ссылка,
ЭтотОбъект.РеквизитФормыПодпись
);
КонецФункции
Этот способ прост и надежен, но применим только тогда, когда вы полностью контролируете процесс вызова печати.
---
В современных типовых конфигурациях 1С (таких как ERP, Управление торговлей, Комплексная автоматизация, Бухгалтерия предприятия) печать реализуется через подсистему «Печать» БСП. В данном случае вызов модуля менеджера происходит не напрямую, а через стандартные обработчики.
Для передачи параметров БСП предоставляет штатный механизм — структуру ДополнительныеПараметрыПечати. Давайте посмотрим, как это настроить.
Шаг 1. В модуле формы документа перед вызовом стандартной команды печати переопределяем или дополняем параметры отправки:
&НаКлиенте
Процедура Подключаемый_ВыполнитьКомандуПечати(Команда)
// Создаем структуру дополнительных параметров
ДополнительныеПараметрыПечати = Новый Структура;
ДополнительныеПараметрыПечати.Вставить("ВыводитьСкидки", ЭтотОбъект.РеквизитФормыСкидки);
ДополнительныеПараметрыПечати.Вставить("ОсобыйТекст", ЭтотОбъект.РеквизитФормыКомментарий);
// Массив объектов для печати (обычно содержит ссылку на текущий документ)
МассивОбъектов = Новый Массив;
МассивОбъектов.Добавить(Объект.Ссылка);
// Вызываем стандартную процедуру БСП с передачей структуры параметров
УправлениеПечатьюКлиент.ВыполнитьКомандуПечати(
"Документ.РеализацияТоваровУслуг",
"ИмяМакетаПечати",
МассивОбъектов,
ЭтотОбъект,
ДополнительныеПараметрыПечати
);
КонецПроцедуры
Шаг 2. В модуле менеджера документа в стандартной экспортной процедуре Печать извлекаем переданные параметры из входящей структуры:
Процедура Печать(МассивОбъектов, ПараметрыПечати, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт
// Инициализируем переменные дефолтными значениями
ВыводитьСкидки = Ложь;
ОсобыйТекст = "";
// Проверяем, пришли ли параметры из формы
Если ПараметрыПечати <> Неопределено Тогда
Если ПараметрыПечати.Свойство("ВыводитьСкидки") Тогда
ВыводитьСкидки = ПараметрыПечати.ВыводитьСкидки;
КонецЕсли;
Если ПараметрыПечати.Свойство("ОсобыйТекст") Тогда
ОсобыйТекст = ПараметрыПечати.ОсобыйТекст;
КонецЕсли;
КонецЕсли;
// Далее используем полученные переменные при формировании табличного документа
// ...
КонецПроцедуры
Использование этого механизма является стандартом разработки в среде 1С, так как оно не нарушает логику БСП и сохраняет совместимость при обновлениях системы.
---
Иногда реквизит формы представляет собой сложную структуру данных, например, ТаблицуЗначений или ДеревоЗначений, которую пользователь редактировал вручную непосредственно в интерфейсе. Передавать такие таблицы напрямую через обычные параметры не всегда эффективно (и невозможно напрямую между клиентом и сервером без сериализации).
Выясним, как решить эту задачу с помощью механизма временного хранилища:
Посмотрим на пример реализации этой схемы.
В модуле формы пишем следующий код:
&НаСервере
Функция ПодготовитьДанныеИВызватьПечать()
// Преобразуем таблицу формы в обычную Таблицу Значений
ТаблицаДляПередачи = РеквизитФормыВЗначение("ТаблицаНаФорме");
// Помещаем таблицу во временное хранилище текущего сеанса
АдресХранилища = ПоместитьВоВременноеХранилище(ТаблицаДляПередачи, ЭтотОбъект.УникальныйИдентификатор);
// Передаем только адрес (строку) в модуль менеджера
Возврат Документы.РеализацияТоваровУслуг.ПечатьПоТаблицеИзФормы(Объект.Ссылка, АдресХранилища);
КонецФункции
В модуле менеджера принимаем адрес и извлекаем данные:
Функция ПечатьПоТаблицеИзФормы(СсылкаДокумента, АдресХранилища) Экспорт
ТабДок = Новый ТабличныйДокумент;
// Извлекаем таблицу значений из временного хранилища
ТаблицаДанных = ПолучитьИзВременногоХранилища(АдресХранилища);
Если ТипЗнч(ТаблицаДанных) = Тип("ТаблицаЗначений") Тогда
Для Каждого СтрокаДанных Из ТаблицаДанных Цикл
// Обрабатываем строки и выводим их в печатную форму
// ...
КонецЦикла;
КонецЕсли;
Возврат ТабДок;
КонецФункции
---
Рассмотрим еще одну частую проблему: пользователь открыл документ, изменил реквизиты (как реквизиты формы, так и реквизиты самого документа), но еще не нажал кнопку «Записать». Если в этот момент вызвать печать, стандартный запрос в модуле менеджера пойдет в базу данных и выберет старые (записанные) значения.
Как нам получить актуальные, еще не сохраненные данные из формы в модуле менеджера? Для этого мы можем преобразовать форму в объект прямо в модуле формы на сервере с помощью функции РеквизитФормыВЗначение.
Разберем по шагам, как это выглядит в коде:
В модуле формы преобразуем объект и передаем его:
&НаСервере
Функция СформироватьПечатьБезЗаписи()
// Преобразуем ДанныеФормыСтруктура в реальный ДокументОбъект со всеми изменениями на экране
ДокументОбъект = РеквизитФормыВЗначение("Объект");
// Передаем весь объект (вместе со всеми несохраненными реквизитами) в модуль менеджера
Возврат Документы.РеализацияТоваровУслуг.СформироватьПечатьНаОсновеОбъекта(ДокументОбъект);
КонецФункции
В модуле менеджера мы отказываемся от выполнения запросов к базе данных по ссылке, а берем данные непосредственно из переданного объекта:
Функция СформироватьПечатьНаОсновеОбъекта(ДокументОбъект) Экспорт
ТабДок = Новый ТабличныйДокумент;
// Вместо запроса читаем реквизиты напрямую из объекта, переданного из формы
НомерДокумента = ДокументОбъект.Номер;
ДатаДокумента = ДокументОбъект.Дата;
Контрагент = ДокументОбъект.Контрагент;
// Обходим табличную часть, которая на форме изменена, но в базе еще старая
Для Каждого СтрокаТовара Из ДокументОбъект.Товары Цикл
// Заполняем области макета данными из строки
КонецЦикла;
Возврат ТабДок;
КонецФункции
Важное примечание: Этот способ требует осторожности, так как передача тяжелых объектов между контекстами нагружает оперативную память сервера. По возможности старайтесь перед печатью программно записывать модифицированный документ методом ЭтотОбъект.Записать().
---
Когда вы проектируете связь между формой и модулем менеджера, всегда помните о следующих правилах:
Используя предложенные методики, вы сможете гибко передавать любые контекстные данные из формы в модули менеджеров, сохраняя при этом чистоту и производительность кода вашей конфигурации.