При разработке бизнес-логики часто возникает задача: уведомить ответственных пользователей по электронной почте, когда реквизит карточки справочника переходит в определенный статус (например, флаг Подтверждено принимает значение Истина). На первый взгляд кажется очевидным просто вставить вызов отправки почты в обработчик события записи. Однако такой подход чреват серьезными проблемами с производительностью и целостностью данных. Разберем, как реализовать этот функционал надежно и архитектурно правильно.
Выясним причину, по которой прямая отправка почтовых сообщений в процедурах ПередЗаписью или ПриЗаписи (как в модуле объекта, так и в подписке на событие) категорически не рекомендуется:
Отказ = Истина в другой подписке, транзакция откатится, и изменения в справочнике сохранены не будут. При этом письмо адресаты уже получат, что приведет к дезинформации.Рассмотрим, как отследить переход флага в состояние Истина именно в момент его установки, исключая повторные рассылки при каждой последующей перезаписи уже подтвержденной карточки. Для этого сравним текущее состояние реквизита в объекте со значением, которое записано в базе данных.
Сделать это можно как в процедуре ПередЗаписью модуля объекта, так и в подписке на событие ПередЗаписью. Подписка предпочтительнее, так как позволяет не снимать типовой объект с поддержки. Посмотрим на пример кода проверки:
// В обработчике подписки на событие ПередЗаписью
Если Источник.Подтверждено Тогда
Если Источник.ЭтоНовый() Тогда
// Новый элемент сразу создается подтвержденным
Источник.ДополнительныеСвойства.Вставить("НеобходимоОтправитьУведомление", Истина);
Иначе
// Проверяем старое значение из базы данных через ссылку
СтароеЗначение = Источник.Ссылка.Подтверждено;
Если Не СтароеЗначение Тогда
Источник.ДополнительныеСвойства.Вставить("НеобходимоОтправитьУведомление", Истина);
КонецЕсли;
КонецЕсли;
КонецЕсли;
Мы используем ДополнительныеСвойства объекта, чтобы передать признак необходимости рассылки дальше по цепочке — в событие фиксации записи.
Разберем наиболее надежный способ интеграции — использование регистра-очереди. Создадим непериодический регистр сведений ОчередьУведомлений со следующей структурой:
ИдентификаторЗаписи (тип УникальныйИдентификатор) или ссылка на объект и дата создания.СсылкаНаОбъект (тип СправочникСсылка...), СтатусОбработки (перечисление: КОтправке, Отправлено, Ошибка), ТекстОшибки (строка), ДатаСоздания (Дата).В подписке на событие ПриЗаписи справочника проверим наличие флага в ДополнительныеСвойства и создадим запись в регистре:
// В подписке на событие ПриЗаписи
Если Источник.ДополнительныеСвойства.Свойство("НеобходимоОтправитьУведомление")
И Источник.ДополнительныеСвойства.НеобходимоОтправитьУведомление Тогда
НаборЗаписей = РегистрыСведений.ОчередьУведомлений.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.ИдентификаторЗаписи.Установить(Новый УникальныйИдентификатор);
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.ИдентификаторЗаписи = Новый УникальныйИдентификатор;
НоваяЗапись.СсылкаНаОбъект = Источник.Ссылка;
НоваяЗапись.ДатаСоздания = ТекущаяДатаСеанса();
НоваяЗапись.СтатусОбработки = Перечисления.СтатусыОчереди.КОтправке;
НаборЗаписей.Записать();
КонецЕсли;
Поскольку запись в регистр сведений происходит внутри транзакции записи справочника, при любой непредвиденной ошибке откатятся сразу оба действия, что гарантирует абсолютную согласованность данных.
Проанализируем процедуру отправки писем, которая будет выполняться через РегламентныеЗадания в фоновом режиме. Такой подход полностью изолирует пользователей от задержек почтовых протоколов.
При отправке почты средствами платформы 1С через объект ИнтернетПочта важно соблюдать правила оптимизации соединений. Не следует подключаться к почтовому серверу заново для каждой отдельной строки регистра. Откроем сессию один раз, отправим всю накопившуюся пачку писем и корректно закроем сессию.
Процедура ОтправитьОчередьУведомлений() Экспорт
// Выбираем порцию записей, ожидающих отправки
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 50
| Очередь.ИдентификаторЗаписи КАК ИдентификаторЗаписи,
| Очередь.СсылкаНаОбъект КАК СсылкаНаОбъект
|ИЗ
| РегистрСведений.ОчередьУведомлений КАК Очередь
|ГДЕ
| Очередь.СтатусОбработки = ЗНАЧЕНИЕ(Перечисление.СтатусыОчереди.КОтправке)";
Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
Возврат;
КонецЕсли;
Выборка = Результат.Выбрать();
// Настраиваем почтовый профиль
Профиль = Новый ИнтернетПочтовыйПрофиль;
Профиль.АдресСервераSMTP = "smtp.example.com";
Профиль.ПортSMTP = 465;
Профиль.ПользовательSMTP = "robot@example.com";
Профиль.ПарольSMTP = "SecretPassword";
Профиль.ВремяОжидания = 30;
Профиль.ИспользоватьSSLSMTP = Истина;
Почта = Новый ИнтернетПочта;
Попытка
Почта.Подключиться(Профиль);
Исключение
ЗаписьЖурналаРегистрации("ОтправкаПочты", УровеньЖурналаРегистрации.Ошибка,,, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
Возврат;
КонецПопытки;
Пока Выборка.Следующий() Цикл
Письмо = Новый ИнтернетПочтовоеСообщение;
Письмо.Тема = "Карточка подтверждена: " + Строка(Выборка.СсылкаНаОбъект);
Письмо.Отправитель = "robot@example.com";
Письмо.Получатели.Добавить("manager@example.com");
Письмо.Тексты.Добавить("Уведомляем, что карточка переведена в подтвержденное состояние.", ТипТекстаПочтовогоСообщения.ПростойТекст);
Попытка
Почта.Послать(Письмо);
// Фиксируем успешную отправку
ЗафиксироватьСтатусОчереди(Выборка.ИдентификаторЗаписи, Перечисления.СтатусыОчереди.Отправлено, "");
Исключение
ЗафиксироватьСтатусОчереди(Выборка.ИдентификаторЗаписи, Перечисления.СтатусыОчереди.Ошибка, ОписаниеОшибки());
КонецПопытки;
КонецЦикла;
Почта.Отключиться();
КонецПроцедуры
Если регламентное задание с интервалом в 1 минуту не подходит по требованиям бизнеса и уведомление должно уходить немедленно, рассмотрим запуск отправки через объект ФоновыеЗадания. Запуск фонового задания следует производить после завершения транзакции (например, в обработчике формы ПослеЗаписи или через обработчик ожидания), передавая в фоновую процедуру ссылку на подтвержденный объект:
&НаКлиенте
Процедура ПослеЗаписи(ПараметрыЗаписи)
Если ЭтотОбъект.Подтверждено Тогда
ЗапуститьФоновоеУведомление(Объект.Ссылка);
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ЗапуститьФоновоеУведомление(СсылкаНаОбъект)
ПараметрыЗадания = Новый Массив;
ПараметрыЗадания.Добавить(СсылкаНаОбъект);
ФоновыеЗадания.Выполнить("ИнтеграцияПочта.ОтправитьУведомлениеПоОбъекту", ПараметрыЗадания);
КонецПроцедуры
Такой механизм позволяет отправить письмо за доли секунды без блокировки интерфейса пользователя и без задержки транзакции СУБД.
Резюме: При реализации бизнес-событий всегда разделяйте транзакцию фиксации данных и интеграционные сетевые вызовы. Подписка на событие идеальна для регистрации намерения в регистре-очереди, а регламентные или фоновые задания гарантируют стабильную и бесперебойную отправку сообщений.