Разберем классическую и очень востребованную задачу, с которой сталкиваются разработчики при настройке интеграции между конфигурациями 1С с помощью программного продукта «1С:Конвертация данных 2.1». Представим ситуацию: мы настраиваем обмен между базами (например, между «1С:Бухгалтерией» и «БИТ.ФИНАНС»). Нам необходимо перегрузить справочник БанковскиеСчета, который содержит реквизит, ссылающийся на справочник Договоры.
Однако бизнес-логика требует, чтобы договоры определенных типов вообще не выгружались из источника как новые объекты и не изменялись в базе-приемнике. Вместо этого программа должна лишь попытаться найти существующий договор в приемнике по уникальному идентификатору (GUID) или коду и подставить его в поле банковского счета. Если же договор не найден, создавать его не нужно. В процессе реализации этой логики разработчики часто наступают на типичные «грабли»: получают ошибки компиляции, путают обработчики загрузки или сталкиваются с появлением «битых» ссылок. Выясним причины этих проблем и рассмотрим проверенные архитектурные варианты решения задачи.
Этот подход является наиболее изящным и правильным с точки зрения архитектуры «Конвертации данных». Мы разделим логику обработки договоров на два независимых Правила Конвертации Объектов (ПКО) и будем динамически переключаться между ними в зависимости от свойств выгружаемого объекта.
Разберем по шагам, как это настроить:
Договоры с наименованием Договоры_ПолныйПеренос. В нем настроим стандартный перенос всех необходимых реквизитов. Это правило будет использоваться по умолчанию для тех типов договоров, которые должны переноситься полностью.Договоры с наименованием Договоры_ТолькоПоиск. В настройках этого правила обязательно установим флаги: «Не замещать существующие объекты в приемнике» и «Не создавать новый объект в приемнике, если он НЕ найден». В ПКС (Правилах конвертации свойств) для этого ПКО мы настроим только ключевые свойства поиска (например, УникальныйИдентификатор или Код).БанковскиеСчета. Найдем ПКС для свойства ДоговорКонтрагента (или аналогичного реквизита, хранящего ссылку на договор).
Если Источник.Договор.ТипДоговора = Перечисления.ТипыДоговоров.СПокупателем Тогда
ИмяПКО = "Договоры_ПолныйПеренос";
Иначе
ИмяПКО = "Договоры_ТолькоПоиск";
КонецЕсли;
Проанализируем этот код. Переменная ИмяПКО является предопределенной в данном обработчике. Указывая в ней имя конкретного правила, мы заставляем платформу выгружать связанный объект по индивидуальной схеме. Если договор относится к типу, который не нужно перезаписывать или создавать, сработает правило Договоры_ТолькоПоиск. В XML-файл запишется только базовая информация для поиска, и при загрузке объект либо найдется и подставится, либо проигнорируется.
Иногда разработчики пытаются решить задачу «в лоб»: передать реквизиты объекта во временные параметры и обработать их при загрузке. Но здесь их часто подстерегает ошибка компиляции вида Получение элемента по индексу для значения не определено или Поле объекта не обнаружено при обращении к параметрам в обработчике ПриЗагрузкеОбъекта.
Выясним причину этой ошибки:
Источник при загрузке возникает ошибка, так как объекта Источник на стороне базы-приемника физически не существует. Вся информация считывается исключительно из XML-файла.При загрузке выполняется на самом начальном этапе разбора XML-узла текущего объекта. В этот момент структура параметров объекта ПараметрыОбъекта еще не инициализирована и не заполнена данными из файла. Попытка прочитать параметр вызывает фатальный сбой.Чтобы решить эту проблему правильно, выполним следующие действия:
бит_ТипДоговора), а поле приемника оставим пустым.ТипДоговораДляПоиска.При загрузке в событие «После загрузки» ПКО. На этом этапе XML-данные уже полностью прочитаны, и структура ПараметрыОбъекта доступна для полноценной работы.
ТипДоговора = "";
Если ПараметрыОбъекта <> Неопределено Тогда
ПараметрыОбъекта.Свойство("ТипДоговораДляПоиска", -ТипДоговора);
КонецЕсли;
Если ТипДоговора = "НеВыгружаемыйТип" Тогда
// Если объект уже существует в базе, откатим изменения реквизитов к исходным значениям из базы
Если Не Объект.ЭтоНовый() Тогда
// Читаем старое состояние объекта напрямую из базы данных
СтарыйОбъект = Объект.Ссылка.ПолучитьОбъект();
Если СтарыйОбъект <> Неопределено Тогда
ЗаполнитьЗначенияСвойств(Объект, СтарыйОбъект);
КонецЕсли;
КонецЕсли;
КонецЕсли;
Этот подход позволяет гибко управлять процессом перезаписи данных, не прерывая чтение самого файла обмена и не вызывая ошибок компиляции.
Рассмотрим ситуацию: мы использовали Способ 1 с флагом «Не создавать новый объект в приемнике, если он не найден». Что произойдет, если в базе-приемнике действительно нет такого договора?
Платформа успешно считает GUID договора из файла обмена, попытается найти объект по этому GUID, потерпит неудачу и откажется создавать новый элемент справочника Договоры. Однако при записи родительского объекта (банковского счета) в базу запишется пустая «битая» ссылка с этим GUID. В интерфейсе пользователя это отобразится в виде некрасивой надписи <Объект не найден>....
Чтобы избежать захламления базы недействительными ссылками, выполним программную очистку в ПКО родительского объекта. В обработчике события «После загрузки» ПКО БанковскиеСчета напишем следующий алгоритм проверки:
Если ЗначениеЗаполнено(Объект.ДоговорКонтрагента) Тогда
// Попытка получить реальный объект из базы данных по ссылке.
// Если ссылка "битая", метод ПолучитьОбъект() вернет Неопределено.
Если Объект.ДоговорКонтрагента.ПолучитьОбъект() = Неопределено Тогда
// Очищаем битую ссылку, записывая пустую ссылку справочника
Объект.ДоговорКонтрагента = Справочники.ДоговорыКонтрагентов.ПустаяСсылка();
КонецЕсли;
КонецЕсли;
Таким образом, мы гарантируем чистоту данных: если договор не был перенесен и не существовал в системе, поле останется аккуратно незаполненным вместо отображения технической ошибки.
Если мы точно знаем, что для определенных типов договоров нам не нужно передавать в XML-файл никаких их реквизитов (даже полей поиска), а достаточно выгрузить исключительно GUID для сопоставления, мы можем существенно ускорить процесс выгрузки и сократить размер файла обмена.
Для этого воспользуемся встроенным флагом оптимизации ТолькоПолучитьУзелСсылки. В ПКО БанковскиеСчета найдем ПКС для свойства ДоговорКонтрагента и в обработчике «Перед выгрузкой» пропишем следующий код:
Если Источник.ДоговорКонтрагента.ТипДоговора = Перечисления.ТипыДоговоров.НеВыгружаемый Тогда
ТолькоПолучитьУзелСсылки = Истина;
КонецЕсли;
При установке флага ТолькоПолучитьУзелСсылки = Истина конвертация пропустит детальный обход связанных реквизитов договора и запишет в файл обмена только базовую ссылку (GUID). Это минимизирует нагрузку на оперативную память сервера при выполнении обмена.
Выясним, как решить задачу без создания дополнительных ПКО. Мы можем перенести всю логику фильтрации и принятия решений о создании объектов непосредственно на сторону базы-приемника. Для этого используется обработчик «Поля поиска» в ПКО Договоры.
В этом обработчике нам доступны полезные переменные управления: СвойстваПоиска (структура свойств поиска), ПрекратитьПоиск и Импортировать. Предположим, мы передали тип договора в параметрах объекта (как описано в Способе 2). В обработчике «Поля поиска» ПКО Договоры мы пишем следующий код:
ТипДоговора = "";
Если ПараметрыОбъекта <> Неопределено Тогда
ПараметрыОбъекта.Свойство("ТипДоговораДляПоиска", ТипДоговора);
КонецЕсли;
Если ТипДоговора = "НеСоздавать" Тогда
// Пытаемся вручную найти объект по коду (или другому ключевому полю)
КодДоговора = СвойстваПоиска["Код"];
СсылкаНаОбъект = Справочники.ДоговорыКонтрагентов.НайтиПоКоду(КодДоговора);
Если Не ЗначениеЗаполнено(СсылкаНаОбъект) Тогда
// Если объект не найден в приемнике, отменяем стандартный механизм поиска
ПрекратитьПоиск = Истина;
// Запрещаем создавать новый объект и импортировать его из файла
Импортировать = Ложь;
КонецЕсли;
КонецЕсли;
Этот способ крайне удобен, так как вся логика сопоставления сосредоточена в одном месте. Нам не требуется плодить дублирующие ПКО, а управление созданием новых элементов гибко контролируется кодом на стороне базы-приемника.