Как определить удаление записи регистра сведений в событиях ПередЗаписью и ПриЗаписи

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

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

Понимание механизма записи набора записей

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

  1. Удаляет из базы данных все существующие записи, которые соответствуют текущему значению Отбор набора.
  2. Записывает в базу данных новые записи, содержащиеся в текущем объекте ЭтотОбъект.

Если в момент вызова метода ЭтотОбъект.Количество() равно нулю, то фактически выполняется только первая часть — очистка данных по отбору. Это и является ключевым индикатором удаления.

Метод 1: Анализ количества записей и отбора

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

Разберем пример кода для модуля набора записей:


Процедура ПередЗаписью(Отказ, Замещение)
    
    // Если параметр Замещение = Ложь, то старые записи не удаляются, 
    // и логика определения удаления меняется. Но обычно Замещение всегда Истина.
    Если НЕ Замещение Тогда
        Возврат;
    КонецЕсли;

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

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

Метод 2: Использование запроса к базе данных

Иногда нам нужно точно знать, были ли в базе данных записи до начала процесса записи, чтобы отличить «удаление пустого набора» (когда и в базе ничего не было) от «реального удаления данных». Для этого в обработчике ПередЗаписью мы можем выполнить запрос по измерениям текущего отбора.

Проанализируем пример реализации с динамическим формированием текста запроса на основе метаданных отбора:


Процедура ПередЗаписью(Отказ, Замещение)

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

    // Формируем условия запроса на основе установленного отбора
    Поля = ""; 
    Условия = "";
    Запрос = Новый Запрос;
    
    Для Каждого ЭлементОтбора Из ЭтотОбъект.Отбор Цикл
        Если ЭлементОтбора.Использование Тогда
            ИмяПоля = ЭлементОтбора.Имя;
            Поля = Поля + ?(Поля = "", "", ", ") + ИмяПоля;
            Условия = Условия + ?(Условия = "", "", " И ") + ИмяПоля + " = &" + ИмяПоля;
            Запрос.УстановитьПараметр(ИмяПоля, ЭлементОтбора.Значение);
        КонецЕсли;
    КонецЦикла;

    Если Поля = "" Тогда
        // Отбор не установлен, запись во весь регистр (опасно!)
        Возврат;
    КонецЕсли;

    Запрос.Текст = "ВЫБРАТЬ ПЕРВЫЕ 1 " + Поля + " ИЗ РегистрСведений.ИмяВашегоРегистра КАК Рег ГДЕ " + Условия;
    РезультатЗапроса = Запрос.Выполнить();

    ЗаписиВБазеСуществуют = НЕ РезультатЗапроса.Пустой();
    БудетПустойНабор = (ЭтотОбъект.Количество() = 0);

    Если ЗаписиВБазеСуществуют И БудетПустойНабор Тогда
        // Это ситуация чистого удаления данных
        Сообщить("Выполняется удаление записей по установленному отбору!");
    КонецЕсли;

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

Метод 3: Передача флага через ДополнительныеСвойства

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

Посмотрим на пример кода в месте вызова (например, в обработке):


Набор = РегистрыСведений.ЦеныНоменклатуры.СоздатьНаборЗаписей();
Набор.Отбор.Номенклатура.Установить(ВыбТовар);
// Устанавливаем свой флаг
Набор.ДополнительныеСвойства.Вставить("ЭтоОперацияУдаления", Истина);
Набор.Записать();

И соответствующая проверка в модуле набора записей:


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

Нюанс с изменением ключевых полей (измерений)

Выясним причину, по которой ПередЗаписью может вызываться дважды. Если в существующей записи регистра пользователь меняет значение измерения (например, дату в периодическом регистре или склад в остатках), платформа выполняет следующие действия:

  1. Вызывает ПередЗаписью для удаления старой записи (набор будет пуст, отбор настроен на старые значения).
  2. Вызывает ПередЗаписью для записи нового значения (набор содержит новую строку, отбор настроен на новые значения).

В первом вызове набор будет выглядеть как удаляемый. Чтобы не перепутать «переименование» с «удалением», разработчики часто используют флаги в ДополнительныеСвойства при первом вызове, чтобы проигнорировать или особым образом обработать цепочку событий.

Особенности для подчиненных регистров

Для регистров, которые имеют режим записи Подчинение регистратору, удаление записей происходит автоматически при отмене проведения или удалении документа-регистратора. В этом случае проверять нужно не только количество записей, но и состояние регистратора:


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

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

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

Важные моменты:

Таким образом, комбинируя анализ ЭтотОбъект.Количество(), использование ДополнительныеСвойства и, при необходимости, легкие запросы к БД, можно со стопроцентной точностью определить операцию удаления в регистрах сведений 1С.

← На главную