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