Почему при обмене через XML загружаются не все документы в 1С и как это исправить?

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

Перенос данных между конфигурациями 1С с помощью правил, созданных в Конвертации данных 2.1, — это стандартная задача для разработчика. Однако иногда в процессе обмена происходят крайне загадочные вещи. Например, выгрузка из базы-источника проходит полностью и без ошибок, файл обмена XML содержит абсолютно все выгруженные объекты, но в базу-приемник попадает лишь малая часть документов (например, первые 20 штук), а остальные просто игнорируются. При этом стандартный Журнал регистрации в 1С молчит и не показывает никаких критических ошибок.

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

В чем суть проблемы?

Рассмотрим классическую ситуацию: выполняется переход с конфигурации «Комплексная автоматизация 1.1» на «Управление нашей фирмой 1.6». Для переноса используется стандартная обработка «Универсальный обмен данными XML». При анализе XML-файла выгрузки выясняется, что в нем присутствуют сотни документов (например, Расход со счета), но после загрузки в целевой базе создается лишь пара десятков документов за начальный период. Причем документы абсолютно идентичны по структуре, отличаются лишь датами, суммами и уникальными идентификаторами.

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

Давайте проанализируем код обработчиков, который использовался при конвертации объектов.

Анализ кода в обработчиках Конвертации данных

В правилах обмена были определены три обработчика. Давайте посмотрим на код каждого из них.

В обработчике «Перед загрузкой данных» инициализируется таблица для хранения загружаемых документов:


ТаблицаДокументов = Новый ТаблицаЗначений;
ТаблицаДокументов.Колонки.Добавить("Документ");
ТаблицаДокументов.Колонки.Добавить("ДатаВремя");
Параметры.Вставить("ТаблицаДокументов", ТаблицаДокументов);

В обработчике «После загрузки объекта» (для документов) происходит добавление ссылок на загруженные объекты в эту таблицу, чтобы впоследствии провести их:


Если НЕ ЗначениеЗаполнено(Объект.Автор) Тогда
    Объект.Автор = Пользователи.ТекущийПользователь;
КонецЕсли;

МетаданныеОбъекта = Объект.Метаданные;

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

В обработчике «После загрузки данных» система перебирает собранные документы, пытается их провести и записывает ошибки проведения в Журнал регистрации:


ТаблицаНеудачногоПроведения = Новый ТаблицаЗначений;
ТаблицаНеудачногоПроведения.Колонки.Добавить("Документ");
ТаблицаНеудачногоПроведения.Колонки.Добавить("Причина");

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

Об = Документы.Событие.СоздатьДокумент;
ЗаполнениеОбъектовУНФ.ЗаполнитьДокумент(Об, Неопределено, "ОбработчикЗаполнения");
Об.Дата = ТекущаяДата;
Содержание = "";
Для Каждого Стр ИЗ ТаблицаНеудачногоПроведения Цикл
    Содержание = Содержание + СОКРЛП(Стр.Документ) + " # " + Стр.Причина + Символы.ВК;
КонецЦикла;
Об.Содержание = Содержание;
Об.Записать;

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

Выясняем причины сбоя обмена

Проанализируем детально каждую из трех причин, по которым перенос данных останавливается на полуслове.

Причина №1. Критическая ошибка в блоке Исключение (несуществующее свойство ссылки)

Обратим внимание на строку записи в Журнал регистрации внутри блока Исключение:


ЗаписьЖурналаРегистрации("Загрузка документов", УровеньЖурналаРегистрации.Ошибка, Строка.Документ.Метаданные, Строка.Документ, ПричинаОшибки);

Переменная Строка.Документ содержит ссылку на документ (тип ДокументСсылка). В платформе 1С:Предприятие у ссылочных типов данных нет свойства Метаданные. Для получения метаданных у ссылки необходимо использовать метод — то есть функцию со скобками: Метаданные().

Поскольку скобки были упущены, при попытке обратиться к свойству Строка.Документ.Метаданные платформа генерирует фатальную ошибку: «Поле объекта не обнаружено (Метаданные)».

