Как узнать, каким действием вызвана процедура ПриЗаписи в модуле объекта справочника 1С?

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

В процессе разработки бизнес-логики в системе «1С:Предприятие» перед программистом часто возникает задача: определить, по какой именно причине или в результате какого действия пользователя была вызвана процедура ПриЗаписи в модуле объекта справочника. Например, нам нужно выполнить определенные действия только в том случае, если пользователь интерактивно пометил элемент на удаление, перенес его в другую группу (папку) в динамическом списке или же запись была произведена программно из фонового задания.

Напрямую платформа не предоставляет готового параметра «ДействиеИсточника» в обработчиках событий. Однако решить эту задачу можно красиво, надежно и в соответствии со стандартами разработки фирмы «1С». В этой статье мы подробно рассмотрим, как реализовать этот механизм, избежав типичных ошибок производительности.

Критический нюанс платформы: разница между ПередЗаписью и ПриЗаписи

Прежде чем перейти к написанию кода, разберем важнейший архитектурный нюанс работы СУБД в платформе «1С:Предприятие». Многие начинающие разработчики совершают грубую ошибку, пытаясь выполнить запрос к базе данных или обратиться к свойству ЭтотОбъект.Ссылка.ПометкаУдаления непосредственно внутри процедуры ПриЗаписи, надеясь получить «старое» (предыдущее) значение реквизита.

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

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

Использование коллекции ДополнительныеСвойства

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

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

Рассмотрим, как с помощью этого механизма решить нашу задачу по шагам.

Шаг 1. Получение старых значений в событии ПередЗаписью (Оптимальный способ)

При сравнении значений в процедуре ПередЗаписью обращение к базе через точку вида ЭтотОбъект.Ссылка.Родитель приводит к неявному запросу, который считывает из СУБД весь объект целиком со всеми его реквизитами и табличными частями. Если справочник «тяжелый», это сильно снизит производительность системы, особенно при групповой обработке данных.

Чтобы избежать этой проблемы, напишем легкий и быстрый запрос, который извлечет только нужные нам поля для сравнения:


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

КонецПроцедуры

Шаг 2. Обработка изменений в процедуре ПриЗаписи

Теперь в процедуре ПриЗаписи мы можем легко проанализировать данные, которые мы предварительно сохранили в ДополнительныеСвойства в первом шаге:


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

Шаг 3. Как точно определить контекст вызова (Форма, Список, Программный код)

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

1. Интерактивное сохранение из формы элемента:

В модуле формы элемента справочника в процедуре ПередЗаписьюНаСервере мы принудительно добавляем специальный признак в дополнительные свойства записываемого объекта:


&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
    ТекущийОбъект.ДополнительныеСвойства.Вставить("ЗаписаноИзФормы", Истина);
КонецПроцедуры

2. Программная запись из внешних обработок или алгоритмов:

Если мы пишем собственный программный код, который изменяет справочник, мы также должны явно передавать признак программной записи. Это позволит нам отделить системные алгоритмы от действий пользователя:


ОбъектСправочника = ВыбраннаяСсылка.ПолучитьОбъект();
ОбъектСправочника.ДополнительныеСвойства.Вставить("ПрограммнаяЗапись", Истина);
ОбъектСправочника.ПометкаУдаления = Истина;
ОбъектСправочника.Записать();

3. Интерактивные действия из динамических списков (например, перемещение Drag-and-Drop или пометка на удаление по кнопке Del):

При выполнении этих действий форма элемента не открывается. Соответственно, обработчик ПередЗаписьюНаСервере формы не вызывается, а признак ЗаписаноИзФормы в структуре ДополнительныеСвойства будет отсутствовать. Сравнив новые данные со значениями в базе в процедуре ПередЗаписью, мы поймем, что изменения произошли интерактивно напрямую из списка.

Проанализируем эту логику в процедуре ПриЗаписи:


// Определяем, откуда пришел запрос на запись
ЗаписаноИзФормы = Ложь;
Если ДополнительныеСвойства.Свойство("ЗаписаноИзФормы") Тогда
    ЗаписаноИзФормы = Истина;
КонецЕсли;

ПрограммнаяЗапись = Ложь;
Если ДополнительныеСвойства.Свойство("ПрограммнаяЗапись") Тогда
    ПрограммнаяЗапись = Истина;
КонецЕсли;

Если Не ЗаписаноИзФормы И Не ПрограммнаяЗапись Тогда
    // Объект изменен из динамического списка (например, перетаскиванием или клавишей Del)
    // Реализуем соответствующую логику безопасности или логирования
КонецЕсли;

Использование подписок на события

Если вам требуется реализовать такой аудит изменений для большого количества справочников в системе, не стоит прописывать этот код вручную в модуле каждого объекта. Это приведет к дублированию кода и усложнит поддержку.

Гораздо эффективнее использовать Подписки на события:

  1. Создайте общую подписку на событие ПередЗаписью для нужного типа или группы справочников. В обработчике этой подписки выполняйте легкий запрос к СУБД, сравнивайте старые и новые значения и помещайте результаты в Источник.ДополнительныеСвойства.
  2. Создайте вторую подписку на событие ПриЗаписи. В ее обработчике анализируйте сохраненные свойства объекта и запускайте необходимую общую бизнес-логику (например, запись изменений в регистр сведений или отправку уведомлений во внешние системы).

Важность проверки ОбменДанными.Загрузка

При написании любых алгоритмов в обработчиках событий ПередЗаписью и ПриЗаписи всегда проверяйте свойство ОбменДанными.Загрузка. Если это свойство имеет значение Истина, это означает, что объект записывается в процессе обмена данными или переноса из другой информационной базы.

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

Готовое решение в БСП: Версионирование объектов

Если ваша конечная задача состоит в том, чтобы фиксировать историю изменений («кто, когда, как и какой реквизит изменил»), не спешите писать собственную систему логирования с нуля.

В составе Библиотеки стандартных подсистем (БСП), которая интегрирована во все типовые конфигурации (например, 1С:ERP, 1С:Управление торговлей, 1С:Бухгалтерия), уже есть готовый, оптимизированный и отлаженный механизм «Версионирование объектов». Вам достаточно просто включить эту опцию в настройках программы для конкретного справочника. Система будет автоматически сохранять версии объектов при записи и предоставлять пользователям удобные отсчеты для сравнения версий.

← На главную