В практике разработки на платформе 1С:Предприятие 8.3 часто возникает задача, когда из одного документа (например, специального рабочего места, реестра или акта сверки) необходимо сформировать и открыть внешнюю печатную форму (ВПФ) совершенно другого, связанного документа. Дополнительно может потребоваться не просто вывести форму на экран, а программно сохранить её в формате PDF для последующей отправки клиенту.
В данной статье мы подробно рассмотрим, как реализовать этот функционал на управляемых формах с использованием механизмов Библиотеки стандартных подсистем (БСП). Мы разберем по шагам как интерактивное открытие формы печати, так и полностью фоновое формирование файла без вывода окон на экран. Также мы проанализируем критическую архитектурную ошибку, связанную с отправкой писем в транзакции проведения документа, и найдем правильное, безопасное решение.
Рассмотрим ситуацию, когда у нас есть документ, в табличной части которого содержатся ссылки на другие документы (например, «Оказание услуг» или «Платежное требование»). Нам нужно сделать так, чтобы при установке флага или нажатии кнопки в строке табличной части открывалось стандартное окно печати 1С с уже сформированным макетом ВПФ для выбранного документа.
Для интерактивного открытия печатных форм в БСП предусмотрена специальная общая форма ОбщаяФорма.ПечатьДокументов. Чтобы её запустить, нам необходимо передать в неё правильную структуру параметров: ссылку на саму внешнюю обработку печатной формы, идентификатор команды печати и массив объектов назначения.
Давайте разберем код клиентской процедуры и серверной функции, которые решают эту задачу:
&НаКлиенте
Процедура ДокументОснованиеПечатнаяФормаПриИзменении(Элемент)
// Получаем текущую строку табличной части
ТекущаяСтрока = Элементы.ДокументОснование.ТекущиеДанные;
Если ТекущаяСтрока = Неопределено Тогда
Возврат;
КонецЕсли;
// Проверяем, установлена ли галочка просмотра
Если Не ТекущаяСтрока.Просмотр Тогда
Возврат;
КонецЕсли;
// Ищем ссылку на элемент справочника "Дополнительные отчеты и обработки"
СсылкаНаВПФ = НайтиВПФНаСервере("Платежное требование");
Если СсылкаНаВПФ.Пустая() Тогда
ВызватьИсключение "Внешняя печатная форма 'Платежное требование' не найдена в справочнике!";
КонецЕсли;
// Формируем массив объектов, для которых вызывается печать
ОбъектыНазначения = Новый Массив;
ОбъектыНазначения.Добавить(ТекущаяСтрока.Документ);
// Настраиваем параметры открытия общей формы печати БСП
ПараметрыОткрытия = Новый Структура("ИсточникДанных, ПараметрыИсточника");
ПараметрыОткрытия.ИсточникДанных = СсылкаНаВПФ;
ПараметрыИсточника = Новый Структура("ИдентификаторКоманды, ОбъектыНазначения");
// Указываем идентификатор команды, прописанный в модуле объекта ВПФ в функции СведенияОВнешнейОбработке()
ПараметрыИсточника.ИдентификаторКоманды = "ПлатежноеТребованиеСПодписями";
ПараметрыИсточника.ОбъектыНазначения = ОбъектыНазначения;
ПараметрыОткрытия.ПараметрыИсточника = ПараметрыИсточника;
// Открываем стандартную форму печати БСП
ОткрытьФорму("ОбщаяФорма.ПечатьДокументов", ПараметрыОткрытия);
КонецПроцедуры
&НаСервереБезКонтекста
Функция НайтиВПФНаСервере(НаименованиеВПФ)
Возврат Справочники.ДополнительныеОтчетыИОбработки.НайтиПоНаименованию(НаименованиеВПФ);
КонецФункции
В этом примере при изменении флага в строке табличной части система находит в справочнике Справочники.ДополнительныеОтчетыИОбработки нужную печатную форму по её наименованию, формирует параметры и открывает стандартную форму печати БСП. Пользователь видит готовый макет и может самостоятельно распечатать его или сохранить локально.
Что делать, если нам не нужно показывать форму пользователю, а требуется сразу получить файл PDF на сервере? Например, для автоматического вложения в письмо или сохранения в архив.
Для этого мы можем использовать серверные методы БСП, которые позволяют «тихо» сформировать табличный документ ВПФ, после чего конвертировать его в PDF. Рассмотрим, как это сделать правильно, избегая записи лишних временных файлов на жесткий диск сервера, работая напрямую с оперативной памятью через потоки (доступно в платформе 1С начиная с версии 8.3.9).
&НаСервереБезКонтекста
Функция СформироватьPDFВнешнейПечатнойФормы(ДокументСсылка, ИмяВнешнейПечатнойФормы, ИдентификаторКоманды)
// 1. Находим внешнюю печатную форму в справочнике
СсылкаНаВПФ = Справочники.ДополнительныеОтчетыИОбработки.НайтиПоНаименованию(ИмяВнешнейПечатнойФормы);
Если СсылкаНаВПФ.Пустая() Тогда
ВызватьИсключение "Не найдена внешняя обработка: " + ИмяВнешнейПечатнойФормы;
КонецЕсли;
// 2. Получаем объект внешней обработки через БСП
ОбъектВПФ = ДополнительныеОтчетыИОбработки.ПолучитьОбъектВнешнейОбработки(СсылкаНаВПФ);
// 3. Подготавливаем необходимые структуры данных для БСП печати
КоллекцияПечатныхФорм = УправлениеПечатью.НоваяКоллекцияПечатныхФорм(ИдентификаторКоманды);
ОбъектыПечати = Новый СписокЗначений;
ПараметрыВывода = УправлениеПечатью.ПодготовитьСтруктуруПараметровВывода();
МассивОбъектов = Новый Массив;
МассивОбъектов.Добавить(ДокументСсылка);
// 4. Вызываем экспортный метод "Печать" самой внешней обработки
ОбъектВПФ.Печать(МассивОбъектов, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода);
// Проверяем, вернулся ли табличный документ
СтрокаПечатнойФормы = КоллекцияПечатныхФорм.Найти(ИдентификаторКоманды, "ИмяВХранилище");
Если СтрокаПечатнойФормы = Неопределено Или СтрокаПечатнойФормы.ТабличныйДокумент = Неопределено Тогда
ВызватьИсключение "Не удалось сформировать табличный документ для команды: " + ИдентификаторКоманды;
КонецЕсли;
ТабДок = СтрокаПечатнойФормы.ТабличныйДокумент;
// 5. Конвертируем Табличный Документ в PDF в оперативной памяти (без создания файлов на сервере)
ПотокВПамяти = Новый ПотокВПамяти;
ТабДок.Записать(ПотокВПамяти, ТипФайлаТабличногоДокумента.PDF);
ДвоичныеДанныеФайла = ПотокВПамяти.ЗакрытьИПолучитьДвоичныеДанные();
// Помещаем во временное хранилище, чтобы передать на клиент или использовать для отправки
Возврат ПоместитьВоВременноеХранилище(ДвоичныеДанныеФайла);
КонецФункции
Используя этот серверный метод, мы получаем адрес во временном хранилище, где лежат готовые двоичные данные PDF-файла. Этот подход работает быстро, не засоряет серверную папку Temp и полностью безопасен в многопоточной среде.
Часто у разработчиков возникает соблазн встроить отправку полученного PDF-файла по электронной почте непосредственно в обработчик проведения документа (событие ОбработкаПроведения в модуле объекта). Делать это категорически запрещено!
Давайте выясним ключевые причины, почему отправка почты внутри транзакции проведения является грубой ошибкой:
Как же правильно организовать процесс отправки сформированных PDF-файлов клиентам? Разберем три основных надежных способа.
Этот способ идеально подходит для высоконагруженных систем. Мы разделяем процесс проведения документа и процесс сетевого взаимодействия:
Если письмо необходимо отправить мгновенно по инициативе пользователя (например, при нажатии кнопки на форме), можно запустить фоновое задание, которое выполнит всю тяжелую работу в отдельном потоке, не блокируя интерфейс программы:
&НаСервере
Процедура ЗапуститьОтправкуПочтыВФоне(ДокументСсылка)
ПараметрыПроцедуры = Новый Массив;
ПараметрыПроцедуры.Добавить(ДокументСсылка);
// Запускаем фоновое задание для генерации PDF и отправки почты
ФоновыеЗадания.Выполнить("МойОбщийМодульСервер.ОтправитьДокументНаПочтуФоном", ПараметрыПроцедуры);
КонецПроцедуры
Метод ОтправитьДокументНаПочтуФоном выполнится асинхронно. Пользователь сможет сразу продолжить работу с программой, а система в фоне сформирует и отправит файл.
Если вы хотите выполнить отправку прямо в сессии пользователя, делайте это на клиенте в обработчике события формы ПослеЗаписи. На этот момент транзакция проведения гарантированно завершена, и база данных находится в безопасности.
Приведем пример кода отправки письма с вложенным PDF-файлом с использованием стандартного почтового модуля БСП РаботаСПочтовымиСообщениями:
&НаСервереБезКонтекста
Процедура СформироватьИОтправитьПисьмоНаСервере(ДокументСсылка)
// 1. Получаем PDF-файл в виде временного хранилища
АдресХранилища = СформироватьPDFВнешнейПечатнойФормы(ДокументСсылка, "Платежное требование", "ПлатежноеТребованиеСПодписями");
ДвоичныеДанныеФайла = ПолучитьИзВременногоХранилища(АдресХранилища);
// 2. Настраиваем параметры отправки через БСП
УчетнаяЗапись = РаботаСПочтовымиСообщениями.СистемнаяУчетнаяЗапись();
ПараметрыПисьма = Новый Структура;
ПараметрыПисьма.Вставить("Кому", "client@email.ru");
ПараметрыПисьма.Вставить("Тема", "Документ " + Строка(ДокументСсылка));
ПараметрыПисьма.Вставить("Тело", "Добрый день! Во вложении к письму находится ваш документ.");
// Подготавливаем массив вложений
МассивВложений = Новый Массив;
Вложение = Новый Структура;
Вложение.Вставить("Представление", "Документ.pdf");
Вложение.Вставить("АдресВоВременномХранилище", ПоместитьВоВременноеХранилище(ДвоичныеДанныеФайла));
МассивВложений.Добавить(Вложение);
ПараметрыПисьма.Вставить("Вложения", МассивВложений);
// 3. Безопасная отправка сообщения
РаботаСПочтовымиСообщениями.ОтправитьПочтовоеСообщение(УчетнаяЗапись, ПараметрыПисьма);
КонецПроцедуры
Этот программный код полностью соответствует стандартам разработки 1С и БСП. Он обеспечивает высокую стабильность системы, исключает взаимные блокировки СУБД и гарантирует корректность отправляемых данных вашим клиентам.