Разработка механизмов автоматического оповещения — одна из самых частых задач, с которыми сталкиваются начинающие программисты 1С. Однако лаконичные формулировки технических заданий нередко вызывают ступор. Разработчики задаются вопросами: нужно ли выводить таблицу на форму внешней обработки? Как правильно применить отборы? Каким образом сформировать и отправить саму таблицу?
В этой статье мы подробно разберем классическую задачу: создание внешней обработки для автоматической отправки по электронной почте таблицы с данными оформленных документов «Заказ клиента», удовлетворяющих заданным условиям отбора. Мы декомпозируем требования, спроектируем правильную архитектуру взаимодействия клиента и сервера, а также напишем весь необходимый код на языке 1С.
Когда мы сталкиваемся с нечеткими требованиями заказчика или аналитика, первым делом необходимо выполнить смысловую декомпозицию текста. Давайте разобьем исходную задачу на простые логические составляющие:
Выяснив суть, перейдем к пошаговому проектированию нашей обработки.
Для реализации отборов нам понадобятся параметры на форме внешней обработки. Создадим форму и добавим на нее два ключевых реквизита:
ПериодОтбора (тип: СтандартныйПериод) — для фильтрации документов по дате оформления.МинимальнаяСумма (тип: Число, длина 15, точность 2) — пороговое значение, начиная с которого заказ считается «интересным» для руководства.Разместим эти реквизиты на форме. Чтобы пользователю не приходилось при каждом запуске обработки вводить параметры заново, настроим их автоматическое запоминание. Для этого в свойствах формы установим флаг «Автоматическое сохранение данных в настройках», а в свойствах самих реквизитов ПериодОтбора и МинимальнаяСумма взведем флаг «Сохранение».
Перед тем как нагружать сервер запросами и отправкой почты, необходимо удостовериться, что пользователь заполнил все обязательные поля на форме. Выполнять эту проверку нужно на клиенте. В случае ошибки мы должны не просто показать текстовое окно, а привязать сообщение об ошибке к конкретному незаполненному полю на форме с помощью объекта СообщениеПользователю.
Посмотрим на пример реализации процедуры валидации на клиенте:
&НаКлиенте
Функция ПроверитьЗаполнениеПолейНаФорме() Экспорт
ВсегоПройдено = Истина;
// Проверяем заполнение периода
Если Не ЗначениеЗаполнено(Объект.ПериодОтбора.ДатаНачала) Или Не ЗначениеЗаполнено(Объект.ПериодОтбора.ДатаОкончания) Тогда
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Необходимо заполнить период отбора заказов!";
Сообщение.Поле = "Объект.ПериодОтбора";
Сообщение.УстановитьДанные(Объект);
Сообщение.Сообщить();
ВсегоПройдено = Ложь;
КонецЕсли;
// Проверяем заполнение суммы
Если Объект.МинимальнаяСумма <= 0 Тогда
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Порог суммы должен быть больше нуля!";
Сообщение.Поле = "Объект.МинимальнаяСумма";
Сообщение.УстановитьДанные(Объект);
Сообщение.Сообщить();
ВсегоПройдено = Ложь;
КонецЕсли;
Возврат ВсегоПройдено;
КонецФункции
Переходим к серверной части разработки. Нам необходимо написать оптимальный запрос к таблице документов Документ.ЗаказКлиента. Мы выберем только оформленные (проведенные) заказы за указанный период, сумма которых больше или равна установленному порогу.
Проанализируем структуру запроса и получение необходимых полей (номер, дата, клиент, сумма, валюта, менеджер):
&НаСервереБезКонтекста
Функция ПолучитьТаблицуЗаказов(ДатаНачала, ДатаОкончания, ПорогСуммы)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЗаказКлиента.Ссылка КАК Ссылка,
| ЗаказКлиента.Номер КАК Номер,
| ЗаказКлиента.Дата КАК Дата,
| ЗаказКлиента.Контрагент.Наименование КАК Клиент,
| ЗаказКлиента.СуммаДокумента КАК Сумма,
| ЗаказКлиента.Валюта.Символ КАК ВалютаСимвол,
| ЗаказКлиента.Менеджер.Наименование КАК МенеджерФИО
|ИЗ
| Документ.ЗаказКлиента КАК ЗаказКлиента
|ГДЕ
| ЗаказКлиента.Проведен = ИСТИНА
| И ЗаказКлиента.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
| И ЗаказКлиента.СуммаДокумента >= &ПорогСуммы
|УПОРЯДОЧИТЬ ПО
| Дата УБЫВ";
Запрос.УстановитьПараметр("ДатаНачала", НачалоДня(ДатаНачала));
// Конец дня используем для захвата документов, созданных в последнюю секунду периода
Запрос.УстановитьПараметр("ДатаОкончания", КонецДня(ДатаОкончания));
Запрос.УстановитьПараметр("ПорогСуммы", ПорогСуммы);
Возврат Запрос.Выполнить().Выгрузить();
КонецФункции
Чтобы письмо выглядело презентабельно и открывалось в любых почтовых клиентах (Outlook, Thunderbird, веб-интерфейсы Яндекс.Почты или Mail.ru), мы сформируем тело письма в формате HTML. Использование инлайновых стилей (inline CSS) гарантирует правильное отображение сетки таблицы.
Разберем по шагам процесс генерации HTML-кода, включая вывод итоговой строки с суммой прописью:
&НаСервереБезКонтекста
Функция СформироватьHTMLТелоПисьма(ТаблицаДанных, ДатаНачала, ДатаОкончания)
// Форматируем даты для заголовка
СтрДатаНачала = Формат(ДатаНачала, "ДФ=dd.MM.yyyy");
СтрДатаОкончания = Формат(ДатаОкончания, "ДФ=dd.MM.yyyy");
// Начало HTML-документа и стили
HTML = "<html><head><style>
|body { font-family: Arial, sans-serif; font-size: 14px; color: #333333; }
|table { border-collapse: collapse; width: 100%; margin-top: 15px; }
|th { background-color: #f2f2f2; padding: 8px; border: 1px solid #dddddd; text-align: left; }
|td { padding: 8px; border: 1px solid #dddddd; }
|tr:nth-child(even) { background-color: #fafafa; }
|.summary { font-weight: bold; margin-top: 15px; background-color: #eaf2ff; padding: 10px; border: 1px solid #b3d4ff; }
|</style></head><body>";
HTML = HTML + "<h2>Уведомление о крупных заказах</h2>";
HTML = HTML + "<p>Приветствуем вас! Ниже представлена сводная таблица по оформленным заказам клиентов за период с <strong>" + СтрДатаНачала + "</strong> по <strong>" + СтрДатаОкончания + "</strong>.</p>";
// Формируем шапку таблицы
HTML = HTML + "<table>";
HTML = HTML + "<tr>
|<th>№</th>
|<th>Дата</th>
|<th>Клиент</th>
|<th>Сумма</th>
|<th>Менеджер</th>
|</tr>";
ВсегоСумма = 0;
КоличествоЗаказов = ТаблицаДанных.Количество();
// Заполняем строки данными
Для Каждого Строка Из ТаблицаДанных Цикл
ВсегоСумма = ВсегоСумма + Строка.Сумма;
СтрДатаДок = Формат(Строка.Дата, "ДФ=dd.MM.yyyy HH:mm");
СтрСуммаДок = Формат(Строка.Сумма, "ЧДЦ=2; ЧГ=3") + " " + Строка.ВалютаСимвол;
HTML = HTML + "<tr>";
HTML = HTML + "<td>" + Строка.Номер + "</td>";
HTML = HTML + "<td>" + СтрДатаДок + "</td>";
HTML = HTML + "<td>" + Строка.Клиент + "</td>";
HTML = HTML + "<td>" + СтрСуммаДок + "</td>";
HTML = HTML + "<td>" + Строка.МенеджерФИО + "</td>";
HTML = HTML + "</tr>";
КонецЦикла;
HTML = HTML + "</table>";
// Формируем сумму прописью
ПараметрыПрописи = "L=ru_RU; ДП=Истина";
СуммаПрописью = ЧислоПрописью(ВсегоСумма, ПараметрыПрописи, "рубль, рубля, рублей, м, копейка, копейки, копеек, ж, 2");
// Формируем подвал с итогами
HTML = HTML + "<div class='summary'>";
HTML = HTML + "Всего " + КоличествоЗаказов + " заказа(ов) на сумму " + Формат(ВсегоСумма, "ЧДЦ=2; ЧГ=3") + " (" + СуммаПрописью + ") руб.";
HTML = HTML + "</div>";
HTML = HTML + "<p style='font-size: 11px; color: #888888; margin-top: 20px;'>Сообщение сформировано автоматически из системы 1С.</p>";
HTML = HTML + "</body></html>";
Возврат HTML;
КонецФункции
Для непосредственной отправки подготовленного HTML-текста воспользуемся встроенными объектами платформы 1С — ИнтернетПочтовыйПрофиль и ИнтернетПочта. Настройки подключения (сервер SMTP, порт, логин и пароль) обычно хранятся в системной учетной записи или константах конфигурации, но в нашем примере для наглядности мы укажем демонстрационные параметры.
Рассмотрим процедуру отправки письма:
&НаСервереБезКонтекста
Процедура ОтправитьПисьмоНаАдрес(Тема, HTMLТело, АдресПолучателя)
Профиль = Новый ИнтернетПочтовыйПрофиль;
// Укажите настройки вашего SMTP-сервера
Профиль.АдресСервераSMTP = "smtp.mail.ru";
Профиль.ПортSMTP = 465;
Профиль.ПользовательSMTP = "robot@mail.ru";
Профиль.ПарольSMTP = "your_secure_password";
Профиль.ИспользоватьSSLSMTP = Истина;
Письмо = Новый ИнтернетПочтовоеСообщение;
Письмо.Тема = Тема;
Письмо.Отправитель.Адрес = "robot@mail.ru";
Письмо.Отправитель.ОтображаемоеИмя = "Уведомления 1С";
// Добавляем получателя
Письмо.Получатели.Добавить(АдресПолучателя);
// Указываем тип текста письма как HTML
Письмо.УстановитьТекст(HTMLТело, ТипТекстаПочтовогоСообщения.HTML);
Почта = Новый ИнтернетПочта;
Попытка
Почта.Подключиться(Профиль);
Почта.Послать(Письмо);
Почта.Отключиться();
Исключение
Почта.Отключиться();
ВызватьИсключение "Ошибка отправки почты: " + ОписаниеОшибки();
КонецПопытки;
КонецПроцедуры
Теперь свяжем все наши шаги в единый процесс обработки нажатия на кнопку «Отправить» на форме. Мы проверим заполнение параметров на клиенте, обратимся к серверу для выборки данных и формирования письма, после чего отправим его получателю.
&НаКлиенте
Процедура Отправить(Команда)
// 1. Валидация
Если Не ПроверитьЗаполнениеПолейНаФорме() Тогда
Возврат;
КонецЕсли;
Состояние("Выполняется сбор данных и отправка сообщения...");
Попытка
ВыполнитьОтправкуНаСервере();
ПоказатьПредупреждение(, "Письмо успешно отправлено руководителю!");
Исключение
ПоказатьПредупреждение(, "Не удалось отправить письмо: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецПроцедуры
&НаСервере
Процедура ВыполнитьОтправкуНаСервере()
// Получаем таблицу подходящих заказов
ТаблицаЗаказов = ПолучитьТаблицуЗаказов(Объект.ПериодОтбора.ДатаНачала, Объект.ПериодОтбора.ДатаОкончания, Объект.МинимальнаяСумма);
Если ТаблицаЗаказов.Количество() = 0 Тогда
ВызватьИсключение "За выбранный период нет заказов, превышающих сумму порога.";
КонецЕсли;
// Формируем динамическое HTML тело письма
HTMLТело = СформироватьHTMLТелоПисьма(ТаблицаЗаказов, Объект.ПериодОтбора.ДатаНачала, Объект.ПериодОтбора.ДатаОкончания);
// Формируем тему письма
СтрДатаНачала = Формат(Объект.ПериодОтбора.ДатаНачала, "ДФ=dd.MM.yyyy");
СтрДатаОкончания = Формат(Объект.ПериодОтбора.ДатаОкончания, "ДФ=dd.MM.yyyy");
ТемаПисьма = "Крупные заказы за период с " + СтрДатаНачала + " по " + СтрДатаОкончания;
// Жестко заданный адрес руководителя (или получаемый из настроек)
АдресРуководителя = "boss@company.ru";
// Отправка почты
ОтправитьПисьмоНаАдрес(ТемаПисьма, HTMLТело, АдресРуководителя);
КонецПроцедуры
Данная архитектурная схема полностью удовлетворяет лучшим практикам разработки в среде 1С:Предприятие 8.3. Клиентская часть занимается интерфейсом и валидацией данных, тогда как сервер берет на себя тяжелую логику работы с СУБД, построение макета письма и отправку по сетевым протоколам.