Как создать внешнюю обработку в 1С для оповещения руководителя о крупных заказах по электронной почте?

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

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

Мы рассмотрим ключевые аспекты: от правильного формирования запросов и работы с датами до отправки табличных документов по email, сохранения пользовательских настроек и корректной обработки ошибок. Наша цель – создать надежный и удобный инструмент для конфигурации 1С:Управление торговлей 8, редакция 11.

1. Корректное формирование запроса для выборки крупных заказов

Центральным элементом нашей обработки является запрос к базе данных, который выбирает документы «Заказ клиента», соответствующие заданным условиям по периоду и сумме. Рассмотрим, как правильно построить такой запрос, чтобы избежать ошибок и получить точные данные.

1.1. Определение параметров периода в запросе

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

В тексте запроса нам необходимо использовать два отдельных параметра для начала и конца периода. Это позволит нам гибко управлять диапазоном выборки. Используйте оператор МЕЖДУ для определения диапазона дат.


"ВЫБРАТЬ
|   ЗаказКлиента.Номер КАК Номер,
|   ЗаказКлиента.Дата КАК Дата,
|   ЗаказКлиента.Партнер.Наименование КАК Клиент, // Используем Наименование партнера
|   ЗаказКлиента.СуммаДокумента КАК СуммаДокумента,
|   ЗаказКлиента.Менеджер КАК Менеджер
|ИЗ
|   Документ.ЗаказКлиента КАК ЗаказКлиента
|ГДЕ
|   ЗаказКлиента.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
|   И ЗаказКлиента.СуммаДокумента > &СуммаДокумента
|УПОРЯДОЧИТЬ ПО
|   Дата, Номер"

1.2. Установка параметров запроса для периода и суммы

После того как текст запроса определен, нам необходимо правильно установить его параметры. Если на форме обработки используется реквизит с типом СтандартныйПериод, мы можем извлечь из него ДатаНачала и ДатаОкончания. Для того чтобы обеспечить корректную выборку данных на начало и конец указанного дня с учетом времени, применяем функции НачалоДня() и КонецДня().

Для поля «Сумма», которое пользователь указывает на форме, мы просто передаем его значение.


// Предполагаем, что на форме есть реквизиты:
// ПериодВыборки (тип СтандартныйПериод)
// СуммаОтбора (тип Число)

Запрос = Новый Запрос;
Запрос.Текст = "
    ВЫБРАТЬ
    |   ЗаказКлиента.Номер КАК Номер,
    |   ЗаказКлиента.Дата КАК Дата,
    |   ЗаказКлиента.Партнер.Наименование КАК Клиент,
    |   ЗаказКлиента.СуммаДокумента КАК СуммаДокумента,
    |   ЗаказКлиента.Менеджер КАК Менеджер
    |ИЗ
    |   Документ.ЗаказКлиента КАК ЗаказКлиента
    |ГДЕ
    |   ЗаказКлиента.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
    |   И ЗаказКлиента.СуммаДокумента >= &СуммаОтбора
    |УПОРЯДОЧИТЬ ПО
    |   Дата, Номер";

// Устанавливаем параметры запроса
Запрос.УстановитьПараметр("ДатаНачала", НачалоДня(ПериодВыборки.ДатаНачала));
Запрос.УстановитьПараметр("ДатаОкончания", КонецДня(ПериодВыборки.ДатаОкончания));
Запрос.УстановитьПараметр("СуммаОтбора", СуммаОтбора);

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

Обратите внимание, что в запросе использовано ЗаказКлиента.СуммаДокумента >= &СуммаОтбора, чтобы включить заказы, сумма которых равна указанному значению, если это соответствует требованиям "свыше которой заказы являются интересными" (в примере письма "от 100 000 руб.").

2. Формирование табличного документа и его отправка по электронной почте

Теперь, когда у нас есть выборка данных, нам необходимо сформировать из них табличный документ и отправить его по электронной почте. Это включает создание макета, заполнение его данными, сохранение во временный файл и прикрепление к письму.

2.1. Создание макета и заполнение его данными

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

  1. Создайте новый макет для внешней обработки (например, "МакетОтчета").
  2. Определите в макете области:
    • Заголовок: для вступительного текста письма (например, "Здравствуйте, Иван Иванович!").
    • ШапкаТаблицы: для заголовков столбцов ("№", "Дата", "Клиент", "Сумма", "Менеджер").
    • Детали: для вывода каждой строки данных из запроса. В этой области вы указываете параметры, соответствующие полям выборки запроса (например, [Номер], [Дата], [Клиент], [СуммаДокумента], [Менеджер]).
    • ПодвалТаблицы: для итоговых данных (например, "Всего X заказов на сумму Y руб.").
    • Подвал: для завершающего текста.

