Как установить пометку удаления на подчиненные документы при удалении документа-основания в 1С 8.3?

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

В типовых и доработанных конфигурациях 1С часто возникает задача: при установке или снятии пометки удаления на документе-основании (например, РеализацияТоваровУслуг) необходимо автоматически выполнять аналогичное действие над подчиненными объектами (например, документами типа Отчет). Рассмотрим, как правильно реализовать данный механизм, какие подводные камни с транзакциями и блокировками данных могут возникнуть и как их избежать.

Особенности работы транзакций при каскадной обработке

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

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

  1. Атомарность операции: если при записи хотя бы одного подчиненного документа возникнет ошибка (например, сработает контроль остатков или проверка заполнения), вся транзакция откатится, и документ-основание также не будет помечен на удаление.
  2. Блокировки таблиц СУБД: чем больше подчиненных документов и чем сложнее их логика проведения/записи, тем дольше открыта транзакция. Это может вызывать взаимные блокировки (deadlocks) и замедлять параллельную работу других пользователей.
  3. Опасность рекурсивных вызовов: если в подчиненном документе прописано обратное обращение к родителю, можно получить бесконечный цикл.

Вариант 1. Синхронная обработка в подписке на событие или модуле объекта

Разберем базовый вариант реализации, подходящий для ситуаций, когда количество подчиненных документов невелико (1–3 документа на одно основание), а их запись не занимает много времени. Для сохранения модульности конфигурации рекомендуется использовать объект метаданных Подписка на событие (событие ПриЗаписи документа-основания).

Для корректного снятия с проведения и очистки движений при установке пометки удаления используем метод УстановитьПометкуУдаления() вместо простого присвоения реквизита. Защиту от повторных и циклических вызовов реализуем через свойство ДополнительныеСвойства.

Посмотрим на пример кода общего модуля:


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

Вариант 2. Асинхронная обработка через фоновые задания

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

Разберем по шагам схему с фоновым заданием:

  1. Документ-основание помечается на удаление и записывается мгновенно, без задержки на подчиненные структуры.
  2. В обработчике ПослеЗаписи или при интерактивном действии на клиенте стартует фоновое задание через метод ФоновыеЗадания.Выполнить().
  3. Фоновое задание в отдельной транзакции поочередно обрабатывает подчиненные документы.
  4. Если пользователю критично видеть актуальный статус сразу в форме, на клиенте можно вызвать метод ожидания завершения фонового задания с отображением состояния выполнения.

Посмотрим на структуру фоновой процедуры:


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

Важные рекомендации при реализации

Рассмотрим правила, которые необходимо соблюдать для надежной работы каскадных изменений:

  1. Сортировка ссылок в запросе: всегда используйте конструкцию УПОРЯДОЧИТЬ ПО Ссылка при выборке объектов для пакетного изменения. Если два пользователя одновременно запустят обработку пересекающихся документов в разном порядке, СУБД заблокирует ресурсы в противоположной последовательности, что приведет к исключению Deadlock.
  2. Управляемые блокировки: при необходимости одновременного чтения и записи используйте объект БлокировкаДанных в режиме управляемых блокировок для явного указания пространства блокировок.
  3. Бизнес-логика снятия пометки: помните, что при снятии пометки удаления с родительского документа могут восстановиться подчиненные документы, которые пользователь изначально пометил на удаление вручную. Если это критично, создают независимый регистр сведений, где фиксируется признак «Помечен автоматически родителем», и снимают пометку только с таких записей.
← На главную