В процессе разработки часто возникает задача: выполнить определенное действие (например, отправить уведомление по электронной почте) ровно один раз при записи документа. На первый взгляд, решение кажется простым — добавить реквизит типа Булево и проверять его. Однако на практике разработчики сталкиваются с тем, что значение реквизита не сохраняется в базе данных или уведомления уходят по несколько раз при неудачных транзакциях. Разберем подробно, почему так происходит и как реализовать этот механизм правильно.
Разберем ситуацию, описанную автором: попытка установить значение реквизита ОтправленоУведомлениеОплачено = Истина; внутри обработчика ПриЗаписи(). Выясним причину неудачи. В 1С событие ПриЗаписи срабатывает, когда данные объекта уже физически переданы в базу данных, но транзакция еще не зафиксирована (не сделан Commit). Изменяя значение реквизита ЭтотОбъект в этой процедуре, мы меняем его только в оперативной памяти текущего экземпляра объекта. Для того чтобы это изменение попало в базу данных, потребовался бы повторный вызов метода Записать(), что неизбежно приведет к бесконечной рекурсии и аварийному завершению работы программы.
Важно понимать: любые изменения данных самого документа, которые должны быть сохранены в его таблице, необходимо производить в обработчике ПередЗаписью.
Проанализируем самый простой способ решения задачи. Если нам нужно, чтобы флаг об отправке сохранился вместе с документом, мы должны установить его до того, как данные уйдут в базу. Рассмотрим пример кода:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Проверяем, что документ проводится и уведомление еще не было отправлено
Если РежимЗаписи = РежимЗаписиДокумента.Проведение
И Не ОтправленоУведомлениеОплачено Тогда
// Устанавливаем флаг, он будет записан в базу вместе с остальными данными
ОтправленоУведомлениеОплачено = Истина;
// Передаем признак необходимости отправки в событие ПриЗаписи через ДополнительныеСвойства
ДополнительныеСвойства.Вставить("НужноОтправитьУведомление", Истина);
КонецЕсли;
КонецПроцедуры
Однако здесь кроется серьезная архитектурная ловушка. Если мы выполним отправку письма прямо в транзакции записи (в ПередЗаписью или ПриЗаписи), а затем произойдет ошибка (например, не хватило остатков на складе или сработала проверка заполнения), транзакция будет отменена (Rollback). Документ не сохранится, флаг Истина в базе не появится, но письмо уже уйдет пользователю. При следующей попытке записи письмо уйдет снова. Это и есть проблема «фантомных» уведомлений.
Для реализации надежной системы уведомлений профессиональное сообщество рекомендует использовать отдельный Регистр сведений. Это позволяет отделить логику хранения данных документа от логики транспортных сообщений. Рассмотрим преимущества этого метода:
Посмотрим на пример логики в обработчике ПриЗаписи с использованием регистра:
Процедура ПриЗаписи(Отказ)
Если ДополнительныеСвойства.Свойство("НужноОтправитьУведомление") Тогда
// Записываем данные в независимый регистр сведений
Менеджер = РегистрыСведений.ОчередьУведомлений.СоздатьМенеджерЗаписи();
Менеджер.Объект = Ссылка;
Менеджер.ДатаСоздания = ТекущаяДата();
Менеджер.Отправлено = Ложь;
Менеджер.Записать();
КонецЕсли;
КонецПроцедуры
Проанализируем проблему блокировок. Отправка почты — это внешняя операция, которая может длиться секунды (ожидание ответа SMTP-сервера). Если делать это внутри транзакции записи документа, то всё это время запись документа в базе данных будет заблокирована. Другие пользователи не смогут редактировать этот документ или связанные с ним данные. В высоконагруженных системах (ERP, УТ) это недопустимо.
Правильным решением будет запуск фонового задания. Выясним, как это реализовать:
ПриЗаписи мы проверяем необходимость отправки.Рассмотрим пример кода для запуска фонового задания:
Процедура ПриЗаписи(Отказ)
Если ДополнительныеСвойства.Свойство("НужноОтправитьУведомление") Тогда
НаименованиеЗадания = "Отправка уведомления по документу " + Строка(Ссылка);
ПараметрыЗадания = Новый Массив;
ПараметрыЗадания.Добавить(Ссылка);
ФоновыеЗадания.Выполнить("ОбщийМодульУведомления.ОтправитьПисьмоПоОбъекту", ПараметрыЗадания, , НаименованиеЗадания);
КонецЕсли;
КонецПроцедуры
Если вы работаете в современной конфигурации на базе БСП (Библиотека стандартных подсистем), разберем самый элегантный способ. Вместо написания велосипедов, стоит использовать подсистему «Электронное взаимодействие» или «Работа с почтовыми сообщениями».
В БСП часто используется справочник ИсходящиеЭлектронныеПисьма. Вместо прямой отправки мы создаем элемент этого справочника. Система сама, с помощью настроенного регламентного задания, отправит его в фоновом режиме, обработает ошибки подключения к серверу и сделает несколько попыток в случае сбоя.
Проанализировав все варианты, мы можем составить алгоритм правильной реализации:
ПередЗаписью, ПриЗаписи, ОбработкаПроведения).ПередЗаписью.ДополнительныеСвойства.Следуя этим правилам, вы обеспечите стабильную работу системы и избежите типичных ошибок, связанных с механизмом транзакций в платформе 1С:Предприятие.