Как отправить почтовое уведомление об изменении справочника только один раз?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В процессе разработки в системе 1С часто возникает задача оперативного информирования пользователей или администраторов об изменениях в важных справочниках. Однако стандартные события записи объекта могут приводить к неожиданным эффектам: например, если пользователь сначала нажимает кнопку Записать, а затем Записать и закрыть, уведомление может уйти дважды. Это происходит потому, что платформа воспринимает каждое нажатие как отдельную транзакцию записи.

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

Проблема: Почему стандартная проверка «ПриЗаписи» не всегда работает

Когда мы размещаем код отправки почты в процедуре ПриЗаписи модуля объекта, система выполняет его каждый раз, когда объект фиксируется в базе данных. Если пользователь внес изменения, нажал «Записать» (уведомление ушло), а затем, не меняя ничего более, нажал «Записать и закрыть», процедура ПриЗаписи выполнится снова. Нам же нужно научить систему понимать: «Если с момента последней записи ничего не поменялось — письмо слать не нужно».

Способ 1. Сравнение текущих данных со ссылкой в модуле объекта

Рассмотрим самый надежный метод, основанный на сравнении текущего состояния объекта (данные в памяти) с его состоянием в базе данных (данные по ссылке). Мы будем использовать процедуру ПередЗаписью, чтобы определить факт изменений, и коллекцию ДополнительныеСвойства, чтобы передать этот флаг в процедуру ПриЗаписи.

Разберем алгоритм по шагам:

  1. В процедуре ПередЗаписью мы обращаемся к ЭтотОбъект.Ссылка. Пока транзакция не завершена, по ссылке в базе еще лежат старые данные.
  2. Сравниваем нужные нам реквизиты.
  3. Если найдено отличие, устанавливаем временный флаг.

Пример программного кода в модуле объекта:


Процедура ПередЗаписью(Отказ)
    Если ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;

    // Если это новый объект, он всегда считается измененным
    ДанныеИзменены = ЭтоНовый();
    
    Если Не ДанныеИзменены Тогда
        // Получаем старые значения из базы данных для сравнения
        СтарыеДанные = Ссылка.ПолучитьОбъект();
        
        // Перечисляем важные реквизиты, изменение которых требует уведомления
        Если Наименование <> СтарыеДанные.Наименование 
            Или Родитель <> СтарыеДанные.Родитель 
            Или Реквизит1 <> СтарыеДанные.Реквизит1 Тогда
            
            ДанныеИзменены = Истина;
        КонецЕсли;
    КонецЕсли;

    // Сохраняем результат проверки во временном свойстве объекта
    ДополнительныеСвойства.Вставить("ЭтоРеальноеИзменение", ДанныеИзменены);
КонецПроцедуры

Процедура ПриЗаписи(Отказ)
    Если ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;

    // Проверяем наш флаг, установленный в ПередЗаписью
    Если ДополнительныеСвойства.Свойство("ЭтоРеальноеИзменение") 
        И ДополнительныеСвойства.ЭтоРеальноеИзменение Тогда
        
        // Вызываем вашу функцию отправки почты
        ОтправитьУведомлениеОбИзменении();
        
        // Очищаем флаг, чтобы при случайном повторном входе в процедуру (в рамках этой записи) не было дубля
        ДополнительныеСвойства.Вставить("ЭтоРеальноеИзменение", Ложь);
    КонецЕсли;
КонецПроцедуры

Способ 2. Автоматическое сравнение через сериализацию

Если в справочнике очень много реквизитов, прописывать сравнение каждого поля вручную — трудозатратно и чревато ошибками при добавлении новых полей в конфигурацию. Проанализируем ситуацию: как сравнить объект целиком? Мы можем использовать механизм сериализации в XML или JSON, чтобы быстро получить строковое представление объекта.

Рассмотрим пример функции, которая проверяет модифицированность:


Функция ДанныеОбъектаДействительноИзменены()
    Если ЭтоНовый() Тогда
        Возврат Истина;
    КонецЕсли;
    
    // Исключаем системные поля, которые меняются всегда (например, ВерсияДанных)
    // Для этого можно использовать СериализаторXDTO или простое сравнение значимых полей в цикле
    МетаданныеОбъекта = Метаданные();
    Для Каждого Реквизит Из МетаданныеОбъекта.Реквизиты Цикл
        Если ЭтотОбъект[Реквизит.Имя] <> Ссылка[Реквизит.Имя] Тогда
            Возврат Истина;
        КонецЕсли;
    КонецЦикла;
    
    Возврат Ложь;
КонецФункции

Способ 3. Асинхронная отправка через очередь (лучшая практика)

Выясним причину, по которой отправка почты напрямую в транзакции записи (события ПриЗаписи) считается плохим тоном. Если почтовый сервер будет недоступен или затормозит, пользователь увидит «зависшее» окно 1С и не сможет продолжать работу, пока не истечет тайм-аут соединения. К тому же, если транзакция отменится, письмо уже может уйти.

Проанализируем более профессиональный подход:

  1. В момент записи объекта, если мы определили факт изменения, мы не шлем письмо сразу.
  2. Мы записываем краткую информацию (какой справочник изменен и когда) в специально созданный Регистр сведений (назовем его ОчередьУведомлений).
  3. Создаем Регламентное задание, которое раз в несколько минут считывает записи из этого регистра, отправляет письма и удаляет обработанные записи.

Этот метод решает сразу две задачи: повышает отказоустойчивость системы и позволяет избежать дублей. Даже если пользователь нажмет «Записать» 10 раз за минуту, регламентное задание может быть настроено так, чтобы сгруппировать эти записи и отправить только одно итоговое уведомление.

Способ 4. Работа на стороне формы (интерактивные изменения)

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

Разберем по шагам работу в модуле формы:


&НаСервере
Процедура ПослеЗаписиНаСервере(ТекущийОбъект, ПараметрыЗаписи)
    // Если запись была инициирована изменением данных в форме
    Если ПараметрыЗаписи.Свойство("ЗаписанПослеИзменения") Тогда
        // Логика отправки
    КонецЕсли;
КонецПроцедуры

&НаКлиенте
Процедура ПередЗаписью(Отказ, ПараметрыЗаписи)
    // Если форма была изменена пользователем
    Если ЭтаФорма.Модифицированность Тогда
        ПараметрыЗаписи.Вставить("ЗаписанПослеИзменения", Истина);
    КонецЕсли;
КонецПроцедуры

Подведем итоги

Для реализации корректной отправки уведомлений выберите один из подходов в зависимости от сложности вашей задачи:

  1. Для простых справочников: сравнение ключевых реквизитов в ПередЗаписью и флаг в ДополнительныеСвойства.
  2. Для универсальности: перебор реквизитов в цикле через метаданные.
  3. Для высокой надежности: использование регистра сведений и регламентного задания для фоновой отправки.

Использование этих методов позволит вам избежать «заспамливания» почтовых ящиков пользователей и обеспечит стабильную работу системы 1С.

← На главную