Код для заполнения табличного документа будет выглядеть следующим образом:


Макет = ВнешниеОбработки.ПолучитьМакет("МакетОтчета"); // Получаем наш макет

ТабДок = Новый ТабличныйДокумент;
ТабДок.Очистить();

// Выводим заголовок письма
ОбластьЗаголовок = Макет.ПолучитьОбласть("Заголовок");
ОбластьЗаголовок.Параметры.ТекстЗаголовка = "Здравствуйте, Иван Иванович!"; // Пример параметра
ОбластьЗаголовок.Параметры.ПериодТекст = "за период с " + Формат(ПериодВыборки.ДатаНачала, "ДФ=dd.MM.yyyy") + " по " + Формат(ПериодВыборки.ДатаОкончания, "ДФ=dd.MM.yyyy");
ОбластьЗаголовок.Параметры.СуммаОтбораТекст = "от " + Формат(СуммаОтбора, "ЧЦ=10; ЧДЦ=2") + " руб.";

ТабДок.Вывести(ОбластьЗаголовок);

// Выводим шапку таблицы
ОбластьШапкаТаблицы = Макет.ПолучитьОбласть("ШапкаТаблицы");
ТабДок.Вывести(ОбластьШапкаТаблицы);

// Выводим детальные записи
ОбластьДетальныхЗаписей = Макет.ПолучитьОбласть("Детали");
СуммаВсего = 0;
КоличествоЗаказов = 0;

Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
    ОбластьДетальныхЗаписей.Параметры.Заполнить(ВыборкаДетальныеЗаписи);
    ОбластьДетальныхЗаписей.Параметры.Клиент = СокрЛП(ВыборкаДетальныеЗаписи.Клиент); // Пример обработки поля
    ОбластьДетальныхЗаписей.Параметры.СуммаДокумента = Формат(ВыборкаДетальныеЗаписи.СуммаДокумента, "ЧЦ=10; ЧДЦ=2");
    ТабДок.Вывести(ОбластьДетальныхЗаписей);
    СуммаВсего = СуммаВсего + ВыборкаДетальныеЗаписи.СуммаДокумента;
    КоличествоЗаказов = КоличествоЗаказов + 1;
КонецЦикла;

// Выводим подвал таблицы с итоговыми данными
ОбластьПодвалТаблицы = Макет.ПолучитьОбласть("ПодвалТаблицы");
ОбластьПодвалТаблицы.Параметры.КоличествоЗаказов = КоличествоЗаказов;
ОбластьПодвалТаблицы.Параметры.СуммаВсего = Формат(СуммаВсего, "ЧЦ=10; ЧДЦ=2");
ОбластьПодвалТаблицы.Параметры.СуммаПрописью = ЧислоПрописью(СуммаВсего, "рубль", "ru_RU");
ТабДок.Вывести(ОбластьПодвалТаблицы);

// Выводим общий подвал
ОбластьПодвал = Макет.ПолучитьОбласть("Подвал");
ТабДок.Вывести(ОбластьПодвал);

2.2. Сохранение табличного документа во временный файл

Для того чтобы прикрепить табличный документ к электронному письму, его необходимо сначала сохранить в файл. Мы можем выбрать различные форматы, например, PDF, HTML, XLS или XLSX. Выбор формата зависит от того, как руководитель предпочитает просматривать информацию.

Рассмотрим пример сохранения в формате HTML, который часто хорошо отображается непосредственно в теле письма, или PDF для универсальности.


// Создаем временное имя файла
ИмяВременногоФайлаHTML = ПолучитьИмяВременногоФайла(".html");
ИмяВременногоФайлаPDF = ПолучитьИмяВременногоФайла(".pdf");

// Сохраняем табличный документ
Попытка
    ТабДок.Записать(ИмяВременногоФайлаHTML, ТипТабличногоДокумента.HTML);
    // Или в PDF:
    // ТабДок.Записать(ИмяВременногоФайлаPDF, ТипТабличногоДокумента.PDF);
Исключение
    Сообщить("Ошибка при сохранении табличного документа: " + ОписаниеОшибки());
    Возврат;
КонецПопытки;

2.3. Отправка электронного письма с вложением

После сохранения табличного документа в файл, мы можем приступить к отправке электронного письма по протоколу SMTP. Для этого нам потребуются объекты ИнтернетПочтовыйПрофиль и ИнтернетПочта.

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