Это приводит к следующему сценарию:

  1. Один из документов не может провестись (например, из-за отсутствия остатков на складе или не заполненного обязательного реквизита в новой базе).
  2. Система заходит в блок Исключение.
  3. Вместо того чтобы тихо записать ошибку проведения в Журнал регистрации и продолжить цикл, код падает на строке вызова ЗаписьЖурналаРегистрации с критической системной ошибкой.
  4. Программа аварийно прекращает выполнение всего обработчика После загрузки данных.

Причина №2. Пустая ссылка нового объекта при ПолучитьСсылкуНового

Вторая уязвимость кроется в обработчике «После загрузки объекта»:


Если Объект.ЭтоНовый Тогда
    НоваяСтрока.Документ = Объект.ПолучитьСсылкуНового;

Метод ПолучитьСсылкуНового возвращает ссылку, только если она была ранее явно установлена для этого объекта (например, методом УстановитьСсылкуНового()). При автоматическом чтении XML платформой эта ссылка зачастую не инициализирована. В результате в таблицу документов записывается пустая ссылка.

Когда выполнение переходит в глобальный обработчик «После загрузки данных», управление доходит до строки:

Объект = Строка.Документ.ПолучитьОбъект;

Для пустой ссылки метод ПолучитьОбъект() возвращает значение Неопределено. Следующая за этим попытка выполнить метод Объект.Записать(...) вызывает закономерную ошибку: «Метод объекта не обнаружен (Записать)», управление передается в многострадальный блок Исключение, где из-за ошибки №1 процесс окончательно аварийно завершается.

Причина №3. Механизм транзакций при обмене XML

Почему автор столкнулся с тем, что загрузилось ровно 20 документов?

При использовании обработки «Универсальный обмен данными XML» по умолчанию включена опция «Использовать транзакции» (с определенным размером порции объектов, например, 100 или 200).

Когда при проведении очередного документа происходит фатальный сбой в коде 1С, транзакция загрузки текущей порции объектов откатывается целиком. В базе сохраняются только те порции данных, которые были успешно обработаны и зафиксированы в предыдущих транзакциях. Именно поэтому в базе остается лишь часть документов, а остальные бесследно исчезают из процесса импорта.

Решение проблемы: пишем правильный и безопасный код

Для того чтобы исправить логику обмена и исключить скрытые падения системы, перепишем обработчики событий по всем правилам безопасной разработки в 1С.

Шаг 1. Исправление обработчика «После загрузки объекта»

Мы должны гарантировать, что в нашу таблицу документов всегда записывается корректная, заполненная ссылка, даже если объект создается впервые. Перепишем этот фрагмент кода:


Если НЕ ЗначениеЗаполнено(Объект.Автор) Тогда
    Объект.Автор = Пользователи.ТекущийПользователь;
КонецЕсли;

МетаданныеОбъекта = Объект.Метаданные();

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

Шаг 2. Исправление обработчика «После загрузки данных»

Теперь исправим финальный обработчик. Мы должны безопасно извлекать метаданные, проверять объект на равенство Неопределено и использовать корректный вызов метода Метаданные() со скобками:


ТаблицаНеудачногоПроведения = Новый ТаблицаЗначений;
ТаблицаНеудачногоПроведения.Колонки.Добавить("Документ");
ТаблицаНеудачногоПроведения.Колонки.Добавить("Причина");

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

// Создаем документ Событие для фиксации итогов загрузки
Об = Документы.Событие.СоздатьДокумент();
ЗаполнениеОбъектовУНФ.ЗаполнитьДокумент(Об, Неопределено, "ОбработчикЗаполнения");
Об.Дата = ТекущаяДата();
Содержание = "";

Для Каждого Стр ИЗ ТаблицаНеудачногоПроведения Цикл
    Содержание = Содержание + СокрЛП(Стр.Документ) + " # " + Стр.Причина + Символы.ВК;
КонецЦикла;

Об.Содержание = Содержание;
Об.Записать();

Дополнительные рекомендации по диагностике обмена

Если вы исправили код, но данные все равно переносятся не полностью, обратите внимание на следующие параметры настройки:

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

← На главную