При разработке функционала аудита, логирования изменений или интеграционных механизмов часто возникает необходимость отслеживать создание и модификацию элементов справочников или документов без изменения типовой конфигурации. Для этого применяются подписки на события. Однако разработчики регулярно сталкиваются с дилеммой разделения логики между событиями ПередЗаписью и ПриЗаписи.
Проанализируем суть проблемы: в обработчике ПередЗаписью мы можем легко определить, является ли объект новым (с помощью метода Источник.ЭтоНовый()), а также сравнить текущие заполненные реквизиты со старыми значениями из базы данных. Но для нового объекта ссылки в базе еще нет. В то же время в обработчике ПриЗаписи объект уже сохранен в базе данных: ссылка гарантированно существует, но метод Источник.ЭтоНовый() возвращает Ложь, а старые значения реквизитов в базе уже перезаписаны новыми данными.
Разберем по шагам, как правильно связать эти два события и передать рассчитанные признаки из ПередЗаписью в ПриЗаписи.
Каждый прикладной объект 1С (справочник, документ и т. д.) содержит свойство ДополнительныеСвойства, представляющее собой объект типа Структура. Данная структура сохраняет свое содержимое на протяжении всего жизненного цикла объекта в оперативной памяти в рамках текущей транзакции записи.
Рассмотрим, как передать признаки новизны и изменения реквизитов из первой подписки во вторую.
Шаг 1. Обработка в подписке ПередЗаписью
В подписке на событие ПередЗаписью проверяем, новый ли это объект, либо считываем старые данные из базы и сравниваем с текущими. Результаты анализа сохраняем в структуру ДополнительныеСвойства:
Процедура спр_ПередЗаписью(Источник, Отказ) Экспорт
// Обязательно исключаем выполнение логики при обмене данными
Если Источник.ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
ЭтоНовыйОбъект = Источник.ЭтоНовый();
ДанныеИзменились = Ложь;
Если ЭтоНовыйОбъект Тогда
ДанныеИзменились = Истина;
Иначе
// Получаем сохраненные в базе значения запросом для сравнения
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТекущиеДанные.Наименование КАК Наименование,
| ТекущиеДанные.Реквизит1 КАК Реквизит1
|ИЗ
| Справочник.Номенклатура КАК ТекущиеДанные
|ГДЕ
| ТекущиеДанные.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", Источник.Ссылка);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Если Выборка.Наименование <> Источник.Наименование
Или Выборка.Реквизит1 <> Источник.Реквизит1 Тогда
ДанныеИзменились = Истина;
КонецЕсли;
КонецЕсли;
КонецЕсли;
// Передаем вычисленные флаги в структуру ДополнительныеСвойства
Источник.ДополнительныеСвойства.Вставить("ЭтоНовыйОбъект", ЭтоНовыйОбъект);
Источник.ДополнительныеСвойства.Вставить("ДанныеИзменились", ДанныеИзменились);
КонецПроцедуры
Шаг 2. Обработка в подписке ПриЗаписи
В обработчике ПриЗаписи извлекаем сохраненные значения и выполняем регистрацию записей в регистре сведений:
Процедура спр_ПриЗаписи(Источник, Отказ) Экспорт
Если Источник.ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
ЭтоНовыйОбъект = Ложь;
ДанныеИзменились = Ложь;
// Безопасно считываем переданные свойства
Если НЕ Источник.ДополнительныеСвойства.Свойство("ЭтоНовыйОбъект", ЭтоНовыйОбъект) Тогда
Возврат;
КонецЕсли;
Источник.ДополнительныеСвойства.Свойство("ДанныеИзменились", ДанныеИзменились);
Если ДанныеИзменились Тогда
МенеджерЗаписи = РегистрыСведений.ИсторияИзмененияОбъектов.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Период = ТекущаяДатаСеанса();
МенеджерЗаписи.Объект = Источник.Ссылка;
Если ЭтоНовыйОбъект Тогда
МенеджерЗаписи.Действие = "Создан";
Иначе
МенеджерЗаписи.Действие = "Изменен";
КонецЕсли;
МенеджерЗаписи.Записать();
КонецЕсли;
КонецПроцедуры
В ряде сценариев требуется выполнить всю логику в рамках одной единственной подписки ПередЗаписью. Для этого необходимо заранее получить ссылку для нового элемента.
Разберем, как сгенерировать ссылку программно:
Источник.ПолучитьСсылкуНового().Неопределено, генерируем ссылку на основе нового уникального идентификатора через Справочники[ИмяСправочника].ПолучитьСсылку(Новый УникальныйИдентификатор()).Источник.УстановитьСсылкуНового(НоваяСсылка).Посмотрим на пример реализации:
Процедура спр_ПередЗаписью_ОдноСобытие(Источник, Отказ) Экспорт
Если Источник.ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
Если Источник.ЭтоНовый() Тогда
СсылкаНаОбъект = Источник.ПолучитьСсылкуНового();
Если НЕ ЗначениеЗаполнено(СсылкаНаОбъект) Тогда
СсылкаНаОбъект = Справочники.Номенклатура.ПолучитьСсылку(Новый УникальныйИдентификатор());
Источник.УстановитьСсылкуНового(СсылкаНаОбъект);
КонецЕсли;
Действие = "Создан";
Иначе
СсылкаНаОбъект = Источник.Ссылка;
Действие = "Изменен";
КонецЕсли;
// Формируем движение в регистре сведений
МенеджерЗаписи = РегистрыСведений.ИсторияИзмененияОбъектов.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Период = ТекущаяДатаСеанса();
МенеджерЗаписи.Объект = СсылкаНаОбъект;
МенеджерЗаписи.Действие = Действие;
МенеджерЗаписи.Записать();
КонецПроцедуры
При работе с подписками на события и записью связанных регистров необходимо учитывать следующие требования платформы:
ПередЗаписью и ПриЗаписи выполняются внутри одной транзакции базы данных. Если в процессе возникнет ошибка или будет установлен флаг Отказ = Истина, изменения объекта и все сопутствующие записи в регистрах будут автоматически откатаны.Запрос по ссылке объекта. Категорически не рекомендуется использовать конструкцию Источник.Ссылка.ПолучитьОбъект(), так как она создает избыточную нагрузку на сервер и считывает весь объект целиком в оперативную память.Источник.ОбменДанными.Загрузка в начале подписок, чтобы избежать дублирования записей и замедления обменов при пакетной загрузке данных.