Как решить проблему с незаполненным реквизитом "Контрагент" в документе "Счет на оплату" в обработчике "ПослеЗагрузки" (Конвертация Данных 2.1)

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

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

В этой статье мы подробно разберем, почему возникает эта ошибка, как работает механизм сопоставления объектов КД 2.1 в оперативной памяти и какие архитектурные подходы позволят решить эту задачу элегантно и без сбоев.

Выясняем техническую причину: почему реквизит пуст в "ПослеЗагрузки"?

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

В этот момент поведение ссылочных полей (таких как Контрагент) может зависеть от нескольких факторов:

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

Разберем основные варианты решения этой проблемы — от быстрой настройки правил до лучших архитектурных практик без написания лишнего кода.

Способ решения 1: Настройка порядка ПКС и принудительная выгрузка объекта

Если вам обязательно нужно выполнять код в обработчике ПослеЗагрузки документа, выполним два простых шага в настройках КД 2.1:

Шаг 1. Изменение порядка ПКС. Откроем ПКО нашего документа (например, «СчетНаОплатуПокупателю»). В табличной части со свойствами (ПКС) найдем реквизит Контрагент и с помощью стрелок переместим его в самый верх списка, чтобы он шел раньше, чем реквизит ДоговорКонтрагента. Это гарантирует, что при чтении файла ссылка на контрагента запишется в объект в первую очередь.

Шаг 2. Принудительная выгрузка объекта. По умолчанию КД может выгружать контрагента «только по ссылке», из-за чего в приемнике он создается позже самого документа. Чтобы заставить систему выгрузить и создать контрагента раньше, откроем ПКС Контрагент в документе «СчетНаОплатуПокупателю» и в событии Перед выгрузкой напишем следующий код:


// Принудительно выгружаем объект целиком, а не только его ссылку
ВыгрузитьОбъект = Истина;

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

Способ решения 2: Декларативное создание договора (без программирования в приемнике)

Написание сложного программного кода по созданию элементов справочников в обработчиках загрузки документов считается плохой практикой (антипаттерном) в Конвертации Данных 2.1. Платформа предоставляет мощный встроенный функционал для декларативного решения таких задач.

Вместо ручного написания кода в приемнике, настроим автоматическое создание договора через ПКО. Посмотрим, как это сделать по шагам:

  1. Создадим новое ПКО для справочника «ДоговорыКонтрагентов» (назовем его, например, ДоговорыПриЗагрузкеДокументов). Источник оставляем пустым.
  2. В ПКО договоров снимем флаги «Искать по УИД» и настроим поля поиска: Владелец (Контрагент), Организация, Наименование. Это позволит системе искать существующий договор в базе-приемнике по ключевым полям.
  3. В ПКО договоров для ПКС настроим заполнение по умолчанию:
    • Для свойства ВидДоговора в событии Перед выгрузкой укажем: Значение = "СПокупателем";
    • Для свойства Наименование укажем: Значение = "Основной договор";
  4. В ПКО нашего документа «Счет на оплату» для свойства ДоговорКонтрагента укажем созданное нами правило конвертации ДоговорыПриЗагрузкеДокументов.
  5. В ПКС ДоговорКонтрагента в документе в обработчике Перед выгрузкой передадим сам источник в качестве значения, чтобы КД могла вытащить из него владельца и организацию:
    
    // Передаем весь объект источника (документ) в качестве источника для договора
    Значение = Источник;
    

При такой настройке КД 2.1 при загрузке документа сама проанализирует контрагента и организацию документа, найдет по этим полям существующий договор у контрагента в базе-приемнике, а если его нет — создаст новый «Основной договор» с правильным владельцем. И все это произойдет автоматически, без написания транзакционного кода создания объектов в обработчиках загрузки.

Способ решения 3: Отложенное создание договоров через глобальные обработчики

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

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

Разберем этот алгоритм реализации по шагам:

Шаг 1. Инициализация параметров. В глобальном обработчике конвертации Перед загрузкой данных создадим временную таблицу или массив для накопления загруженных счетов:


// Инициализируем массив для хранения ссылок на созданные счета
Параметры.Вставить("ЗагруженныеСчета", Новый Массив);

Шаг 2. Накопление объектов при загрузке. В ПКО документа «Счет на оплату» в обработчике ПослеЗагрузки вместо непосредственного создания договора просто добавляем ссылку на загружаемый документ в наш глобальный массив параметров:


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

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


// Проверяем наличие накопленных документов
Если Параметры.Свойство("ЗагруженныеСчета") И Параметры.ЗагруженныеСчета.Количество() > 0 Тогда

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

КонецЕсли;

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

← На главную