В качестве темы письма и текста мы используем динамически формируемые строки, включающие период и сумму отбора.


// Формируем тему письма
ТемаПисьма = "Крупные заказы за период с " + Формат(ПериодВыборки.ДатаНачала, "ДФ=dd.MM.yyyy") + " по " + Формат(ПериодВыборки.ДатаОкончания, "ДФ=dd.MM.yyyy");

// Настраиваем почтовый профиль
Профиль = Новый ИнтернетПочтовыйПрофиль;
Профиль.АдресСервераSMTP = "smtp.yandex.ru"; // или другой SMTP-сервер
Профиль.ПортSMTP = 587; // Для SSL/TLS обычно 465 или 587
Профиль.ИспользоватьSSL = Истина; // Рекомендуется для безопасности
Профиль.Пользователь = "ваш_логин@yandex.ru"; // Ваш почтовый ящик
Профиль.Пароль = "ваш_пароль"; // Пароль от почтового ящика
Профиль.АутентификацияSMTP = СпособSMTPАутентификации.Login;
Профиль.ПользовательSMTP = Профиль.Пользователь;
Профиль.ПарольSMTP = Профиль.Пароль;

Почта = Новый ИнтернетПочта;
Попытка
    Почта.Подключиться(Профиль);
Исключение
    Сообщить("Ошибка при подключении к почтовому серверу: " + ОписаниеОшибки(), СтатусСообщения.Важное);
    Возврат;
КонецПопытки;

Сообщение = Новый ИнтернетПочтовоеСообщение;
Сообщение.Отправитель = "ваш_логин@yandex.ru";
Сообщение.Получатели.Добавить("rukovoditel@firma.ru"); // Адрес руководителя
Сообщение.Тема = ТемаПисьма;
Сообщение.Тексты.Добавить("Текст письма смотрите во вложении.", ТипТекстаПочтовогоСообщения.Простой);

// Прикрепляем файл с табличным документом
Если ФайлСуществует(ИмяВременногоФайлаHTML) Тогда
    Сообщение.Вложения.Добавить(ИмяВременногоФайлаHTML, "КрупныеЗаказы.html");
    // Или, если сохранили в PDF:
    // Сообщение.Вложения.Добавить(ИмяВременногоФайлаPDF, "КрупныеЗаказы.pdf");
КонецЕсли;

Попытка
    Почта.Послать(Сообщение);
    Сообщить("Письмо успешно отправлено.", СтатусСообщения.Информация);
Исключение
    Сообщить("Ошибка при отправке письма: " + ОписаниеОшибки(), СтатусСообщения.Важное);
КонецПопытки;

Почта.Отключиться();

// Удаляем временные файлы
УдалитьФайлы(ИмяВременногоФайлаHTML);
// УдалитьФайлы(ИмяВременногоФайлаPDF);

Важно помнить, что если вы хотите, чтобы табличный документ отображался непосредственно в теле письма, то его лучше сохранять в формате HTML и передавать как основной текст сообщения, а не вложение. Для этого используйте Сообщение.Тексты.Добавить(ПрочитатьТекстФайла(ИмяВременногоФайлаHTML), ТипТекстаПочтовогоСообщения.HTML).

3. Запоминание значения поля «Сумма»

Для удобства пользователя необходимо, чтобы значение поля «Сумма» запоминалось при повторном открытии обработки. Мы можем реализовать это, используя методы СохранитьЗначение() и ВосстановитьЗначение().

Эти методы позволяют сохранять и восстанавливать значения реквизитов формы в пользовательских настройках, привязанных к конкретной обработке.

  1. В обработчике события формы ПриОткрытии восстанавливаем значение.
  2. В обработчике события формы ПриЗакрытии или ПриЗавершенииРаботыСистемы сохраняем текущее значение.

// В модуле формы:

// Процедура "ПриОткрытии"
&НаСервере
Процедура ПриОткрытииНаСервере()
    // Восстанавливаем сохраненное значение суммы при открытии формы
    ЗначениеСуммы = ВосстановитьЗначение("СуммаОтбора");
    Если ЗначениеСуммы <> Неопределено Тогда
        СуммаОтбора = ЗначениеСуммы;
    Иначе
        СуммаОтбора = 100000; // Значение по умолчанию, если ничего не сохранено
    КонецЕсли;
КонецПроцедуры

// Процедура "ПриЗакрытии"
&НаКлиенте
Процедура ПриЗакрытии()
    // Сохраняем текущее значение суммы при закрытии формы
    СохранитьЗначение("СуммаОтбора", СуммаОтбора);
