Как реализовать «мягкое удаление» в регистре сведений 1С?

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

В практике разработки на платформе 1С часто возникает задача сохранения истории данных. Стандартное удаление записей из регистра сведений безвозвратно стирает информацию, что недопустимо, если требуется аудит или возможность восстановления данных. Решением является механизм «мягкого удаления»: когда вместо физического удаления записи в базе данных, у нее просто сбрасывается флаг актуальности (например, реквизит Актуальность становится Ложь).

Разберем подробно, как перехватить процесс удаления и заменить его на обновление данных, используя возможности модуля набора записей и встроенного языка 1С.

Принцип работы механизма перехвата

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

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

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

Перейдем в модуль набора записей вашего регистра сведений. Рассмотрим основной алгоритм действий. Нам необходимо проверить количество записей в наборе и состояние модифицированности. Если записей 0 — значит, инициировано удаление.

Рассмотрим пример кода, который решает эту задачу:


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

    // 2. Определяем попытку удаления по пустому набору
    Если ЭтотОбъект.Количество() = 0 И ЭтотОбъект.Отбор.Количество() > 0 Тогда
        
        // Проанализируем ситуацию: набор пуст, но есть отбор - это типичное удаление.
        // Чтобы изменить записи, нам нужно их сначала получить из БД.
        ЭтотОбъект.Прочитать();
        
        Если ЭтотОбъект.Количество() > 0 Тогда
            
            // Проходим циклом по всем считанным записям
            Для Каждого Запись Из ЭтотОбъект Цикл
                // Устанавливаем признак неактуальности вместо удаления
                Запись.Актуальность = Ложь; 
            КонецЦикла;
            
            // Чтобы система не удалила записи после завершения процедуры, 
            // мы должны записать измененный набор самостоятельно.
            // Используем ДополнительныеСвойства, чтобы избежать бесконечного цикла.
            ЭтотОбъект.ДополнительныеСвойства.Вставить("ЭтоТехническаяЗапись", Истина);
            ЭтотОбъект.Записать(Истина); // Замещаем данные актуальными, но с пометкой
            
            // Отменяем стандартную операцию записи (удаления), так как мы уже все сделали
            Отказ = Истина;
            
        КонецЕсли;
        
    КонецЕсли;

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

Проблема бесконечной рекурсии

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

Особенности работы с методом Прочитать()

Выясним причину использования ЭтотОбъект.Прочитать(). В момент вызова события ПередЗаписью для пустого набора, объект ЭтотОбъект не содержит самих строк данных — он содержит только Отбор. Если мы не выполним чтение, цикл Для Каждого просто не отработает. Метод Прочитать() заполняет коллекцию набора данными из таблицы БД, которые соответствуют текущим измерениям в отборе.

Использование системного реквизита «Активность»

Рассмотрим альтернативный и зачастую более элегантный способ. У любого регистра сведений в 1С есть стандартное поле Активность. Если установить его в значение Ложь, запись перестает учитываться в виртуальных таблицах СрезПоследних и СрезПервых.

Плюсы использования стандартной «Активности»:

  1. Вам не нужно добавлять собственное измерение или реквизит Актуальность.
  2. Вам не придется переписывать все существующие запросы в системе, добавляя условие ГДЕ Актуальность = Истина. Платформа отфильтрует их автоматически.

Пример модификации кода под использование системного поля:


Для Каждого Запись Из ЭтотОбъект Цикл
    Запись.Активность = Ложь; // Запись остается в базе, но становится "невидимой" для итогов
КонецЦикла;

Нюансы работы с виртуальными таблицами СрезПоследних

Проанализируем ситуацию, если вы все же решили использовать собственное измерение Актуальность. Если в регистре для одного и того же набора измерений появится новая запись с Актуальность = Ложь, то стандартный СрезПоследних вернет именно ее (как самую свежую по времени). Если вы просто добавите в запрос условие ГДЕ Актуальность = Истина, то запрос не вернет ничего, даже если в истории есть предыдущая актуальная запись.

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

Рекомендация по использованию подписок на события

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

Помните также о правах доступа: при такой схеме работы пользователю, выполняющему «удаление», теперь потребуются права на Изменение записей регистра, так как фактически происходит обновление данных, а не их очистка.

← На главную