Как отправить несколько печатных форм одному контрагенту одним письмом в 1С?

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

При автоматизации рассылки документов из 1С разработчики часто сталкиваются с задачей пакетной отправки. Например, когда в исходной таблице содержится несколько строк по одному и тому же контрагенту (разные счета, акты или накладные), и их требуется объединить в одно электронное письмо, а не отправлять десяток писем подряд. Это убережет почтовый ящик клиента от спама и повысит лояльность к компании.

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

Общий алгоритм решения задачи

Для того чтобы сгруппировать файлы и отправить их одному адресату, нам необходимо выполнить следующие шаги:

  1. Получить уникальный список получателей (контрагентов). Для этого мы скопируем исходную таблицу и свернем ее по колонке контрагента.
  2. Запустить цикл по уникальным контрагентам. На каждой итерации этого цикла будет создаваться ровно одно письмо.
  3. Найти все документы, относящиеся к текущему контрагенту. Мы выберем строки из исходной таблицы, соответствующие текущему получателю.
  4. Сформировать печатные формы. Во внутреннем цикле мы обойдем найденные строки, сгенерируем печатные формы (в формате PDF, XLS или др.) и поместим их в массив вложений для текущего письма.
  5. Отправить письмо. После обхода всех строк текущего контрагента мы отправляем готовое письмо со всеми вложениями и переходим к следующему контрагенту.

Разбор типичной ошибки с методом НайтиСтроки()

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


ТЗ = ТЗФормы.Скопировать();
ТЗ.Свернуть("Контрагент");
Для Каждого СтрокаКонтрагент ИЗ ТЗ Цикл
    // ОШИБКА! Метод НайтиСтроки ожидает структуру, а не ссылку на элемент
    Ссылки = ТЗФормы.НайтиСтроки(СтрокаКонтрагент.Контрагент); 
КонецЦикла;

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

Правильный вариант вызова метода выглядит следующим образом:


ПараметрыОтбора = Новый Структура("Контрагент", СтрокаКонтрагент.Contragent);
Ссылки = ТЗФормы.НайтиСтроки(ПараметрыОтбора);

Оптимальный код группировки и отправки писем

Ранее в старых конфигурациях разработчики часто сохраняли печатные формы во временные файлы на жестком диске с помощью метода ПолучитьИмяВременногоФайла(). Это приводило к фрагментации диска и захламлению временных каталогов операционной системы при сбоях отправки.

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


// Шаг 1. Получаем уникальный список контрагентов для отправки писем
ТЗКонтрагенты = ТЗФормы.Скопировать(, "Контрагент");
ТЗКонтрагенты.Свернуть("Контрагент");

// Шаг 2. Запускаем цикл по уникальным адресатам
Для Каждого СтрокаКонтрагент Из ТЗКонтрагенты Цикл
    
    ТекущийКонтрагент = СтрокаКонтрагент.Контрагент;
    
    // Проверим, заполнен ли контрагент в строке
    Если Не ЗначениеЗаполнено(ТекущийКонтрагент) Тогда
        Продолжить;
    КонецЕсли;
    
    // Массив вложений конкретно для ЭТОГО контрагента
    ВложенияДляПисьма = Новый Массив;
    
    // Шаг 3. Ищем все строки в исходной таблице по нашему контрагенту
    Отбор = Новый Структура("Контрагент", ТекущийКонтрагент);
    НайденныеСтроки = ТЗФормы.НайтиСтроки(Отбор);
    
    // Шаг 4. Наполняем массив вложений для текущего письма
    Для Каждого СтрокаПечати Из НайденныеСтроки Цикл
        
        ТабДок = СтрокаПечати.ТабличныйДокумент; // Наш табличный документ (печатная форма)
        ИмяВложения = СтрокаПечати.Представление; // Например, "Акт сверки №120"
        
        // Оптимальная запись файла в память без использования диска
        Поток = Новый ПотокВПамяти();
        ТабДок.Записать(Поток, ТипФайлаТабличногоДокумента.PDF);
        ДвоичныеДанныеФайла = Поток.ЗакрытьИПолучитьДвоичныеДанные();
        
        // Помещаем во временное хранилище (требуется для БСП)
        АдресХранилища = ПоместитьВоВременноеХранилище(ДвоичныеДанныеФайла, Новый УникальныйИдентификатор);
        
        // Формируем структуру описания вложения для механизмов почты БСП
        ОписаниеВложения = Новый Структура;
        ОписаниеВложения.Вставить("Представление", ИмяВложения + ".pdf");
        ОписаниеВложения.Вставить("АдресВоВременномХранилище", АдресХранилища);
        
        ВложенияДляПисьма.Добавить(ОписаниеВложения);
        
    КонецЦикла;
    
    // Шаг 5. Формируем и отправляем письмо, если собраны вложения
    Если ВложенияДляПисьма.Количество() > 0 Тогда
        
        // Получаем адрес электронной почты контрагента с помощью функций БСП
        АдресПолучателя = УправлениеКонтактнойИнформацией.КонтактнаяИнформацияОбъекта(
            ТекущийКонтрагент, 
            Справочники.ВидыКонтактнойИнформации.EmailКонтрагента, 
            ТекущаяДата()
        );
        
        Если ПустаяСтрока(АдресПолучателя) Тогда
            ЗаписьЖурналаРегистрации("Отправка писем", УровеньЖурналаРегистрации.Предупреждение, , , 
                "Не найден адрес почты для контрагента: " + ТекущийКонтрагент);
            Продолжить;
        КонецЕсли;
        
        // Настройки структуры параметров отправляемого письма
        ПараметрыПисьма = Новый Структура;
        ПараметрыПисьма.Вставить("Кому", АдресПолучателя);
        ПараметрыПисьма.Вставить("Тема", "Пакет документов для компании " + ТекущийКонтрагент);
        ПараметрыПисьма.Вставить("Тело", "Здравствуйте! Направляем вам пакет документов во вложении к этому письму.");
        ПараметрыПисьма.Вставить("ТипТекста", "ПростойТекст");
        ПараметрыПисьма.Вставить("Вложения", ВложенияДляПисьма);
        
        // Отправка через системную учетную запись средствами общего модуля БСП
        Попытка
            УчетнаяЗапись = Справочники.УчетныеЗаписиЭлектроннойПочты.СистемнаяУчетнаяЗаписьЭлектроннойПочты;
            РаботаСПочтовымиСообщениями.ОтправитьПочтовоеСообщение(УчетнаяЗапись, ПараметрыПисьма);
        Исключение
            ЗаписьЖурналаРегистрации("Отправка писем", УровеньЖурналаРегистрации.Ошибка, , , 
                "Ошибка отправки почты контрагенту " + ТекущийКонтрагент + ": " + ОписаниеОшибки());
        КонецПопытки;
        
    КонецЕсли;
    
КонецЦикла;

Полезные советы и развитие идеи

Для того чтобы сделать ваше решение еще более надежным и масштабируемым, рекомендуем придерживаться следующих правил:

← На главную