Как отправить печатную форму на почту в формате PDF без сохранения файла на диск?

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

Введение в проблему

В практике разработки на платформе 1С:Предприятие часто возникает задача отправки печатных форм (счетов, накладных, актов) клиентам по электронной почте. Самый простой способ, который приходит в голову начинающему разработчику, — это сохранить табличный документ в файл по жестко заданному пути, а затем прикрепить этот файл к письму. Рассмотрим пример кода, с которого обычно начинаются подобные попытки:


// Пример, как делать НЕ стоит в многопользовательской среде
ТабДок = Новый ТабличныйДокумент;
// ... заполнение документа ...
ТабДок.Записать("e:\prim.pdf", ТипФайлаТабличногоДокумента.PDF);

Письмо = Новый ИнтернетПочтовоеСообщение;
Письмо.Вложения.Добавить("e:\prim.pdf");
// ... отправка ...

Такой подход имеет существенные недостатки. Если несколько пользователей одновременно нажмут кнопку отправки, возникнет конфликт доступа к файлу e:\prim.pdf. Кто-то получит чужую печатную форму, или программа выдаст ошибку блокировки файла. Кроме того, в облачных инфраструктурах или при строгих настройках безопасности сервера запись на диск C:\ или E:\ может быть вовсе запрещена.

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

Способ 1. Использование временных файлов

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

Рассмотрим алгоритм действий:

  1. Получаем имя временного файла с нужным расширением.
  2. Записываем табличный документ по этому пути.
  3. Прикрепляем файл к письму.
  4. Желательно (но не обязательно для отправки) удалить файл после использования, чтобы не засорять диск.

Разберем код реализации:


// 1. Получаем уникальное имя временного файла
// Платформа сама создаст путь в папке Temp пользователя или сервера
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("pdf");

// 2. Записываем Табличный Документ
ТабДок.Записать(ИмяВременногоФайла, ТипФайлаТабличногоДокумента.PDF);

// 3. Создаем письмо и добавляем вложение
Письмо = Новый ИнтернетПочтовоеСообщение;
// Метод Добавить здесь принимает путь к файлу
Письмо.Вложения.Добавить(ИмяВременногоФайла);

// ... код отправки письма ...

// 4. Хороший тон: удаляем временный файл после отправки
УдалитьФайлы(ИмяВременногоФайла);

Преимущества: Простота реализации, работает на старых платформах (8.2, старые релизы 8.3).
Недостатки: Требуется работа с дисковой подсистемой (IO operations), что медленнее работы с памятью. Необходимо следить за удалением файлов, иначе папка Temp может переполниться.

Способ 2. Использование потоков в памяти (Рекомендуемый)

Начиная с версии платформы 8.3.10, мы получили возможность работать с потоками в памяти. Это более современный и производительный метод, идеально подходящий для серверных процедур и облачных баз. Мы вообще не касаемся жесткого диска, все операции происходят в оперативной памяти (RAM).

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


// Создаем поток в оперативной памяти
Поток = Новый ПотокВПамяти;

// Записываем табличный документ прямо в поток
// Обратите внимание: первый параметр теперь не строковый путь, а объект Поток
ТабДок.Записать(Поток, ТипФайлаТабличногоДокумента.PDF);

// Получаем ДвоичныеДанные из потока
// После этого метода поток закрывается для записи
ДвоичныеДанныеPDF = Поток.ЗакрытьИПолучитьДвоичныеДанные();

// Формируем письмо
Письмо = Новый ИнтернетПочтовоеСообщение;

// ВАЖНЫЙ МОМЕНТ:
// При добавлении двоичных данных во вложение, вторым параметром
// нужно обязательно указать имя файла, которое увидит получатель.
// Если этого не сделать, файл может прийти как "untitled.bin" или подобное.
Письмо.Вложения.Добавить(ДвоичныеДанныеPDF, "СчетНаОплату.pdf");

Давайте рассмотрим подробнее нюансы метода Письмо.Вложения.Добавить. У него есть перегрузки. Если мы передаем путь к файлу (строку), платформа сама берет имя файла из пути. Если же мы передаем объект ДвоичныеДанные, платформа не знает, как назвать файл. Поэтому мы должны явно задать имя с расширением (например, "Order.pdf").

Преимущества:

Настройка параметров PDF

В обоих рассмотренных способах мы использовали метод Записать. Давайте углубимся в его возможности. Часто требуется не просто создать PDF, а, например, защитить его паролем или оптимизировать размер (актуально для отправки по почте).

Для этого используем объект ПараметрыЗаписиPDF. Посмотрим на пример:


ПараметрыPDF = Новый ПараметрыЗаписиPDF;
ПараметрыPDF.ВнедрятьШрифты = Истина; // Чтобы документ выглядел одинаково везде
ПараметрыPDF.ПарольВладельца = "SecretPassword"; // Защита от редактирования

// Используем параметры при записи в поток
ТабДок.Записать(Поток, ТипФайлаТабличногоДокумента.PDF, ПараметрыPDF);

Архитектурные особенности: Клиент-Сервер

Важно помнить, где именно выполняется ваш код. Отправка почты (работа с объектом ИнтернетПочтовоеСообщение) обычно происходит на Сервере, так как там настроен доступ в Интернет и к SMTP-шлюзам.

Если табличный документ формируется у вас на клиенте (например, пользователь видит печатную форму на экране и нажимает кнопку "Отправить"), вы не можете просто так передать ТабличныйДокумент на сервер, так как это мутабельный объект (его состояние может меняться).

В этом случае, правильная схема взаимодействия будет следующей:

  1. На клиенте помещаем табличный документ во временное хранилище.
  2. Передаем адрес этого хранилища на сервер.
  3. На сервере получаем табличный документ и конвертируем его в PDF (через поток).

Разберем пример реализации перехода с Клиента на Сервер:


&НаКлиенте
Процедура ОтправитьНаПочту(Команда)
    
    // Помещаем ТабДок во временное хранилище
    // УникальныйИдентификатор нужен, чтобы хранилище жило пока открыта форма
    АдресВХранилище = ПоместитьВоВременноеХранилище(Объект.ТабДок, УникальныйИдентификатор);
    
    // Вызываем серверную процедуру
    ОтправитьПечатнуюФормуНаСервере(АдресВХранилище);
    
КонецПроцедуры

&НаСервере
Процедура ОтправитьПечатнуюФормуНаСервере(Адрес)
    
    // Получаем ТабДок из хранилища
    ТабДокДляОтправки = ПолучитьИзВременногоХранилища(Адрес);
    
    // Далее используем метод с потоком
    Поток = Новый ПотокВПамяти;
    ТабДокДляОтправки.Записать(Поток, ТипФайлаТабличногоДокумента.PDF);
    ДД = Поток.ЗакрытьИПолучитьДвоичныеДанные();
    
    // Создаем и отправляем письмо...
    // ...
    
КонецПроцедуры

Резюме

Мы выяснили, что использование жестких путей к файлам (e:\file.pdf) является плохой практикой. Для современных конфигураций 1С наиболее эффективным решением является использование объекта ПотокВПамяти. Это позволяет конвертировать табличный документ в PDF и прикрепить его к письму «на лету», не затрагивая файловую систему сервера. Если же вы поддерживаете старую конфигурацию (платформа ниже 8.3.10), используйте функцию ПолучитьИмяВременногоФайла() с последующим удалением созданного файла.

← На главную