При организации обмена данными между различными конфигурациями «1С:Предприятие 8» (например, между системами оперативного управления производством MES и комплексными учетными системами типа УПП или ERP) разработчики часто сталкиваются с ситуацией, когда перенесенные документы записываются, но не проводятся. Еще более запутанной проблема становится тогда, когда при штатном обмене документ успешно проводится, а в случае малейшего сетевого или системного сбоя «зависает» в состоянии «Записан» (не проведен) и не обрабатывается при последующих сеансах обмена.
В рамках этой статьи мы подробно разберем, почему возникает такое поведение, проанализируем внутренние механизмы обработки данных при обмене через XML/COM, выясним скрытые архитектурные ошибки в правилах конвертации данных и настроим корректную обработку исключительных ситуаций.
Выясним причину странного поведения системы, описанного автором темы. В журнале регистрации базы-приемника при успешном обмене для одного документа фиксируется 4 события: три изменения и одно проведение. При сбойном обмене фиксируются только два изменения, после чего документ навсегда остается непроведенным.
Для понимания этой аномалии проанализируем, как устроен типичный обмен по правилам конвертации. Процесс импорта и проведения документов обычно делится на два глобальных этапа:
ОбменДанными.Загрузка = Истина. При этом проверки бизнес-логики отключаются, а сам документ записывается в том режиме, в котором он пришел. Если в источнике он был проведен, в приемнике он также помечается как проведенный, но без фактического формирования движений (проводки не генерируются, если они не были явно перенесены в файле обмена).ПослеЗагрузкиДанных запускается специальный алгоритм, который обходит массив загруженных документов и последовательно проводит их в штатном режиме (когда ОбменДанными.Загрузка = Ложь).Если во время второго этапа происходит сбой (например, не хватает остатков на складе, заблокированы таблицы базы данных или сработала дата запрета изменения), проведение прерывается. Из-за этого документ остается в режиме «просто записан», а все его предыдущие движения (если они были) очищаются.
Разберем по шагам алгоритм проведения документов, который используется в правилах обмена. Внимательно посмотрим на пример кода, выполняемый в обработчике ПослеЗагрузкиДанных:
Для Каждого Документ Из Параметры.ДокументыДляПроведения Цикл
Если Не Документ.ЭтоНовый() Тогда
// Переполучаем объект, так как он мог быть изменен в процессе транзакции
ДокументОбъект = Документ.Ссылка.ПолучитьОбъект();
УстановитьОбменДаннымиЗагрузка(ДокументОбъект, Ложь);
Попытка
Если ДокументОбъект.ПроверитьЗаполнение() Тогда
ДокументОбъект.ДополнительныеСвойства.Вставить("ОтключитьМеханизмРегистрацииОбъектов", Истина);
ДокументОбъект.ДополнительныеСвойства.Вставить("НеПроверятьДатыЗапретаИзмененияДанных");
// Выполняем попытку проведения документа
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
КонецЕсли;
Исключение
// Сюда управление передается при ошибке проведения, но блок пуст!
КонецПопытки;
КонецЕсли;
КонецЦикла;
Проанализируем этот фрагмент кода. Конструкция Попытка ... Исключение ... КонецПопытки предназначена для обработки нештатных ситуаций. Однако в данном случае блок Исключение оставлен абсолютно пустым! Это критическая ошибка проектирования («говнокод»), которая приводит к следующим последствиям:
Записать(РежимЗаписиДокумента.Проведение) вызывает системную ошибку (например, конфликт блокировок СУБД, отрицательные остатки на складах, отсутствие прав доступа), платформа генерирует исключение.Для того чтобы решить проблему «тихих» ошибок, нам необходимо модифицировать алгоритм проведения. Обязательно фиксируйте подробные сведения о возникших ошибках в Журнале регистрации, чтобы администратор системы мог оперативно их устранить.
Перепишем блок проведения документов следующим образом:
Для Каждого Документ Из Параметры.ДокументыДляПроведения Цикл
Если Не Документ.ЭтоНовый() Тогда
ДокументОбъект = Документ.Ссылка.ПолучитьОбъект();
Если ДокументОбъект = Неопределено Тогда
Продолжить;
КонецЕсли;
// Отключаем режим загрузки для полноценного проведения и проверки бизнес-логики
УстановитьОбменДаннымиЗагрузка(ДокументОбъект, Ложь);
Попытка
// Проверяем заполнение обязательных реквизитов перед проведением
Если ДокументОбъект.ПроверитьЗаполнение() Тогда
ДокументОбъект.ДополнительныеСвойства.Вставить("ОтключитьМеханизмРегистрацииОбъектов", Истина);
ДокументОбъект.ДополнительныеСвойства.Вставить("НеПроверятьДатыЗапретаИзмененияДанных");
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Иначе
// Логируем ошибку проверки заполнения реквизитов
ЗаписатьВЖурналРегистрации(
"ОбменДанными.ПроведениеДокументов",
УровеньЖурналаРегистрации.Предупреждение,
ДокументОбъект.Метаданные(),
ДокументОбъект.Ссылка,
"Документ не прошел проверку заполнения реквизитов перед проведением!"
);
КонецЕсли;
Исключение
// Получаем подробное описание возникшей ошибки
ИнформацияОбОшибке = ИнформацияОбОшибке();
ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке);
// Фиксируем ошибку в системном Журнале регистрации базы-приемника
ЗаписатьВЖурналРегистрации(
"ОбменДанными.ПроведениеДокументов",
УровеньЖурналаРегистрации.Ошибка,
ДокументОбъект.Метаданные(),
ДокументОбъект.Ссылка,
"Не удалось провести документ в процессе обмена! Текст ошибки: " + ТекстОшибки
);
КонецПопытки;
КонецЕсли;
КонецЦикла;
После внедрения этого кода любые сбои при проведении документов перестанут быть «невидимыми». Вы сможете открыть Журнал регистрации в конфигурации УПП/ERP, отфильтровать записи по событию "ОбменДанными.ПроведениеДокументов" и сразу увидеть конкретную причину, почему документ не провелся (например, нехватка партий товара на складе).
Рассмотрим еще одну грубую ошибку, обнаруженную в правилах обмена (сообщение 17). В глобальном обработчике ПослеВыгрузкиДанных один и тот же цикл выгрузки по правилу конвертации выполняется дважды подряд без какого-либо логического обоснования:
// ВЫБОРКА И ВЫГРУЗКА - ПЕРВЫЙ РАЗ
Запрос.УстановитьПараметр("ЗаказыНаПроизводство", Параметры.ЗаказыНаПроизводство);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
ВыгрузитьПоПравилу(Выборка,,,, "ЗаказНаПроизводствоТолькоСтатус");
КонецЦикла;
// ВЫБОРКА И ВЫГРУЗКА - ВТОРОЙ РАЗ (ДУБЛИРОВАНИЕ!)
Запрос.УстановитьПараметр("ЗаказыНаПроизводство", Параметры.ЗаказыНаПроизводство);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
ВыгрузитьПоПравилу(Выборка,,,, "ЗаказНаПроизводствоТолькоСтатус");
КонецЦикла;
Выясним последствия такой избыточности. Во-первых, это приводит к двойному объему выгружаемых XML-данных. Во-вторых, база-приемник вынуждена обрабатывать и перезаписывать один и тот же объект несколько раз в течение одной транзакции обмена. Это существенно снижает производительность системы, перегружает жесткие диски серверов и увеличивает время блокировок таблиц СУБД.
Решение: Удалите второй (дублирующий) блок кода из правил конвертации в конфигурации «Конвертация данных 2.1». Оставьте только один цикл вызова функции ВыгрузитьПоПравилу.
Почему сбой проведения происходит эпизодически, а при ручном запуске обмена все работает нормально? Проанализируем ситуацию с точки зрения параллельного доступа к СУБД.
Когда обмен запускается автоматически по расписанию через регламентное задание, он работает в фоновом режиме на сервере «1С:Предприятия». Если в этот же момент пользователь вручную запускает аналогичный обмен или начинает интерактивно перепроводить документы закрытого периода, в СУБД (например, MS SQL Server или PostgreSQL) возникает конфликт блокировок таблиц.
В момент проведения документа система пытается наложить исключительную блокировку на регистры накопления и регистры бухгалтерии. Если параллельный сеанс уже удерживает эти таблицы, СУБД ожидает определенное время (таймаут), после чего аварийно завершает одну из транзакций, возвращая ошибку Deadlock (взаимная блокировка).
Из-за того, что в исходном алгоритме проведения ошибка блокировки гасилась пустым исключением, документ оставался незаписанным/непроведенным, а система даже не сообщала администратору о возникшем конфликте параллельных сеансов.
Для предотвращения подобных ситуаций рекомендуется:
Проанализируем интересное правило конвертации свойств (ПКС), описанное в сообщении 39. В правиле ЗаказНаПроизводствоТолькоСтатус сопоставляются свойства, у которых источник — реквизит Номер (тип Строка), а приемник — СБ_СтатусПроизводства (тип ПеречислениеСсылка.СБ_СтатусыЗаказовНаПроизводство). При этом в событии ПередВыгрузкой прописан код:
Значение = Источник.СБ_СтатусПроизводства;
Выясним сакральный смысл этой конструкции. Это классический обходной маневр в «Конвертации данных 2.1», применяемый в случаях несовпадения структуры метаданных источника и приемника:
СБ_СтатусПроизводства или его тип физически не совпадает с перечислением в УПП, напрямую сопоставить эти поля в визуальном редакторе КД 2.1 невозможно — конфигурация КД будет выдавать ошибки при проверке метаданных.Номер), но указывает в качестве приемника целевое поле СБ_СтатусПроизводства.ПередВыгрузкой разработчик принудительно подменяет выгружаемое значение, считывая нужные данные из источника напрямую. Программа выгружает в XML-узел правильное значение, которое затем без проблем считывается и записывается базой-приемником.Подобные конструкции абсолютно работоспособны, однако они усложняют чтение и поддержку правил обмена другими программистами. Если вы сопровождаете такую систему, обязательно комментируйте подобные «костыли» непосредственно в правилах конвертации.
Для того чтобы документы после обмена гарантированно переводились в статус проведенных и не зависали при сбоях, выполните следующие действия:
ПослеЗагрузкиДанных).Попытка ... Исключение на корректную конструкцию с записью ошибок в системный Журнал регистрации через функцию ПодробноеПредставлениеОшибки().ПослеВыгрузкиДанных на наличие дублирующихся циклов выгрузки и удалите лишние куски кода.