КонецПроцедуры

Не забудьте, что реквизит формы СуммаОтбора должен быть создан с соответствующим типом (Число).

4. Проверка заполнения полей и привязка сообщений об ошибках

Перед отправкой письма обязательно необходимо проверить, что пользователь заполнил все необходимые поля. Если поля не заполнены, мы должны вывести информативное сообщение, привязанное к соответствующему полю формы. Кроме того, поле «Сумма» не может быть меньше 100 000 руб., и это тоже следует контролировать.

4.1. Проверка заполнения и вывод сообщений

Для проверки заполнения полей мы можем использовать метод Сообщить(), передавая вторым параметром имя элемента формы, к которому нужно привязать сообщение. Это обеспечит визуальное выделение незаполненного поля для пользователя.

Также, для реквизитов формы можно установить свойство ПроверкаЗаполнения в значение ВыдаватьОшибку, что обеспечит автоматическую проверку платформой.


// В обработчике кнопки "Отправить"
&НаКлиенте
Процедура ОтправитьНажатие(Команда)
    Если Не ПроверитьЗаполнениеПолей() Тогда
        Возврат;
    КонецЕсли;
    
    // Выполняем действия по отправке письма на сервере
    ОтправитьПисьмоНаСервере();
КонецПроцедуры

&НаКлиенте
Функция ПроверитьЗаполнениеПолей()
    РезультатПроверки = Истина;

    // Проверка поля "Период"
    Если ПериодВыборки.ДатаНачала = Дата(1,1,1) И ПериодВыборки.ДатаОкончания = Дата(1,1,1) Тогда
        Сообщить("Поле 'Период' не заполнено!", СтатусСообщения.Важное, "ПериодВыборки"); // "ПериодВыборки" - имя элемента формы
        РезультатПроверки = Ложь;
    КонецЕсли;

    // Проверка поля "Сумма"
    Если СуммаОтбора = 0 Тогда // или любое другое значение, означающее незаполненность
        Сообщить("Поле 'Сумма' не заполнено!", СтатусСообщения.Важное, "СуммаОтбора"); // "СуммаОтбора" - имя элемента формы
        РезультатПроверки = Ложь;
    КонецЕсли;
    
    // Проверка значения суммы не менее 100 000 руб.
    Если СуммаОтбора < 100000 Тогда
        Сообщить("Поле 'Сумма' не может быть меньше 100 000 руб.", СтатусСообщения.Важное, "СуммаОтбора");
        РезультатПроверки = Ложь;
    КонецЕсли;

    Возврат РезультатПроверки;
КонецФункции

&НаСервере
Процедура ОтправитьПисьмоНаСервере()
    // Здесь размещаем весь код по формированию запроса,
    // формированию табличного документа и отправке письма,
    // который мы рассмотрели выше.
    // ...
КонецПроцедуры

4.2. Запрет ввода значения менее 100 000 руб.

Для поля «Сумма» мы можем также настроить ограничение на ввод прямо в свойствах элемента формы, используя минимальное значение. Однако более надежным и интерактивным будет использование обработчика события ПриИзменении для этого поля.


// В модуле формы, для элемента формы "СуммаОтбора":

&НаКлиенте
Процедура СуммаОтбораПриИзменении(Элемент)
    Если СуммаОтбора < 100000 Тогда
        Сообщить("Сумма не может быть меньше 100 000 руб. Установлено минимальное значение.", СтатусСообщения.Важное, Элемент);
        СуммаОтбора = 100000;
    КонецЕсли;
КонецПроцедуры

Таким образом, мы не только проинформируем пользователя об ошибке, но и автоматически скорректируем значение, если оно не соответствует условию.

5. Распределение программного кода по модулям

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

5.1. Модуль формы

Модуль формы предназначен для всего, что непосредственно связано с пользовательским интерфейсом и взаимодействием с ним. Здесь размещаем:

  1. Обработчики событий элементов формы (кнопки, поля ввода, таблицы).
  2. Логику клиентской проверки данных, как мы видели в ПроверитьЗаполнениеПолей().
  3. Код, управляющий видимостью, доступностью или другими свойствами элементов формы.
  4. Вызовы серверных процедур из модуля объекта или серверных процедур формы для выполнения основной бизнес-логики.
  5. Код для сохранения/восстановления значений реквизитов формы, использующий СохранитьЗначение() и ВосстановитьЗначение().

Пример из нашей задачи:

5.2. Модуль объекта

