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