При автоматизации рассылок часто возникает ситуация, когда система отправляет отдельное письмо на каждое событие — например, на каждый новый заказ. Если у одного менеджера за день накопилось десять крупных сделок, он получит десять уведомлений, что крайне неудобно. Наша задача — научить 1С группировать данные и отправлять один сводный отчет одному получателю. Рассмотрим, как это реализовать правильно с точки зрения архитектуры 1С.
Разберем типичную ошибку начинающего разработчика. В исходном коде автора используется обычная выгрузка результата запроса в таблицу значений и последующий цикл Для каждого. Поскольку запрос возвращает плоскую таблицу (где каждая строка — это отдельный заказ), цикл добросовестно выполняет отправку письма на каждой итерации. Даже если адрес почты в строках совпадает, команда ОтправкаПисьма вызывается столько раз, сколько строк в результате запроса. Чтобы исправить это, нам нужно изменить структуру запроса и логику обхода данных.
Это самый «правильный» и производительный способ в системе 1С:Предприятие. Мы заставляем саму СУБД сгруппировать данные по адресату. Для этого в текст запроса необходимо добавить секцию ИТОГИ ПО. Проанализируем, как должен выглядеть модифицированный код.
Сначала подготовим текст запроса. Важно, чтобы поле, по которому мы группируем (в нашем случае АдресЭП), было вынесено в итоги:
ТекстЗапроса =
"ВЫБРАТЬ
| ЗаказКлиента.Ссылка КАК Ссылка,
| ЗаказКлиента.СуммаДокумента КАК СуммаДокумента,
| ВложенныйЗапрос.АдресЭП КАК АдресЭП
|ИЗ
| Документ.ЗаказКлиента КАК ЗаказКлиента
| ЛЕВОЕ СОЕДИНЕНИЕ (...) КАК ВложенныйЗапрос ПО ...
|ГДЕ
| ЗаказКлиента.СуммаДокумента >= 100000
|ИТОГИ ПО
| АдресЭП";
Теперь рассмотрим, как правильно обойти такой результат. Вместо выгрузки в таблицу (Выгрузить()), мы будем использовать метод Выбрать() с указанием типа обхода ОбходРезультатаЗапроса.ПоГруппировкам. Это позволит нам создать вложенный цикл: внешний цикл по адресам почты и внутренний — по конкретным заказам этого адресата.
РезультатЗапроса = Запрос.Выполнить();
// Первый уровень выборки - группировка по АдресЭП
ВыборкаПочта = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаПочта.Следующий() Цикл
Получатель = ВыборкаПочта.АдресЭП;
Если ПустаяСтрока(Получатель) Тогда
Продолжить; // Пропускаем, если почта не заполнена
КонецЕсли;
Тема = "Список потенциальных крупных сделок";
ТекстПисьма = "Данные счета были выставлены 3 дня назад:" + Символы.ПС;
// Второй уровень выборки - детальные записи (заказы) для текущей почты
ВыборкаЗаказы = ВыборкаПочта.Выбрать();
Пока ВыборкаЗаказы.Следующий() Цикл
ТекстПисьма = ТекстПисьма + "- " + ВыборкаЗаказы.Ссылка + " на сумму " + ВыборкаЗаказы.СуммаДокумента + " руб.;" + Символы.ПС;
КонецЦикла;
// Отправляем письмо один раз для текущего менеджера, когда текст полностью собран
ОтправкаПисьма(Получатель, ТекстПисьма, Тема);
КонецЦикла;
Если по каким-то причинам вы предпочитаете работать с таблицами значений (например, данные в таблицу попадают из разных источников, а не только из одного запроса), можно воспользоваться методом Свернуть(). Рассмотрим этот алгоритм по шагам:
ТаблицаЗначений.АдресЭП.Свернуть("АдресЭП") для копии таблицы — теперь у нас есть список уникальных адресатов.НайтиСтроки() к основной таблице, чтобы получить массив всех заказов конкретного менеджера.
ТаблицаДанных = Запрос.Выполнить().Выгрузить();
ТаблицаАдресатов = ТаблицаДанных.Скопировать();
ТаблицаАдресатов.Свернуть("АдресЭП");
Для Каждого СтрокаАдресат Из ТаблицаАдресатов Цикл
ПараметрыОтбора = Новый Структура("АдресЭП", СтрокаАдресат.АдресЭП);
СтрокиЗаказов = ТаблицаДанных.НайтиСтроки(ПараметрыОтбора);
Текст = "Список заказов:" + Символы.ПС;
Для Каждого СтрокаЗаказа Из СтрокиЗаказов Цикл
Текст = Текст + СтрокаЗаказа.Ссылка + Символы.ПС;
КонецЦикла;
ОтправкаПисьма(СтрокаАдресат.АдресЭП, Текст, "Тема");
КонецЦикла;
Проанализируем важный технический нюанс. В 1С строки являются неизменяемыми. Это значит, что при выполнении операции Строка = Строка + НоваяЧасть система каждый раз создает в памяти новую строку, копируя туда старую. Если заказов в письме будет сотни, это может привести к замедлению работы.
Профессиональный подход заключается в использовании объекта Массив. Мы добавляем каждую строку текста в массив, а в конце объединяем их одной командой СтрСоединить. Это работает в разы быстрее:
МассивТекста = Новый Массив;
МассивТекста.Добавить("Здравствуйте! Список ваших заказов:");
Пока ВыборкаЗаказы.Следующий() Цикл
СтрокаИнфо = СтрШаблон("Заказ %1 на сумму %2 руб.",
ВыборкаЗаказы.Ссылка,
Формат(ВыборкаЗаказы.СуммаДокумента, "ЧЦ=15; ЧДЦ=2; ЧГ=3"));
МассивТекста.Добавить(СтрокаИнфо);
КонецЦикла;
ИтоговыйТекст = СтрСоединить(МассивТекста, Символы.ПС);
Чтобы письмо выглядело презентабельно, рекомендуем использовать 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>";
Использование таких методов, как ИТОГИ ПО в запросах и СтрСоединить для сборки строк, позволяет создавать надежные и быстрые механизмы рассылки, которые не спамят в почтовые ящики сотрудников и предоставляют информацию в удобном структурированном виде.