При изучении платформы 1С:Предприятие начинающие разработчики часто сталкиваются с комплексными задачами, которые требуют одновременно спроектировать удобный пользовательский интерфейс, написать оптимальный запрос к базе данных и настроить интеграцию со сторонними сервисами (например, отправку писем по протоколу SMTP). Разберем по шагам создание внешней обработки на примере реального тестового задания: оповещение руководителя о крупных заказах клиентов за выбранный период.
В этой статье мы подробно рассмотрим, как правильно настроить элементы формы, разделить логику между модулем формы и модулем объекта, составить оптимальный запрос, динамически сформировать красивую HTML-таблицу в теле письма и отправить сообщение адресату.
Начнем с проектирования интерфейса нашей внешней обработки. Нам понадобятся два ключевых параметра, которые будет вводить пользователь: период анализа данных и минимальная пороговая сумма заказа.
Выясним, какие типы данных и настройки свойств лучше всего использовать для решения этой задачи:
Дата (для начала и окончания периода) мы создадим один реквизит с типом СтандартныйПериод. Это позволит пользователю использовать удобное стандартное диалоговое окно для выбора периода (например, "Текущий месяц", "Прошлая неделя" и т.д.).Имя и Заголовок. Свойство Имя используется для обращения к элементу из программного кода, поэтому оно должно быть написано в одно слово, без пробелов, точек и спецсимволов. Свойство Заголовок — это то, что видит пользователь на экране. В поле Заголовок можно писать любой понятный текст, включая скобки, знаки препинания и пробелы.СохраняемыеДанные для конкретного реквизита). В списке сохраняемых параметров достаточно отметить галочкой реквизит Сумма. Теперь платформа автоматически запишет последнее введенное число в системное хранилище и подставит его при следующем запуске.Правильная архитектура — залог стабильной работы конфигурации и успешного прохождения любого собеседования. Нам нужно четко разграничить ответственность между модулем формы и модулем объекта нашей обработки.
Модуль формы отвечает исключительно за интерактивную работу с пользователем на клиенте. В нем мы выполняем валидацию данных перед отправкой. Если обязательные поля (период или сумма) не заполнены, мы прерываем операцию и выводим пользователю адресное предупреждение.
Проанализируем пример кода проверки заполнения полей на форме с привязкой ошибки к конкретному полю ввода с помощью объекта СообщениеПользователю:
&НаКлиенте
Процедура ОтправитьПисьмо(Команда)
// Сбросим возможные старые сообщения об ошибках
ОчиститьСообщения();
Отказ = Ложь;
// Проверяем заполнение периода
Если НЕ ЗначениеЗаполнено(Объект.Период.ДатаНачала) Или НЕ ЗначениеЗаполнено(Объект.Период.ДатаОкончания) Тогда
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Поле ""Период"" должно быть заполнено!";
Сообщение.Поле = "Объект.Период";
Сообщение.УстановитьДанные(Объект);
Сообщение.Сообщить();
Отказ = Истина;
КонецЕсли;
// Проверяем заполнение и корректность суммы
Если НЕ ЗначениеЗаполнено(Объект.Сумма) Тогда
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Поле ""Сумма"" должно быть заполнено!";
Сообщение.Поле = "Объект.Сумма";
Сообщение.УстановитьДанные(Объект);
Сообщение.Сообщить();
Отказ = Истина;
ИначеЕсли Объект.Сумма < 100000 Тогда
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Сумма не может быть меньше 100 000 рублей!";
Сообщение.Поле = "Объект.Сумма";
Сообщение.УстановитьДанные(Объект);
Сообщение.Сообщить();
Отказ = Истина;
КонецЕсли;
Если Отказ Тогда
Возврат;
КонецЕсли;
// Если все проверки пройдены, передаем управление серверному методу
ОтправитьПисьмоНаСервере();
КонецПроцедуры
Модуль объекта предназначен для выполнения бизнес-логики на сервере. Форма не должна напрямую отправлять почту или строить сложные SQL-запросы. Вместо этого из серверной процедуры формы мы конвертируем реквизит формы во временный прикладной объект в памяти и вызываем его экспортный метод:
&НаСервере
Процедура ОтправитьПисьмоНаСервере()
// Получаем прикладной объект обработки из данных формы
ОбработкаОбъект = РеквизитФормыВЗначение("Объект");
// Вызываем экспортную процедуру модуля объекта, передавая параметры
ОбработкаОбъект.ВыполнитьРассылку(Объект.Период.ДатаНачала, Объект.Период.ДатаОкончания, Объект.Сумма);
КонецПроцедуры
Рассмотрим подробнее получение данных о заказах. Часто начинающие программисты делают прямой запрос к таблице документов Документ.ЗаказКлиента. В учебных целях или в простых базах это сработает, но в реальных крупных информационных системах такой подход считается неоптимальным, так как обращение к таблицам документов напрямую может замедлять работу базы данных.
Правильным решением является обращение к виртуальным таблицам соответствующих регистров накопления (например, РегистрНакопления.ЗаказыКлиентов или оборотным регистрам продаж). Однако, если структура метаданных вашей демонстрационной конфигурации требует получения реквизитов, которых нет в регистре (например, ссылка на Менеджера документа или Партнера), то запрос к документам допустим. Важно обязательно наложить фильтр по дате и пометке удаления (проведению документа).
Посмотрим на пример построения безопасного и быстрого запроса с использованием параметров:
Функция ПолучитьТаблицуЗаказов(ДатаНачала, ДатаОкончания, МинимальнаяСумма) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Заказ.Номер КАК Номер,
| Заказ.Дата КАК Дата,
| Заказ.Партнер.Наименование КАК Клиент,
| Заказ.СуммаДокумента КАК Сумма,
| Заказ.Менеджер.Наименование КАК Менеджер
|ИЗ
| Документ.ЗаказКлиента КАК Заказ
|ГДЕ
| Заказ.Проведен = ИСТИНА
| И Заказ.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
| И Заказ.СуммаДокумента >= &МинимальнаяСумма
|УПОРЯДОЧИТЬ ПО
| Заказ.Дата УБЫВ";
Запрос.УстановитьПараметр("ДатаНачала", НачалоДня(ДатаНачала));
// Устанавливаем границу окончания дня, чтобы захватить заказы, созданные в последнюю секунду периода
Запрос.УстановитьПараметр("ДатаОкончания", КонецДня(ДатаОкончания));
Запрос.УстановитьПараметр("МинимальнаяСумма", МинимальнаяСумма);
Возврат Запрос.Выполнить().Выгрузить();
КонецФункции
Для того чтобы руководитель получил красивое структурированное письмо, мы должны сформировать его тело в формате HTML-кода. Нам потребуется программно собрать HTML-таблицу, подсчитать количество подходящих документов, вычислить их общую сумму и перевести её в текстовый вид.
Для перевода итоговой суммы в текстовое представление воспользуемся стандартной мощной функцией платформы 1С ЧислоПрописью(). Проанализируем реализацию этой логики:
Функция СформироватьТелоПисьма(ТаблицаДанных, ДатаНачала, ДатаОкончания, МинимальнаяСумма) Экспорт
// Начало разметки документа
HTML = "<html><body style='font-family: Arial, sans-serif;'>";
HTML = HTML + "<p>Здравствуйте, Иван Иванович!</p>";
HTML = HTML + "<p>Список заказов с суммой от <strong>" + Формат(МинимальнаяСумма, "ЧДЦ=2; ЧГ=3") + "</strong> руб. за период с "
+ Формат(ДатаНачала, "ДФ=dd.MM.yyyy") + " по " + Формат(ДатаОкончания, "ДФ=dd.MM.yyyy") + ":</p>";
// Создаем HTML-таблицу с рамкой и стилями
HTML = HTML + "<table border='1' cellpadding='5' style='border-collapse: collapse; width: 100%; border-color: #cccccc;'>";
HTML = HTML + "<tr bgcolor='#f2f2f2'>"
+ "<th><strong>№</strong></th>"
+ "<th><strong>Дата</strong></th>"
+ "<th><strong>Клиент</strong></th>"
+ "<th><strong>Сумма</strong></th>"
+ "<th><strong>Менеджер</strong></th>"
+ "</tr>";
ВсегоСумма = 0;
КоличествоЗаказов = ТаблицаДанных.Количество();
Для Каждого Строка Из ТаблицаДанных Цикл
HTML = HTML + "<tr>"
+ "<td align='center'>" + Строка.Номер + "</td>"
+ "<td align='center'>" + Формат(Строка.Дата, "ДФ=dd.MM.yyyy") + "</td>"
+ "<td>" + Строка.Клиент + "</td>"
+ "<td align='right'>" + Формат(Строка.Сумма, "ЧДЦ=2; ЧГ=3") + "</td>"
+ "<td>" + Строка.Менеджер + "</td>"
+ "</tr>";
ВсегоСумма = ВсегоСумма + Строка.Сумма;
КонецЦикла;
HTML = HTML + "</table>";
// Форматируем сумму прописью по правилам русского языка
ПараметрыПрописи = "L=ru_RU; ДП=Истина";
СуммаПрописью = ЧислоПрописью(ВсегоСумма, ПараметрыПрописи, "рубль, рубля, рублей, м, копейка, копейки, копеек, ж, 2");
HTML = HTML + "<p>Всего " + КоличествоЗаказов + " заказа(ов) на сумму "
+ Формат(ВсегоСумма, "ЧДЦ=2; ЧГ=3") + " (" + СуммаПрописью + ") руб.</p>";
HTML = HTML + "</body></html>";
Возврат HTML;
КонецФункции
Теперь объединим все шаги в общую бизнес-логику в модуле объекта и выполним отправку сообщения на сервер. Нам понадобятся объекты ИнтернетПочтовыйПрофиль для настройки параметров соединения и ИнтернетПочта для непосредственного взаимодействия с SMTP-сервером.
Важное предупреждение по безопасности: Современные почтовые сервисы (Yandex, Mail.ru, Gmail) требуют использования SSL-соединения и специальных безопасных портов. Кроме того, обычные пароли от почтовых ящиков в коде использовать больше нельзя — предварительно в личном кабинете вашей почты необходимо сгенерировать специальный "Пароль для внешних приложений" (SMTP-пароль).
Ниже представлен завершенный программный код для модуля объекта обработки:
Процедура ВыполнитьРассылку(ДатаНачала, ДатаОкончания, МинимальнаяСумма) Экспорт
// Шаг 1: Получаем данные из базы
ТаблицаЗаказов = ПолучитьТаблицуЗаказов(ДатаНачала, ДатаОкончания, МинимальнаяСумма);
Если ТаблицаЗаказов.Количество() = 0 Тогда
Сообщить("За выбранный период крупных заказов не обнаружено. Письмо не отправлено.");
Возврат;
КонецЕсли;
// Шаг 2: Формируем текст письма в HTML
ТекстHTML = СформироватьТелоПисьма(ТаблицаЗаказов, ДатаНачала, ДатаОкончания, МинимальнаяСумма);
// Шаг 3: Настраиваем защищенное подключение к SMTP
Профиль = Новый ИнтернетПочтовыйПрофиль;
Профиль.АдресСервераSMTP = "smtp.yandex.ru";
Профиль.ИспользоватьSSLSMTP = Истина;
Профиль.ПортSMTP = 465;
// Укажите ваши реальные параметры авторизации
Профиль.ПользовательSMTP = "report-sender@yandex.ru";
Профиль.ПарольSMTP = "xxxx-xxxx-xxxx-xxxx"; // Пароль приложения, сгенерированный в Яндексе
// Шаг 4: Подготавливаем сообщение
Письмо = Новый ИнтернетПочтовоеСообщение;
Письмо.Тема = "Крупные заказы за период с " + Формат(ДатаНачала, "ДФ=dd.MM.yyyy") + " по " + Формат(ДатаОкончания, "ДФ=dd.MM.yyyy");
Письмо.Отправитель = "report-sender@yandex.ru";
// Добавляем получателя (руководителя)
Письмо.Получатели.Добавить("director@mycompany.ru");
// Критически важная деталь: явно указываем тип текста как HTML
Письмо.Тексты.Добавить(ТекстHTML, ТипТекстаПочтовогоСообщения.HTML);
// Шаг 5: Выполняем отправку сообщения
Почта = Новый ИнтернетПочта;
Попытка
Почта.Подключиться(Профиль);
Почта.Послать(Письмо);
Почта.Отключиться();
Сообщить("Отчет о крупных заказах успешно отправлен руководителю.");
Исключение
ВызватьИсключение "Не удалось отправить почту. Описание ошибки: " + ОписаниеОшибки();
КонецПопытки;
КонецПроцедуры
Мы полностью разобрали реализацию сложного прикладного алгоритма во внешней обработке 1С. Созданная архитектура разделения кода гарантирует быструю и отказоустойчивую работу на управляемых формах, защищает от ошибок ручного ввода данных и предоставляет руководителю отчетность в презентабельном и удобном виде.