При автоматизации рассылок из 1С, например, при отправке универсальных передаточных документов (УПД) или счетов клиентам (есть готовая обработка для автоматизации создания и отправки счетов и УПД из 1С), разработчики часто сталкиваются с необходимостью формирования файлов «на лету» без сохранения их на жесткий диск сервера. Современная платформа 1С:Предприятие 8.3 предоставляет для этого мощный механизм потоков. Однако при реализации циклической обработки документов возникают нюансы, связанные с управлением памятью, уникальностью имен файлов и очисткой ресурсов.
В этой статье мы подробно разберем, как правильно организовать цикл формирования документов, как использовать потоки в памяти для создания PDF-вложений (поможет автоматическая рассылка печатных форм и произвольных файлов по email) и как избежать типичных ошибок, приводящих к дублированию или перезаписи данных.
Рассмотрим стандартную ситуацию: у нас есть выборка из базы данных (например, результат запроса), содержащая сформированные табличные документы. Наша задача — превратить каждый документ в PDF и поместить его в структуру вложений для почтового сообщения — с этим справится модуль массового формирования и регулярной рассылки PDF-документов. Проанализируем правильный подход к реализации этой задачи:
// Предположим, у нас есть структура или соответствие для хранения вложений
Вложения = Новый Соответствие();
Пока ВыборкаУПД.Следующий() Цикл
// Получаем табличный документ из выборки
ТабДок = ВыборкаУПД.ТабличныйДокумент;
// Проверяем, не пустой ли документ, чтобы не отправлять пустые файлы
Если ТабДок.ВысотаТаблицы = 0 Тогда
Продолжить;
КонецЕсли;
// Создаем новый поток в оперативной памяти
Поток = Новый ПотокВПамяти();
// Записываем табличный документ в поток в формате PDF
ТабДок.Записать(Поток, ТипФайлаТабличногоДокумента.PDF);
// Получаем двоичные данные из потока и закрываем его для освобождения ресурсов
ДвоичныеДанные = Поток.ЗакрытьИПолучитьДвоичныеДанные();
// Формируем уникальное имя файла
ИмяФайла = "УПД_№" + ВыборкаУПД.Номер + "_" + Формат(ВыборкаУПД.Дата, "ДФ=yyyyMMdd") + ".pdf";
// Добавляем во вложения. Если используется Соответствие, старое значение с тем же ключом будет заменено.
Вложения.Вставить(ИмяФайла, ДвоичныеДанные);
КонецЦикла;
В данном примере мы используем объект ПотокВПамяти. Это позволяет избежать медленных операций ввода-вывода на диск. Метод Записать() объекта ТабличныйДокумент умеет работать напрямую с потоком, что делает код лаконичным и быстрым.
Если вы обнаружили, что во всех вложениях письма оказывается один и тот же документ, следует проанализировать ситуацию по двум направлениям:
ТабличныйДокумент инициализируется один раз ПЕРЕД циклом, а внутри цикла только заполняется, то во все вложения попадет состояние объекта на момент последней итерации. Проверьте, чтобы на каждом шаге цикла создавался новый экземпляр документа или извлекалась уникальная ссылка.Соответствии или Структуре, где ключом является имя файла. Если у двух разных документов номера совпадают (например, документы от разных организаций), метод Вставить() просто заменит первый файл вторым.Для гарантированной уникальности имен рекомендуется добавлять к названию файла GUID или временную метку с миллисекундами:
ИмяФайла = СтрШаблон("УПД_%1_%2.pdf", ВыборкаУПД.Номер, СтрЗаменить(Строка(Новый УникальныйИдентификатор()), "-", ""));
Рассмотрим подробнее вопрос производительности. При использовании ПотокВПамяти все сформированные PDF-файлы хранятся в оперативной памяти сервера до тех пор, пока письмо не будет отправлено и объект сообщения не будет уничтожен. Если вы отправляете 100 тяжелых УПД по 2 Мб каждый, вы мгновенно резервируете 200 Мб памяти в одном сеансе.
Если объем данных критичен, разберем альтернативный подход с использованием временных файлов:
ПолучитьИмяВременногоФайла("pdf") для создания пути на диске.ТабличныйДокумент в этот файл.ДвоичныеДанные.Иногда требуется не плодить множество вложений, а собрать все УПД в один PDF-файл — для этой задачи есть инструмент массового формирования счетов и реализаций с рассылкой по e-mail. Посмотрим на пример реализации такой задачи:
ОбщийТабДок = Новый ТабличныйДокумент;
Пока ВыборкаУПД.Следующий() Цикл
// Выводим очередной документ в общий документ
ОбщийТабДок.Вывести(ВыборкаУПД.ТабличныйДокумент);
// Добавляем разделитель страниц, чтобы каждый документ начинался с нового листа
ОбщийТабДок.ВывестиВертикальныйРазделительСтраниц();
КонецЦикла;
// Теперь записываем один раз общий результат
Поток = Новый ПотокВПамяти();
ОбщийТабДок.Записать(Поток, ТипФайлаТабличногоДокумента.PDF);
ДвоичныеДанные = Поток.ЗакрытьИПолучитьДвоичныеДанные();
Вложения.Вставить("Все_УПД_за_период.pdf", ДвоичныеДанные);
Если документов очень много, хорошим тоном считается упаковка их в архив — удобно через расширение для автоматизации рассылок документов и уведомлений. Это снижает риск блокировки письма почтовыми фильтрами по количеству вложений и уменьшает общий размер сообщения. В 1С это реализуется с помощью объекта ЗаписьZipФайла.
Проанализируем пример создания архива прямо в памяти:
ПотокАрхива = Новый ПотокВПамяти();
Архив = Новый ЗаписьZipФайла(ПотокАрхива);
Пока ВыборкаУПД.Следующий() Цикл
ПотокФайла = Новый ПотокВПамяти();
ВыборкаУПД.ТабличныйДокумент.Записать(ПотокФайла, ТипФайлаТабличногоДокумента.PDF);
ДДФайла = ПотокФайла.ЗакрытьИПолучитьДвоичныеДанные();
// Добавляем данные в ZIP из двоичных данных
ИмяВАрхиве = "УПД_" + ВыборкаУПД.Номер + ".pdf";
Архив.Добавить(ДДФайла, МетодСжатияZip.Сжатие, ИмяВАрхиве);
КонецЦикла;
Архив.Записать();
ДвоичныеДанныеАрхива = ПотокАрхива.ЗакрытьИПолучитьДвоичныеДанные();
Вложения.Вставить("Документы.zip", ДвоичныеДанныеАрхива);
При работе с форматом PDF в 1С следует помнить о шрифтах. Платформа встраивает шрифты в документ для корректного отображения у получателя. Если используются нестандартные или декоративные шрифты, размер файла может вырасти в десятки раз. Рекомендуется использовать стандартные шрифты типа Arial или Times New Roman.
Также не забывайте про лимиты почтовых серверов. Большинство корпоративных серверов не принимают письма объемом более 15–20 Мегабайт. Перед вставкой очередного файла в Вложения полезно проверять накопленный размер данных, используя свойство Размер() объекта ДвоичныеДанные.
Подводя итог, использование объекта ПотокВПамяти в сочетании с методом Записать() является наиболее эффективным способом формирования вложений. Главное — следить за уникальностью ключей во вложениях и своевременно освобождать ресурсы памяти.