Почему после обмена данными документы остаются непроведенными и как это исправить

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

При организации обмена данными между различными конфигурациями «1С:Предприятие 8» (например, между системами оперативного управления производством MES и комплексными учетными системами типа УПП или ERP) разработчики часто сталкиваются с ситуацией, когда перенесенные документы записываются, но не проводятся. Еще более запутанной проблема становится тогда, когда при штатном обмене документ успешно проводится, а в случае малейшего сетевого или системного сбоя «зависает» в состоянии «Записан» (не проведен) и не обрабатывается при последующих сеансах обмена.

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

Анализ симптомов: почему лог обмена фиксирует несколько изменений

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

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

  1. Загрузка данных (первый этап): Объект считывается из файла обмена (или COM-соединения) и записывается в информационную базу. Чтобы загрузка прошла максимально быстро и без конфликтов блокировок, запись выполняется со специальным свойством ОбменДанными.Загрузка = Истина. При этом проверки бизнес-логики отключаются, а сам документ записывается в том режиме, в котором он пришел. Если в источнике он был проведен, в приемнике он также помечается как проведенный, но без фактического формирования движений (проводки не генерируются, если они не были явно перенесены в файле обмена).
  2. Отложенное проведение (второй этап): Чтобы документ сформировал актуальные движения по регистрам в базе-приемнике с учетом ее локальной специфики и остатков, его необходимо полноценно провести. Для этого в глобальном обработчике правил конвертации ПослеЗагрузкиДанных запускается специальный алгоритм, который обходит массив загруженных документов и последовательно проводит их в штатном режиме (когда ОбменДанными.Загрузка = Ложь).

Если во время второго этапа происходит сбой (например, не хватает остатков на складе, заблокированы таблицы базы данных или сработала дата запрета изменения), проведение прерывается. Из-за этого документ остается в режиме «просто записан», а все его предыдущие движения (если они были) очищаются.

Главный виновник проблемы: «тихие» пустые исключения

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


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

Проанализируем этот фрагмент кода. Конструкция Попытка ... Исключение ... КонецПопытки предназначена для обработки нештатных ситуаций. Однако в данном случае блок Исключение оставлен абсолютно пустым! Это критическая ошибка проектирования («говнокод»), которая приводит к следующим последствиям:

Как исправить пустые исключения и организовать логирование ошибок

Для того чтобы решить проблему «тихих» ошибок, нам необходимо модифицировать алгоритм проведения. Обязательно фиксируйте подробные сведения о возникших ошибках в Журнале регистрации, чтобы администратор системы мог оперативно их устранить.

Перепишем блок проведения документов следующим образом:


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

После внедрения этого кода любые сбои при проведении документов перестанут быть «невидимыми». Вы сможете открыть Журнал регистрации в конфигурации УПП/ERP, отфильтровать записи по событию "ОбменДанными.ПроведениеДокументов" и сразу увидеть конкретную причину, почему документ не провелся (например, нехватка партий товара на складе).

Устранение дублирования кода выгрузки в ПослеВыгрузкиДанных

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


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

// ВЫБОРКА И ВЫГРУЗКА - ВТОРОЙ РАЗ (ДУБЛИРОВАНИЕ!)
Запрос.УстановитьПараметр("ЗаказыНаПроизводство", Параметры.ЗаказыНаПроизводство);
Выборка = Запрос.Выполнить().Выбрать();    
Пока Выборка.Следующий() Цикл
    ВыгрузитьПоПравилу(Выборка,,,, "ЗаказНаПроизводствоТолькоСтатус");
КонецЦикла;

Выясним последствия такой избыточности. Во-первых, это приводит к двойному объему выгружаемых XML-данных. Во-вторых, база-приемник вынуждена обрабатывать и перезаписывать один и тот же объект несколько раз в течение одной транзакции обмена. Это существенно снижает производительность системы, перегружает жесткие диски серверов и увеличивает время блокировок таблиц СУБД.

Решение: Удалите второй (дублирующий) блок кода из правил конвертации в конфигурации «Конвертация данных 2.1». Оставьте только один цикл вызова функции ВыгрузитьПоПравилу.

Конфликты параллельной работы и транзакционные блокировки

Почему сбой проведения происходит эпизодически, а при ручном запуске обмена все работает нормально? Проанализируем ситуацию с точки зрения параллельного доступа к СУБД.

Когда обмен запускается автоматически по расписанию через регламентное задание, он работает в фоновом режиме на сервере «1С:Предприятия». Если в этот же момент пользователь вручную запускает аналогичный обмен или начинает интерактивно перепроводить документы закрытого периода, в СУБД (например, MS SQL Server или PostgreSQL) возникает конфликт блокировок таблиц.

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

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

Для предотвращения подобных ситуаций рекомендуется:

Особые приемы КД 2.1: Конструкция «Номер -> СБ_СтатусПроизводства»

Проанализируем интересное правило конвертации свойств (ПКС), описанное в сообщении 39. В правиле ЗаказНаПроизводствоТолькоСтатус сопоставляются свойства, у которых источник — реквизит Номер (тип Строка), а приемник — СБ_СтатусПроизводства (тип ПеречислениеСсылка.СБ_СтатусыЗаказовНаПроизводство). При этом в событии ПередВыгрузкой прописан код:

Значение = Источник.СБ_СтатусПроизводства;

Выясним сакральный смысл этой конструкции. Это классический обходной маневр в «Конвертации данных 2.1», применяемый в случаях несовпадения структуры метаданных источника и приемника:

  1. Если у объекта-источника в MES-системе нет реквизита с именем СБ_СтатусПроизводства или его тип физически не совпадает с перечислением в УПП, напрямую сопоставить эти поля в визуальном редакторе КД 2.1 невозможно — конфигурация КД будет выдавать ошибки при проверке метаданных.
  2. Чтобы обойти это ограничение, разработчик создает ПКС для любого гарантированно существующего стандартного свойства (например, строкового реквизита Номер), но указывает в качестве приемника целевое поле СБ_СтатусПроизводства.
  3. В обработчике ПередВыгрузкой разработчик принудительно подменяет выгружаемое значение, считывая нужные данные из источника напрямую. Программа выгружает в XML-узел правильное значение, которое затем без проблем считывается и записывается базой-приемником.

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

Резюме и рекомендации

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

  1. Откройте правила конвертации данных (xml-файл) в конфигурации «1С:Конвертация данных 2.1».
  2. Найдите глобальный алгоритм проведения документов (обычно вызываемый в обработчике ПослеЗагрузкиДанных).
  3. Замените пустые блоки Попытка ... Исключение на корректную конструкцию с записью ошибок в системный Журнал регистрации через функцию ПодробноеПредставлениеОшибки().
  4. Проверьте обработчик ПослеВыгрузкиДанных на наличие дублирующихся циклов выгрузки и удалите лишние куски кода.
  5. Обеспечьте разграничение времени работы автоматических сеансов обмена и ручных корректировок документов пользователями для минимизации транзакционных блокировок на уровне СУБД.
← На главную