При настройке обмена данными между конфигурациями 1С (например, между ЗУП и БП) с помощью «Конвертации данных 2.0» разработчики часто сталкиваются с ситуацией, когда документ в базе-приемнике успешно создается, но остается в статусе «Записан», даже если в базе-источнике он был «Проведен». Это происходит из-за особенностей работы платформы в режиме загрузки данных. В этой статье мы подробно разберем, почему стандартные механизмы не всегда срабатывают и как правильно реализовать синхронизацию состояний документа, избежав при этом «зацикливания» обмена.
Рассмотрим стандартный механизм работы универсальной обработки обмена. Когда данные попадают в базу-приемник, для каждого объекта устанавливается свойство ОбменДанными.Загрузка = Истина. Это критически важный флаг, который сообщает платформе и конфигурации, что объект записывается не пользователем, а программным механизмом. В этом режиме:
Проведен = Истина, платформа просто установит этот флаг в базе данных, но движения по регистрам (бухгалтерские проводки, записи в регистрах накопления) созданы не будут.Объект.Записать(РежимЗаписиДокумента.Проведение) в режиме загрузки выдаст ошибку, так как эти режимы являются взаимоисключающими.Проанализируем ситуацию: чтобы документ сделал движения, нам нужно вызвать его проведение программно, но делать это непосредственно внутри процесса загрузки объекта (например, в обработчике «После загрузки» ПКО) — плохая практика. Это может привести к падению производительности и конфликтам транзакций. Разберем наиболее правильный и надежный метод.
Наиболее элегантный способ — это сбор всех загруженных документов в специальный массив и их массовое проведение в самом конце сеанса обмена, когда основной процесс загрузки уже завершен. Выясним, как это реализовать по шагам в «Конвертации данных». Для тестирования написанного в правилах кода можно использовать инструмент для отладки обработчиков «Конвертации данных».
Шаг 1. Инициализация контейнера в обработчике «Перед загрузкой данных» (глобальный обработчик правил)
Нам нужно подготовить массив, в который мы будем складывать ссылки на документы. Используем для этого структуру Параметры, которая доступна на протяжении всего процесса обмена.
// Инициализируем массив для хранения ссылок на документы, требующие проведения
Если Параметры.Свойство("СписокДокументовДляПроведения") = Ложь Тогда
Параметры.Вставить("СписокДокументовДляПроведения", Новый Массив);
КонецЕсли;
Шаг 2. Наполнение массива в обработчике «После загрузки» (ПКО конкретного документа)
В правилах конвертации объектов (ПКО) для нужного нам типа документа пропишем логику добавления ссылки в наш список. Нам важно проводить только те документы, которые в источнике были проведены. Для более удобного написания кода в правилах обмена пригодится доработанная конфигурация «Конвертация данных» с подсказками.
// Проверяем, что объект — это документ и в источнике он был проведен
// При этом проверяем, что документ не помечен на удаление
Если Объект.Проведен И НЕ Объект.ПометкаУдаления Тогда
Параметры.СписокДокументовДляПроведения.Добавить(Объект.Ссылка);
КонецЕсли;
Шаг 3. Обработка массива в обработчике «После загрузки данных» (глобальный обработчик)
Когда все объекты из файла загружены в базу, наступает время «магии». Мы перебираем наш массив и проводим документы уже в обычном режиме (без флага Загрузка = Истина).
Для Каждого СсылкаНаДокумент Из Параметры.СписокДокументовДляПроведения Цикл
ДокументОбъект = СсылкаНаДокумент.ПолучитьОбъект();
Если ДокументОбъект <> Неопределено Тогда
// Отключаем режим загрузки, чтобы отработали механизмы проведения
ДокументОбъект.ОбменДанными.Загрузка = Ложь;
Попытка
// Пытаемся провести документ
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение, РежимСообщенийОбъекта.Нет);
Исключение
// Если проведение не удалось (например, не хватает остатков),
// записываем ошибку в журнал регистрации или выводим сообщение
ЗаписьЖурналаРегистрации("ОбменДанными.Проведение", УровеньЖурналаРегистрации.Ошибка, , ,
"Не удалось провести документ: " + Строка(СсылкаНаДокумент) + ". Ошибка: " + ОписаниеОшибки());
КонецПопытки;
КонецЕсли;
КонецЦикла;
Рассмотрим типичную ловушку. Если в обеих базах настроены подписки на событие ПриЗаписи, которые инициируют выгрузку документа обратно, возникнет бесконечный цикл: База А отправляет в Базу Б -> База Б записывает и отправляет в Базу А -> База А записывает и так далее.
Проанализируем способы защиты:
Если ИмяПользователя() = "ExchangeUser" Тогда Возврат; КонецЕсли;.Записать() можно добавить флаг:
Объект.ДополнительныеСвойства.Вставить("ЗагрузкаИзОбмена", Истина);.
В подписке на событие, которая отвечает за выгрузку, проверяйте наличие этого свойства.ОбменДанными.Загрузка в коде выгрузки. Если объект записывается из обмена, его не нужно регистрировать к повторной отправке.Когда мы переносим документы между разными конфигурациями, например, из Зарплаты и управления персоналом (ЗУП) в Бухгалтерию предприятия (БП), возникает проблема «недостающих данных». Документ в БП может требовать заполнения счетов учета, аналитики номенклатуры или статей затрат, которых просто нет в ЗУП.
В этом случае в обработчике ПослеЗагрузки (ПКО) нам необходимо программно дозаполнить объект значениями по умолчанию:
// Пример заполнения счетов учета для БП
Если ТипЗнч(Объект) = Тип("ДокументОбъект.РеализацияТоваровУслуг") Тогда
// Вызываем стандартную процедуру заполнения счетов, если она есть в конфигурации
ЗаполнениеСчетовУчета.Заполнить(Объект);
КонецЕсли;
Без такого дозаполнения метод Записать(РежимЗаписиДокумента.Проведение) вернет ошибку, и документ останется непроведенным, что приведет к расхождению данных между базами.
Не забудем про сценарий, когда в источнике документ был отменен с проведения или помечен на удаление. Если вы просто переносите реквизит ПометкаУдаления, платформа установит его в приемнике, но если документ был ранее проведен, он так и останется в состоянии «Проведен + Пометка на удаление» (в некоторых случаях), либо просто не сбросит свои движения.
Разберем, как это обработать. В том же цикле проведения в конце загрузки добавьте проверку:
Если ДокументОбъект.ПометкаУдаления Тогда
// Если в базе приемнике документ был проведен, снимаем с проведения
Если ДокументОбъект.Проведен Тогда
ДокументОбъект.Записать(РежимЗаписиДокумента.ОтменаПроведения);
КонецЕсли;
ИначеЕсли НЕ Объект.Проведен И ДокументОбъект.Проведен Тогда
// Если в источнике сняли с проведения, а в приемнике он еще проведен
ДокументОбъект.Записать(РежимЗаписиДокумента.ОтменаПроведения);
КонецЕсли;
Используя предложенные методы, мы обеспечиваем целостность данных в обеих информационных системах, гарантируем актуальность бухгалтерских и управленческих итогов и исключаем риск возникновения бесконечных циклов обмена.