Как избежать дублирования документов в РИБ при их программном создании во время обмена?

Программист 1С v8.3 (Управляемые формы) Управленческий учет Промышленность, строительство и АПК
← На главную

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

Анализ проблемы: почему возникают дубли?

Рассмотрим исходную ситуацию. У нас есть двусторонний обмен РИБ между центральной базой и подчиненным узлом. Схема, приводящая к ошибке, выглядит так:

  1. В подчиненном узле пользователь создает документ Рапорт, в котором реквизит-ссылка на ОтчетПроизводстваЗаСмену остается незаполненным.
  2. В ходе обмена этот Рапорт выгружается в центральную базу.
  3. В ЦБ в модуле плана обмена, в обработчике события ПриПолученииДанныхОтПодчиненного, написан код, который проверяет реквизит ОтчетПроизводстваЗаСмену. Если он пустой, система создает новый документ ОтчетПроизводстваЗаСмену, записывает его и проставляет ссылку в полученный Рапорт.
  4. На этом этапе в ЦБ все выглядит корректно: есть Рапорт с заполненной ссылкой.
  5. Проблема: при следующем сеансе обмена из подчиненного узла снова приходит тот же самый Рапорт, но в его "старой" версии — с пустой ссылкой. Обработчик в ЦБ снова срабатывает и создает еще один, дублирующий ОтчетПроизводстваЗаСмену.

Ключевая причина дублирования кроется в том, что объект ЭлементДанных, с которым мы работаем в процедуре ПриПолученииДанныхОтПодчиненного, — это еще не объект базы данных. Это временный объект, прочитанный из XML-файла сообщения обмена. Логика создания нового отчета выполняется повторно, потому что система каждый раз получает исходные, не измененные в ЦБ данные из узла.

Пример кода, который приводит к проблеме:


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

Такой подход, когда бизнес-логика встраивается напрямую в механизм обмена, считается рискованным. Он может приводить не только к дублированию, но и к зацикливанию обменов и нарушению целостности данных.

Решение 1: Проверка существования объекта в базе данных

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

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

Пошаговый алгоритм:

  1. Внутри обработчика, когда приходит ЭлементДанных типа Рапорт, получаем его ссылку (уникальный идентификатор).
  2. Выполняем запрос к базе данных, чтобы найти документ Рапорт с таким же УИДом.
  3. Если документ найден, получаем из него значение реквизита ДокументОПС.
  4. Если ссылка на ОтчетПроизводстваЗаСмену в существующем документе уже заполнена, значит, мы его обработали ранее. В этом случае мы просто присваиваем эту ссылку нашему загружаемому ЭлементДанных, чтобы не потерять ее при перезаписи объекта. Новый отчет создавать не нужно.
  5. Если документ в базе не найден (первая загрузка) или найден, но ссылка пуста, тогда выполняем логику создания нового отчета.

Пример исправленного кода:


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

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

        // Если дошли сюда, значит, отчет нужно создать
        ЭлементДанных.Записать(РежимЗаписиДокумента.Запись);
        ДокОПС = ЭлементДанных.ЗаполнитьОтчетПроизводстваЗаСменуПоРапорту(ЭлементДанных.Ссылка, ЭлементДанных.ДокументОПС.Ссылка);
        ДокОПС.Записать(РежимЗаписиДокумента.Запись);
        ЭлементДанных.ДокументОПС = ДокОПС.Ссылка;
        ЭлементДанных.Записать(РежимЗаписиДокумента.Запись);

    КонецЕсли;
    // ...
КонецПроцедуры

Этот подход решает проблему дублирования, но все еще оставляет сложную бизнес-логику внутри системного механизма обмена.

Решение 2: Отложенная обработка (архитектурно правильный подход)

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

Суть подхода:

  1. Упрощаем обмен. Убираем всю сложную логику из обработчика ПриПолученииДанныхОтПодчиненного. Его задача — просто загрузить документ Рапорт в том виде, в котором он пришел, то есть с пустым реквизитом ДокументОПС.
  2. Создаем регламентное задание. Разрабатываем отдельный механизм (например, регламентное задание), который будет запускаться по расписанию (например, каждые 10-15 минут, после завершения сеансов обмена).
  3. Обрабатываем данные. Это регламентное задание ищет в базе данных все документы Рапорт, у которых не заполнен реквизит ДокументОПС. Для каждого найденного документа оно выполняет логику создания и привязки ОтчетаПроизводстваЗаСмену.

Преимущества этого подхода:

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

После того как мы в центральной базе создали ОтчетПроизводстваЗаСмену и заполнили ссылку в Рапорте, это изменение существует только в ЦБ. В подчиненном узле Рапорт остается в исходном состоянии. Чтобы синхронизировать данные, измененный Рапорт нужно отправить обратно.

Для этого есть два основных механизма:

  1. ОтправкаНазад = Истина

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

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

  2. ПланыОбмена.ЗарегистрироватьИзменения()

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

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

    Авторегистрация для документа ОтчетПроизводстваЗаСмену также зарегистрирует его к выгрузке, если она включена в плане обмена.

Итог: Самый надежный и рекомендуемый способ — выносить бизнес-логику из процедур обмена в регламентные задания. Если же требуется быстрое решение, добавьте в обработчик обмена проверку на существование уже обработанного объекта в базе данных, чтобы избежать повторного создания связанных документов.

← На главную