В процессе разработки в среде 1С Предприятие часто возникает задача, когда перед выводом печатной формы на экран или отправкой ее на принтер необходимо запросить у пользователя дополнительные параметры. Например, выбрать подписанта, указать склады, настроить отображение колонок с ценами или выбрать дополнительные реквизиты. Начинающие разработчики часто пытаются вызвать открытие диалогового окна непосредственно из процедуры формирования печатной формы на сервере, что приводит к критическим ошибкам системы. Для исключения ручного ввода при подготовке документов используется система автоматического формирования счетов и договоров из электронных писем.
В этой статье мы подробно разберем, почему возникает ошибка отсутствия общего модуля или недоступности интерактивных функций, проанализируем архитектурные ограничения платформы 1С и реализуем правильный, отказоустойчивый механизм вызова вспомогательной формы перед печатью по стандартам БСП (Библиотеки Стандартных Подсистем).
Рассмотрим типовую ошибку, с которой сталкиваются разработчики при попытке решить задачу "в лоб". Код формирования печатной формы располагается в модуле менеджера документа или специализированном общем модуле печати. Эти модули всегда выполняются в контексте &НаСервере.
Попытка вызвать метод ОткрытьФорму из серверного кода выглядит следующим образом:
// Код в модуле менеджера (Выполняется НА СЕРВЕРЕ)
Функция ПечатьМХ(МассивОбъектов, ОбъектыПечати, ПараметрыПечати)
// Попытка открыть форму интерактивно из серверного контекста
МТД_ОбщийМодуль1.ОткрытьФорму(); // Ошибка: общий модуль не обнаружен или метод недоступен!
// ... дальнейшая логика формирования макета
КонецФункции
При выполнении этого кода система выдает ошибку о том, что общий модуль не обнаружен, либо сообщает о недопустимости интерактивных действий на сервере. Объясняется это фундаментальным правилом платформы 1С: любые интерактивные действия (открытие окон, предупреждения, вопросы пользователю) могут выполняться исключительно на стороне клиента (&НаКлиенте). Серверный процесс работает в фоновом режиме, у него нет графического интерфейса, и он физически не может показать форму пользователю и дождаться его ответа.
Для решения этой задачи нам необходимо полностью разделить процесс на три логических этапа:
Рассмотрим два практических способа реализации этой схемы: "быстрый" (через перехват команды на форме списка) и "архитектурно верный" (через переопределение обработчика команды БСП).
---
Если ваша конфигурация построена на базе БСП (например, 1С:Бухгалтерия, 1С:УНФ, 1С:ERP, 1С:КА), то в ней используется единый механизм печати. Разработчики БСП предусмотрели штатный механизм для предварительного сбора параметров на клиенте. Для этого при регистрации команды печати указывается специальный клиентский обработчик.
Разберем по шагам, как это реализовать в своем расширении.
В модуле менеджера вашего документа найдите процедуру ДобавитьКомандыПечати. Переопределите ее в расширении с помощью аннотации &Вместо. Вместо стандартного обработчика БСП укажите свой собственный общий модуль, который будет работать на клиенте:
&Вместо("ДобавитьКомандыПечати")
Процедура МТД_ДобавитьКомандыПечати(КомандыПечати)
КомандаПечати = КомандыПечати.Добавить();
КомандаПечати.Идентификатор = "МТД_Макет";
КомандаПечати.Представление = НСтр("ru = 'Новая печать (МХ-18)'");
// Указываем НАШ СОБСТВЕННЫЙ КЛИЕНТСКИЙ ОБРАБОТЧИК вместо стандартного
КомандаПечати.Обработчик = "МТД_ПечатьКлиент.ВыполнитьПечатьСЗапросомПараметров";
КомандаПечати.Порядок = 210;
ПродолжитьВызов(КомандыПечати);
КонецПроцедуры
Создайте в расширении новый общий модуль с именем МТД_ПечатьКлиент. В его свойствах установите флажок Клиент (управляемое приложение). Все остальные флажки (включая Сервер) должны быть сняты!
Внутри этого модуля мы напишем процедуру открытия формы и процедуру ожидания ответа (обратного вызова). Проанализируем этот код:
&НаКлиенте
Процедура ВыполнитьПечатьСЗапросомПараметров(ОписаниеКоманды) Экспорт
// Структура параметров, которую мы можем передать в открываемую форму (например, значения по умолчанию)
ПараметрыОткрытия = Новый Структура;
ПараметрыОткрытия.Вставить("ОбъектыПечати", ОписаниеКоманды.ОбъектыПечати);
// Создаем ОписаниеОповещения. Оно указывает, какую процедуру запустить, когда форма настроек закроется.
// Передаем ОписаниеКоманды в качестве дополнительных параметров, чтобы не потерять контекст БСП.
ОписаниеОповещения = Новый ОписаниеОповещения("ПослеВводаПараметровПечати", ЭтотОбъект, ОписаниеКоманды);
// Открываем форму нашей обработки настроек интерактивно
ОткрытьФорму(
"Обработка.МТД_ПечатьНаОбработку.Форма.Форма",
ПараметрыОткрытия,
ОписаниеКоманды.Источник, // Владелец формы
УникальныйИдентификатор,
,
,
ОписаниеОповещения,
РежимОткрытияОкнаФормы.БлокироватьВесьИнтерфейс // Блокируем окно, чтобы пользователь завершил ввод
);
КонецПроцедуры
&НаКлиенте
Процедура ПослеВводаПараметровПечати(РезультатВыбора, ОписаниеКоманды) Экспорт
// Если пользователь закрыл форму крестиком или нажал "Отмена", РезультатВыбора будет Неопределено
Если РезультатВыбора = Неопределено Тогда
Возврат;
КонецЕсли;
// РезультатВыбора содержит структуру настроек, возвращенных из формы.
// Передаем их в стандартный механизм БСП через структуру ПараметрыПечати.
ПараметрыПечати = Новый Структура;
ПараметрыПечати.Вставить("ДополнительныеПараметрыПечати", РезультатВыбора);
// Теперь, когда параметры собраны, вызываем штатную печать БСП
УправлениеПечатьюКлиент.ВыполнитьКомандуПечати(
ОписаниеКоманды.МенеджерПечати,
ОписаниеКоманды.Идентификатор,
ОписаниеКоманды.ОбъектыПечати,
ОписаниеКоманды.Источник,
ПараметрыПечати
);
КонецПроцедуры
В вашей форме настроек (в нашем примере это форма обработки МТД_ПечатьНаОбработку) создайте кнопку "ОК" или "Применить". Напишите обработчик для этой кнопки, который соберет все заполненные реквизиты в структуру и передаст их в метод Закрыть:
&НаКлиенте
Процедура КомандаОК(Команда)
// Формируем структуру с настройками, которые выбрал пользователь
ВыбранныеНастройки = Новый Структура;
ВыбранныеНастройки.Вставить("Отправитель", Объект.Отправитель);
ВыбранныеНастройки.Вставить("Подписант", Объект.Подписант);
ВыбранныеНастройки.Вставить("Получатель", Объект.Получатель);
// Закрываем форму и передаем структуру.
// Платформа автоматически передаст ее в качестве первого параметра в процедуру "ПослеВводаПараметровПечати".
Закрыть(ВыбранныеНастройки);
КонецПроцедуры
Теперь в модуле менеджера в серверной процедуре Печать мы можем легко и безопасно извлечь переданные настройки прямо из параметра ПараметрыПечати:
&Вместо("Печать")
Процедура МТД_Печать(МассивОбъектов, ПараметрыПечати, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт
Если УправлениеПечатью.НужноПечататьМакет(КоллекцияПечатныхФорм, "МТД_Макет") Тогда
// Извлекаем наши настройки
ДопНастройки = Неопределено;
Если ПараметрыПечати <> Неопределено Тогда
ПараметрыПечати.Свойство("ДополнительныеПараметрыПечати", ДопНастройки);
КонецЕсли;
// Передаем настройки непосредственно в функцию генерации табличного документа
ТабДокумент = ПечатьМХ(МассивОбъектов, ОбъектыПечати, ДопНастройки);
УправлениеПечатью.ВывестиТабличныйДокументВКоллекцию(
КоллекцияПечатныхФорм,
"МТД_Макет",
"МТД макет",
ТабДокумент,
,
"ОбщийМакет.ПФ_MXL_МХ18"
);
Иначе
ПродолжитьВызов(МассивОбъектов, ПараметрыПечати, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода);
КонецЕсли;
КонецПроцедуры
Данный подход является наиболее чистым и поддерживаемым. Кнопка печати будет корректно работать из любого места конфигурации: из формы документа, из формы списка, общего журнала или из групповой обработки печати документов.
---
Если по каким-то причинам вам нужно реализовать это быстро и только для одной конкретной формы (например, формы списка документов), можно использовать метод перехвата подключаемых команд на клиенте самой формы. Этот метод использовался в обсуждении на форуме.
Давайте подробно разберем, как правильно реализовать этот перехват, избежав бесконечных циклов печати и дублирования окон.
В расширении для формы списка вашего документа переопределите процедуру Подключаемый_ВыполнитьКоманду:
&НаКлиенте
&Вместо("Подключаемый_ВыполнитьКоманду")
Процедура МТД_Подключаемый_ВыполнитьКоманду(Команда)
// Проверяем, что нажата именно наша кнопка печати
Если Команда.Имя = "ПодменюПечатьОбычное_МТД_Макет" Тогда
// Создаем структуру с контекстом команды для последующего запуска
КонтекстКоманды = Новый Структура;
КонтекстКоманды.Вставить("Команда", Команда);
ОписаниеОповещения = Новый ОписаниеОповещения("ПослеЗакрытияФормыОбработки", ЭтотОбъект, КонтекстКоманды);
// Открываем форму параметров
ОткрытьForm = "Обработка.МТД_ПечатьНаОбработку.Форма.Форма";
ОткрытьФорму(ОткрытьForm, , , , , , ОписаниеОповещения, РежимОткрытияОкнаФормы.БлокироватьВесьИнтерфейс);
// КРИТИЧЕСКИ ВАЖНО: Делаем Возврат!
// Это останавливает дальнейшее выполнение стандартного механизма печати БСП.
// Без "Возврат" форма откроется, но печать запустится параллельно, не дожидаясь ввода параметров.
Возврат;
КонецЕсли;
// Для всех остальных команд вызываем стандартный обработчик
ПродолжитьВызов(Команда);
КонецПроцедуры
&НаКлиенте
Процедура ПослеЗакрытияФормыОбработки(Результат, КонтекстКоманды) Экспорт
// Если пользователь закрыл форму параметров без сохранения
Если Результат = Неопределено Тогда
Возврат;
КонецЕсли;
// Сохраняем полученные параметры. В форуме предлагалось использовать ХранилищеОбщихНастроек.
// Запишем настройки на сервере во временное хранилище или кэш сеанса, чтобы серверная печать могла их прочитать.
СохранитьНастройкиДляПечатиВСеансе(Результат);
// Возобновляем прерванное выполнение команды печати
// Теперь стандартный код БСП отработает штатно, но на сервере уже будут сохраненные настройки
ПодключаемыеКомандыКлиент.ВыполнитьКоманду(ЭтотОбъект, КонтекстКоманды.Команда, Элементы.Список);
КонецПроцедуры
Для сохранения настроек между вызовами на клиенте и сервере можно использовать параметры сеанса или временное хранилище. Пример сохранения через ХранилищеОбщихНастроек (вызывается на сервере):
&НаСервереБезКонтекста
Процедура СохранитьНастройкиДляПечатиВСеансе(Результат)
// Сохраняем настройки в системное хранилище под уникальным именем текущего пользователя
ХранилищеОбщихНастроек.Сохранить("МТД_ПараметрыПечатиМХ18", Пользователи.ТекущийПользователь(), Результат);
КонецПроцедуры
Соответственно, в серверном коде формирования макета печати вам потребуется прочитать эти данные обратно:
// В модуле менеджера при генерации макета
Настройки = ХранилищеОбщихНастроек.Загрузить("МТД_ПараметрыПечатиМХ18", Пользователи.ТекущийПользователь());
Если Настройки <> Неопределено Тогда
// Применяем настройки к макету
КонецЕсли;
Хотя этот способ рабочий, помните о его недостатках: использование ХранилищеОбщихНастроек создает лишнюю нагрузку на базу данных (производится физическая запись в таблицы SQL). Кроме того, если печать будет вызвана из другого места программы, этот перехват просто не сработает.
---
Мы детально изучили проблему программного открытия окон при печати документов в 1С 8.3. Основной вывод: всегда разделяйте интерфейсную логику (клиент) и логику генерации данных (сервер). Техническое обслуживание и проверка стабильности системы после доработок кода выполняются через инструмент для автоматического тестирования и обновления баз 1С.
Для качественной реализации используйте штатный асинхронный механизм БСП с переопределением свойства Обработчик команды печати и применением ОписаниеОповещения. Это гарантирует стабильную работу вашей программы, чистоту кода и отсутствие непредвиденных ошибок при обновлении конфигурации.