Модуль объекта — это место для размещения основной бизнес-логики, которая не зависит от конкретной формы представления. Все, что связано с работой с данными, выполнением сложных расчетов, взаимодействием с другими объектами метаданных или внешними системами (такими как почтовый сервер), должно находиться здесь.

Основные задачи модуля объекта:

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

Пример из нашей задачи:

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


// Пример взаимодействия между модулем формы и модулем объекта
// в упрощенном виде

// --- В модуле формы ---

&НаКлиенте
Процедура ОтправитьНажатие(Команда)
    Если Не ПроверитьЗаполнениеПолей() Тогда
        Возврат;
    КонецЕсли;
    
    // Вызываем серверную процедуру, которая может использовать методы модуля объекта
    ОтправитьОтчетРуководителюНаСервере(ПериодВыборки, СуммаОтбора);
КонецПроцедуры

&НаСервере
Процедура ОтправитьОтчетРуководителюНаСервере(Период, Сумма)
    // Здесь мы можем вызывать функции из модуля объекта обработки
    ЭтаНаСервере.ОтправитьОтчетРуководителю(Период, Сумма);
КонецПроцедуры


// --- В модуле объекта (внутри объекта внешней обработки) ---

Процедура ОтправитьОтчетРуководителю(Период, Сумма) Экспорт
    // 1. Формирование запроса и получение данных
    Запрос = Новый Запрос;
    Запрос.Текст = "..." // Текст запроса
    Запрос.УстановитьПараметр("ДатаНачала", НачалоДня(Период.ДатаНачала));
    Запрос.УстановитьПараметр("ДатаОкончания", КонецДня(Период.ДатаОкончания));
    Запрос.УстановитьПараметр("СуммаОтбора", Сумма);
    РезультатЗапроса = Запрос.Выполнить();
    Выборка = РезультатЗапроса.Выбрать();

    // 2. Формирование ТабличногоДокумента
    ТабДок = СформироватьТабличныйДокументОтчета(Выборка, Период, Сумма); // Вызываем вспомогательную функцию из этого же модуля объекта

    // 3. Сохранение ТабличногоДокумента в файл
    ИмяФайлаОтчета = ПолучитьИмяВременногоФайла(".html");
    ТабДок.Записать(ИмяФайлаОтчета, ТипТабличногоДокумента.HTML);

    // 4. Отправка письма
    ОтправитьEmailСОтчетом(ИмяФайлаОтчета, Период, Сумма); // Вызываем вспомогательную процедуру

    // 5. Удаление временного файла
    УдалитьФайлы(ИмяФайлаОтчета);
КонецПроцедуры

// Вспомогательная функция для формирования ТабличногоДокумента
Функция СформироватьТабличныйДокументОтчета(Выборка, Период, Сумма)
    // ... логика формирования ТабДок из пункта 2.1 ...
    // Возвращает готовый ТабличныйДокумент
    Возврат ТабДок;
КонецФункции

// Вспомогательная процедура для отправки Email
Процедура ОтправитьEmailСОтчетом(ПутьКФайлуОтчета, Период, Сумма)
    // ... логика отправки Email из пункта 2.3 ...
    // Использует ПутьКФайлуОтчета для вложения
КонецПроцедуры

6. Дополнительные возможности и лучшие практики для 1С:УТ 11

При работе с 1С:Управление торговлей 8, редакция 11, всегда полезно знать о встроенном функционале, который может упростить или даже полностью заменить часть нашей разработки. Конфигурация УТ 11 предоставляет мощные инструменты для работы с отчетами и их рассылками.

  1. Встроенный функционал рассылок отчетов: В УТ 11 существует механизм «Настройки рассылок отчетов». Он позволяет создавать регламентные задания для автоматической отправки любых отчетов по электронной почте по заданному расписанию. Для этого мы могли бы создать внешний отчет на Системе Компоновки Данных (СКД), который бы выдавал требуемый результат, а затем настроить его рассылку через стандартные средства. Это является более предпочтительным решением в реальных проектах, так как обеспечивает гибкость настройки без изменения кода.
  2. Создание внешнего отчета на СКД: Вместо обработки, формирующей ТабличныйДокумент вручную, мы могли бы создать внешний отчет на СКД. СКД предоставляет гибкие возможности для формирования отчетов, включая группировки, отборы, вывод итогов и даже форматирование. Результат такого отчета также можно сохранить в файл и отправить.
  3. Использование общих модулей: Для повторно используемой логики (например, отправки писем) можно создать общий модуль, который будет содержать соответствующие экспортные процедуры и функции. Это позволит использовать их не только в данной обработке, но и в других частях конфигурации или внешних разработках.

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

← На главную