Как отправить несколько заказов одним письмом менеджеру в 1С

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

При автоматизации рассылок часто возникает ситуация, когда система отправляет отдельное письмо на каждое событие — например, на каждый новый заказ. Если у одного менеджера за день накопилось десять крупных сделок, он получит десять уведомлений, что крайне неудобно. Наша задача — научить 1С группировать данные и отправлять один сводный отчет одному получателю. Рассмотрим, как это реализовать правильно с точки зрения архитектуры 1С.

Причина проблемы: линейный обход выборки

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

Решение 1: Использование итогов в запросе и иерархического обхода

Это самый «правильный» и производительный способ в системе 1С:Предприятие. Мы заставляем саму СУБД сгруппировать данные по адресату. Для этого в текст запроса необходимо добавить секцию ИТОГИ ПО. Проанализируем, как должен выглядеть модифицированный код.

Сначала подготовим текст запроса. Важно, чтобы поле, по которому мы группируем (в нашем случае АдресЭП), было вынесено в итоги:


ТекстЗапроса = 
"ВЫБРАТЬ
|   ЗаказКлиента.Ссылка КАК Ссылка,
|   ЗаказКлиента.СуммаДокумента КАК СуммаДокумента,
|   ВложенныйЗапрос.АдресЭП КАК АдресЭП
|ИЗ
|   Документ.ЗаказКлиента КАК ЗаказКлиента
|   ЛЕВОЕ СОЕДИНЕНИЕ (...) КАК ВложенныйЗапрос ПО ...
|ГДЕ
|   ЗаказКлиента.СуммаДокумента >= 100000
|ИТОГИ ПО
|   АдресЭП";

Теперь рассмотрим, как правильно обойти такой результат. Вместо выгрузки в таблицу (Выгрузить()), мы будем использовать метод Выбрать() с указанием типа обхода ОбходРезультатаЗапроса.ПоГруппировкам. Это позволит нам создать вложенный цикл: внешний цикл по адресам почты и внутренний — по конкретным заказам этого адресата.


РезультатЗапроса = Запрос.Выполнить();

// Первый уровень выборки - группировка по АдресЭП
ВыборкаПочта = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

Пока ВыборкаПочта.Следующий() Цикл
    
    Получатель = ВыборкаПочта.АдресЭП;
    Если ПустаяСтрока(Получатель) Тогда
        Продолжить; // Пропускаем, если почта не заполнена
    КонецЕсли;
    
    Тема = "Список потенциальных крупных сделок";
    ТекстПисьма = "Данные счета были выставлены 3 дня назад:" + Символы.ПС;
    
    // Второй уровень выборки - детальные записи (заказы) для текущей почты
    ВыборкаЗаказы = ВыборкаПочта.Выбрать();
    
    Пока ВыборкаЗаказы.Следующий() Цикл
        ТекстПисьма = ТекстПисьма + "- " + ВыборкаЗаказы.Ссылка + " на сумму " + ВыборкаЗаказы.СуммаДокумента + " руб.;" + Символы.ПС;
    КонецЦикла;
    
    // Отправляем письмо один раз для текущего менеджера, когда текст полностью собран
    ОтправкаПисьма(Получатель, ТекстПисьма, Тема);
    
КонецЦикла;

Решение 2: Группировка через таблицу значений и метод Свернуть

Если по каким-то причинам вы предпочитаете работать с таблицами значений (например, данные в таблицу попадают из разных источников, а не только из одного запроса), можно воспользоваться методом Свернуть(). Рассмотрим этот алгоритм по шагам:

  1. Выгружаем результат запроса в объект ТаблицаЗначений.
  2. Создаем копию этой таблицы, в которой оставляем только колонку АдресЭП.
  3. Вызываем метод Свернуть("АдресЭП") для копии таблицы — теперь у нас есть список уникальных адресатов.
  4. Запускаем цикл по уникальным адресатам.
  5. Внутри цикла используем метод НайтиСтроки() к основной таблице, чтобы получить массив всех заказов конкретного менеджера.

ТаблицаДанных = Запрос.Выполнить().Выгрузить();

ТаблицаАдресатов = ТаблицаДанных.Скопировать();
ТаблицаАдресатов.Свернуть("АдресЭП");

Для Каждого СтрокаАдресат Из ТаблицаАдресатов Цикл
    
    ПараметрыОтбора = Новый Структура("АдресЭП", СтрокаАдресат.АдресЭП);
    СтрокиЗаказов = ТаблицаДанных.НайтиСтроки(ПараметрыОтбора);
    
    Текст = "Список заказов:" + Символы.ПС;
    Для Каждого СтрокаЗаказа Из СтрокиЗаказов Цикл
        Текст = Текст + СтрокаЗаказа.Ссылка + Символы.ПС;
    КонецЦикла;
    
    ОтправкаПисьма(СтрокаАдресат.АдресЭП, Текст, "Тема");
    
КонецЦикла;

Оптимизация сборки текста: используем Массив вместо конкатенации

Проанализируем важный технический нюанс. В 1С строки являются неизменяемыми. Это значит, что при выполнении операции Строка = Строка + НоваяЧасть система каждый раз создает в памяти новую строку, копируя туда старую. Если заказов в письме будет сотни, это может привести к замедлению работы.

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


МассивТекста = Новый Массив;
МассивТекста.Добавить("Здравствуйте! Список ваших заказов:");

Пока ВыборкаЗаказы.Следующий() Цикл
    СтрокаИнфо = СтрШаблон("Заказ %1 на сумму %2 руб.", 
                            ВыборкаЗаказы.Ссылка, 
                            Формат(ВыборкаЗаказы.СуммаДокумента, "ЧЦ=15; ЧДЦ=2; ЧГ=3"));
    МассивТекста.Добавить(СтрокаИнфо);
КонецЦикла;

ИтоговыйТекст = СтрСоединить(МассивТекста, Символы.ПС);

Улучшение читаемости письма: HTML-форматирование

Чтобы письмо выглядело презентабельно, рекомендуем использовать HTML. Вместо простого перечисления строк через перенос, мы можем сформировать красивую таблицу. Для этого в процедуре ОтправкаПисьма нужно указать тип текста как HTML, а в самом коде формирования сообщения использовать соответствующие теги.

Выясним, как это реализовать:


ТекстHTML = "<html><body>";
ТекстHTML = ТекстHTML + "<h2>Отчет по крупным сделкам</h2>";
ТекстHTML = ТекстHTML + "<table border='1' style='border-collapse: collapse;'>";
ТекстHTML = ТекстHTML + "<tr style='background-color: #f2f2f2;'><td>Заказ</td><td>Сумма</td></tr>";

Пока ВыборкаЗаказы.Следующий() Цикл
    ТекстHTML = ТекстHTML + "<tr>";
    ТекстHTML = ТекстHTML + "<td>" + ВыборкаЗаказы.Ссылка + "</td>";
    ТекстHTML = ТекстHTML + "<td>" + ВыборкаЗаказы.СуммаДокумента + "</td>";
    ТекстHTML = ТекстHTML + "</tr>";
КонецЦикла;

ТекстHTML = ТекстHTML + "</table></body></html>";

Использование таких методов, как ИТОГИ ПО в запросах и СтрСоединить для сборки строк, позволяет создавать надежные и быстрые механизмы рассылки, которые не спамят в почтовые ящики сотрудников и предоставляют информацию в удобном структурированном виде.

← На главную