В процессе разработки в системе 1С часто возникает задача оперативного информирования пользователей или администраторов об изменениях в важных справочниках. Однако стандартные события записи объекта могут приводить к неожиданным эффектам: например, если пользователь сначала нажимает кнопку Записать, а затем Записать и закрыть, уведомление может уйти дважды. Это происходит потому, что платформа воспринимает каждое нажатие как отдельную транзакцию записи.
В данной статье мы разберем, как реализовать механизм, который будет четко определять, изменились ли данные на самом деле, и гарантировать однократную отправку сообщения за одну сессию редактирования.
Когда мы размещаем код отправки почты в процедуре ПриЗаписи модуля объекта, система выполняет его каждый раз, когда объект фиксируется в базе данных. Если пользователь внес изменения, нажал «Записать» (уведомление ушло), а затем, не меняя ничего более, нажал «Записать и закрыть», процедура ПриЗаписи выполнится снова. Нам же нужно научить систему понимать: «Если с момента последней записи ничего не поменялось — письмо слать не нужно».
Рассмотрим самый надежный метод, основанный на сравнении текущего состояния объекта (данные в памяти) с его состоянием в базе данных (данные по ссылке). Мы будем использовать процедуру ПередЗаписью, чтобы определить факт изменений, и коллекцию ДополнительныеСвойства, чтобы передать этот флаг в процедуру ПриЗаписи.
Разберем алгоритм по шагам:
ПередЗаписью мы обращаемся к ЭтотОбъект.Ссылка. Пока транзакция не завершена, по ссылке в базе еще лежат старые данные.Пример программного кода в модуле объекта:
Процедура ПередЗаписью(Отказ)
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// Если это новый объект, он всегда считается измененным
ДанныеИзменены = ЭтоНовый();
Если Не ДанныеИзменены Тогда
// Получаем старые значения из базы данных для сравнения
СтарыеДанные = Ссылка.ПолучитьОбъект();
// Перечисляем важные реквизиты, изменение которых требует уведомления
Если Наименование <> СтарыеДанные.Наименование
Или Родитель <> СтарыеДанные.Родитель
Или Реквизит1 <> СтарыеДанные.Реквизит1 Тогда
ДанныеИзменены = Истина;
КонецЕсли;
КонецЕсли;
// Сохраняем результат проверки во временном свойстве объекта
ДополнительныеСвойства.Вставить("ЭтоРеальноеИзменение", ДанныеИзменены);
КонецПроцедуры
Процедура ПриЗаписи(Отказ)
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// Проверяем наш флаг, установленный в ПередЗаписью
Если ДополнительныеСвойства.Свойство("ЭтоРеальноеИзменение")
И ДополнительныеСвойства.ЭтоРеальноеИзменение Тогда
// Вызываем вашу функцию отправки почты
ОтправитьУведомлениеОбИзменении();
// Очищаем флаг, чтобы при случайном повторном входе в процедуру (в рамках этой записи) не было дубля
ДополнительныеСвойства.Вставить("ЭтоРеальноеИзменение", Ложь);
КонецЕсли;
КонецПроцедуры
Если в справочнике очень много реквизитов, прописывать сравнение каждого поля вручную — трудозатратно и чревато ошибками при добавлении новых полей в конфигурацию. Проанализируем ситуацию: как сравнить объект целиком? Мы можем использовать механизм сериализации в XML или JSON, чтобы быстро получить строковое представление объекта.
Рассмотрим пример функции, которая проверяет модифицированность:
Функция ДанныеОбъектаДействительноИзменены()
Если ЭтоНовый() Тогда
Возврат Истина;
КонецЕсли;
// Исключаем системные поля, которые меняются всегда (например, ВерсияДанных)
// Для этого можно использовать СериализаторXDTO или простое сравнение значимых полей в цикле
МетаданныеОбъекта = Метаданные();
Для Каждого Реквизит Из МетаданныеОбъекта.Реквизиты Цикл
Если ЭтотОбъект[Реквизит.Имя] <> Ссылка[Реквизит.Имя] Тогда
Возврат Истина;
КонецЕсли;
КонецЦикла;
Возврат Ложь;
КонецФункции
Выясним причину, по которой отправка почты напрямую в транзакции записи (события ПриЗаписи) считается плохим тоном. Если почтовый сервер будет недоступен или затормозит, пользователь увидит «зависшее» окно 1С и не сможет продолжать работу, пока не истечет тайм-аут соединения. К тому же, если транзакция отменится, письмо уже может уйти.
Проанализируем более профессиональный подход:
ОчередьУведомлений).Этот метод решает сразу две задачи: повышает отказоустойчивость системы и позволяет избежать дублей. Даже если пользователь нажмет «Записать» 10 раз за минуту, регламентное задание может быть настроено так, чтобы сгруппировать эти записи и отправить только одно итоговое уведомление.
Если уведомления должны уходить только когда пользователь меняет данные руками в интерфейсе (игнорируя программную загрузку), можно использовать события управляемой формы. В управляемых формах есть свойство Модифицированность.
Разберем по шагам работу в модуле формы:
ПослеЗаписиНаСервере.ПараметрыЗаписи.Модифицированность сбрасывается в Ложь. Поэтому проверку нужно делать с умом.
&НаСервере
Процедура ПослеЗаписиНаСервере(ТекущийОбъект, ПараметрыЗаписи)
// Если запись была инициирована изменением данных в форме
Если ПараметрыЗаписи.Свойство("ЗаписанПослеИзменения") Тогда
// Логика отправки
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ПередЗаписью(Отказ, ПараметрыЗаписи)
// Если форма была изменена пользователем
Если ЭтаФорма.Модифицированность Тогда
ПараметрыЗаписи.Вставить("ЗаписанПослеИзменения", Истина);
КонецЕсли;
КонецПроцедуры
Для реализации корректной отправки уведомлений выберите один из подходов в зависимости от сложности вашей задачи:
ПередЗаписью и флаг в ДополнительныеСвойства.Использование этих методов позволит вам избежать «заспамливания» почтовых ящиков пользователей и обеспечит стабильную работу системы 1С.