Несколько раз приходилось решать задачу синхронизации справочников в двух базах через сервис. При этом исходные конфигурации корректировать нельзя или крайне нежелательно. В процессе получил код, который показался мне интересным. Поэтому решил разместить свою первую публикацию. Об актуальности и полезности судите сами.
Решение показалось мне довольно универсальным. Оно состоит из двух расширений и обработки для регламентного запуска в источнике. Для меня самым интересным в результате оказалось следующее. Написанный для взаимодействия двух конкретных конфигураций код оказался пригодным к использованию для разных комбинаций источника и приемника. Набор справочников тоже можно расширять, внося небольшие и понятные изменения в состав плана обмена и код обработки.
Затем потребовалась синхронизация с таблицами сторонних баз, которые можно было привязать как внешние источники. Тогда я понял, что для обмена со всем что основано на использовании SQL этот код тоже подходит. Конечно, в этом случае не удалось обойтись только расширениями. Источники пришлось честно привязать к конфигурации приемнику. Но это не критичное изменение конфигурации, которое всех устроило. Еще потребовалось предусмотреть регистрацию изменений в источнике. Код обработки также пришлось немного изменить и сама обработка уже запускалась в приемнике. В этом случае использование сервиса стало не актуальным, но его обработчик прекрасно справился со своей задачей. В целом, схема с минимальными изменениями заработала и показала хорошие результаты. Стало возможным организовать синхронизацию с таблицами внешнего источника, просто написав запросы к ним.
Это не решение для пользователя. Скорее, небольшая адаптируемая библиотечка, с помощью которой опытный программист может очень быстро создать сложный обмен по справочникам. Поэтому заранее извиняюсь перед неискушенными читателями. Ниже будет приведено подробное описание и текст будет изобиловать техническими подробностями. Дальнейшее предназначено исключительно для программистов.
Краткое описание:
В приведенном примере, для простоты, представлен обмен между двумя конфигурациями 1С. Демонстрация работы с внешним источником требует наличие оного. Писать запросы для него и поставлять сам источник в виде файла хлопотно. Код используется, практически тот же, но ситуация 1С->1С проще для понимания механизма работы с данным кодом.
В качестве источника, выступает свежая конфигурация Документооборот 3.0.4.41, а приемником является УНФ - старая учебная конфигурация 1.6.19.150. Синхронизируются два справочника ДО - "Контрагенты" и "ДокументыПредприятия" со справочниками УНФ, соответственно, "Контрагенты" и "ДоговорыКонтрагентов". Синхронизация справочников "Организации" не проводилась, т.к. эти справочники проще и надежнее синхронизировать руками. В данном примере предполагается, что они синхронизированы по коду.
Естественно, перед запуском реальной синхронизации требуется первоначальное сопоставление объектов, но это тема отдельной беседы. Для демонстрации возможностей движка это не требуется. Если будет проявлен интерес к этой теме и у меня будет свободное от работы время - сделаю отдельную публикацию.
1. Регистрация изменений
Для накопления информации об изменениях используется план обмена "ОбменСправочниками", который находится в расширении "ОбменИсточник". Узел обмена с кодом "ОбменУНФ" создается автоматически при первом запуске обработки. Указанные справочники уже добавлены в состав плана обмена в расширении. Регистрация изменений в простом случае может производиться автоматически. В случае справочника ДО "ДокументыПредприятия" возникла необходимость в выборочной регистрации с использованием дополнительного реквизита. Регистрация реализована в расширении "ОбменИсточник" для события "ПриЗаписи". В обработчике события также есть ссылка на имя узла "ОбменУНФ" - прошу обратить внимание.
2. Обработка пакета:
Движком является HTTP-сервис "StartCreateNewRecords", реализованный в расширении "ОбменПриемник". Он принимает запрос в формате JSON определенной структуры. Читает JSON, извлекая пакет в виде структуры, из которой узнает имя справочника и получает массив структур с новыми значениями реквизитов элементов.
Далее вступает в дело обработчик пакета. Он умеет искать существующие и содавать новые элементы. Присваивает значения реквизитам простых типов, узнает перечисления и пытается распознать ссылочные типы. Строки неограниченной длины надо не забывать выражать в запросе как строки определенной длины.
Для синхронизации ссылочных реквизитов используется дополнительное свойство "ЗначениеИдентификатораИсточника". Оно также будет автоматически создано в приемнике при первом запуске обмена. Да простят меня любители более традиционных способов с использованием регистра "СоответствияОбъектовИнформационныхБаз" или подобных. Обмен с помощью правил, при данной постановке задачи, не планируется и о совместимости со стандартным обменом переживать не приходится. Синхронизация же с помощью свойства показалась мне безобидной, простой и эффективной. Уникальность значения свойства обеспечивается в пределах конкретного справочника. Кроме поиска по значению свойства идентификатора также поддерживается поиск по коду и наименованию.
В случае наличия, для поиска будут использоваться значения реквизитов "ИНН" и "КПП". Это небольшой элемент кастомизации. Вообще, если ваша конфигурация-приемник не использует библиотеку интернет-поддержки пользователей, то некоторые вещи из кода придется выкинуть.
Если подключена поддержка, то также будет производиться работа с банками, валютами и странами мира без необходимости отдельной синхронизации классификаторов. Также, при необходимости, будет запрашиваться из внешнего сервиса наименование и адрес контрагента по ИНН. Опять же, для этого использование БИПП в приемнике обязательно.
Переданные из источника значения с ключами, которые не совпадают с именами реквизитов приемника, будут использованы для заполнения контактной информации (при наличии соответствующей табличной части у справочника). Для этого ключи переданных значений должны совпадать с именами предопределенных видов контактной информации.
Все, что осталось после заполнения реквизитов и контактов будет записано в автоматически созданные дополнительные реквизиты строкового типа. Если, конечно, соответствующая табличная часть у справочника имеется.
В случае возникновения проблем при заполнении реквизитов элемент справочника не записывается. Создается достаточно подробная диагностика, которая, в последствии, выводится обработкой в журнал регистрации источника.
3. Собственно, обмен:
Обмен инициируется обработкой "Обмен", запускаемой по расписанию с необходимым интервалом. Данные для заполнения реквизитов приемника извлекаются из результата запроса, который пишется для каждого конкретного справочника. Функция, возвращающая запрос, размещается в модуле обработки. Имя функции должно совпадать с именем справочника. Структура функции проста.
Например, функция для инициации обмена по договорам из ДО в УНФ в данном примере выглядит так:
Функция ДоговорыКонтрагентов(КодУзла)
Запрос = Новый Запрос();
Запрос.УстановитьПараметр("КодУзла", КодУзла);
Запрос.УстановитьПараметр("Имя", "ВидДоговора");
Запрос.Текст =
"ВЫБРАТЬ
| ДокументыПредприятия.Ссылка КАК Источник,
| ДокументыПредприятия.Ссылка.Код КАК id_value,
| ДокументыПредприятия.Узел КАК Узел,
| ДокументыПредприятия.Ссылка.Наименование КАК Наименование,
| ДокументыПредприятия.Ссылка.Валюта.Наименование КАК Валюта,
| ДокументыПредприятия.Ссылка.ДатаНачалаДействия КАК ДатаДоговора,
| ДокументыПредприятия.Ссылка.ДатаОкончанияДействия КАК СрокДействия,
| ДокументыПредприятия.Ссылка.Контрагент.Код КАК Владелец,
| ДокументыПредприятия.Ссылка.Организация.Код КАК Организация,
| ВЫБОР
| КОГДА ДокументыПредприятия.Ссылка.РегистрационныйНомер = """"
| ТОГДА ДокументыПредприятия.Ссылка.ВременныйНомер
| ИНАЧЕ ДокументыПредприятия.Ссылка.РегистрационныйНомер
| КОНЕЦ КАК НомерДоговора,
| ДополнительныеРеквизиты.Значение.Наименование КАК ВидДоговора
|ИЗ
| Справочник.ДокументыПредприятия.Изменения КАК ДокументыПредприятия
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ДокументыПредприятия.ДополнительныеРеквизиты КАК ДополнительныеРеквизиты
| ПО ДокументыПредприятия.Ссылка = ДополнительныеРеквизиты.Ссылка
|ГДЕ
| ДокументыПредприятия.Узел.Код = &КодУзла
| И ДополнительныеРеквизиты.Свойство.Имя = &Имя";
Возврат Запрос;
КонецФункции
Обязательными полями в запросе являются:
- "Узел", "Источник" - ссылки на узел и измененный элемент для снятия с регистрации после удачного обмена.
- "id_value" - идентификатор для поиска объекта и записи значения свойства в приемнике.
В приведенном выше запросе в качестве идентификатора используется код справочника. Можно использовать любую уникальную последовательность, но при наличии уникального в пределах справочника кода так проще всего. В случае работы с внешним источником используется идентификатор записи или строка из уникальной комбинации полей. В данной реализации размер идентификатора не должен превышать 100 символов. При необходимости это можно легко поправить в коде.
А процедура, задающая состав и последовательность обмена, в примере выглядит так:
Процедура ВыполнитьОбмен() Экспорт
СоздатьНеобходимыеОбъектыПриОтсутствии();
КодУзла = "ОбменУНФ";
ВыполнитьОбменПоСправочнику("Контрагенты", КодУзла);
ВыполнитьОбменПоСправочнику("ДоговорыКонтрагентов", КодУзла);
КонецПроцедуры
В теле процедуры должна быть последовательно запущена универсальная процедура "ВыполнитьОбменПоСправочнику" для каждого справочника, участвующего в обмене. Эта процедура запрашивает данные, инициирует обмен, в случае удачи удаляет регистрацию изменений и записывает протокол. Из приведенного ниже кода процедуры очевидно, почему имя добавляемой Вами функции запроса должно совпадать с именем справочника. Также понятно для чего в запросе нужны обязательные поля "Источник" и "Узел". Обращаю на это внимание.
Процедура ВыполнитьОбменПоСправочнику(ИмяСправочника, КодУзла) Экспорт
Запрос = Неопределено;
Выполнить("Запрос = " + ИмяСправочника + "(""" + КодУзла + """)");
Результат = Запрос.Выполнить();
ТаблицаДляПередачи = Результат.Выгрузить();
ТаблицаДляПередачи.Колонки.Удалить(ТаблицаДляПередачи.Колонки["Источник"]);
ТаблицаДляПередачи.Колонки.Удалить(ТаблицаДляПередачи.Колонки["Узел"]);
СтруктураЗапроса = ПолучитьИсходнуюСтруктуру(ИмяСправочника, "Код", , , Истина, ТаблицаДляПередачи);
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, СтруктураЗапроса);
ЗапросДляПередачи = ЗаписьJSON.Закрыть();
ЗапросДляПередачи = СтрЗаменить(ЗапросДляПередачи, "0001-01-01T00:00:00", "");
Ответ = ПолучитьОтветОтСервиса(ЗапросДляПередачи);
Если Ответ.КодСостояния = 200 Тогда
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
ПланыОбмена.УдалитьРегистрациюИзменений(Выборка.Узел, Выборка.Источник);
КонецЦикла;
КонецЕсли;
ТекстПротокола = Ответ.ПолучитьТелоКакСтроку() + Символы.ПС + ЗапросДляПередачи;
// Здесь может быть запись в журнал регистрации или запись протокола в файл лога
ЗаписьЖурналаРегистрации(КодУзла, УровеньЖурналаРегистрации.Информация, , , ТекстПротокола, РежимТранзакцииЗаписиЖурналаРегистрации.Транзакционная);
КонецПроцедуры
Таким образом, создавая новые функции-запросы, раскручиваем дерево ссылок снизу вверх. От ссылочных реквизитов к головным справочникам. В запросе можно использовать простые типы данных, перечисления (в виде строки) и идентификаторы ссылок. В качестве идентификаторов используем ранее заполненные (при синхронизации) значения свойства-идентификатора для элементов соответствующих справочников. Прозвучало сложно, но в случае если обе базы - конфигурации 1С, как идентификатор и ссылку почти всегда можно использовать стандартный реквизит "Код".
В примере предоставлены запросы для обмена по основным реквизитам для справочников "Контрагенты" и "ДоговорыКонтрагентов". Для обмена пришлось создать в ДО дополнительный реквизит "ВидДоговора". Он создается автоматически при первом запуске обработки процедурой "СоздатьНеобходимыеОбъектыПриОтсутствии". Т. е., пример кода для подобной ситуации имеется.
Напоследок, чтобы было общее представление о структуре модуля обработчика сервиса, размещаю картинку. Модуль выглядит так (примерно 850 строк):
Я разместил эту публикацию исходя из личной оценки. Лично мне библиотека в эксплуатации показалась очень удобной. Я больше не задумываюсь об алгоритмах поиска и коррекции элементов. Не забочусь о правильном заполнении реквизитов. Просто пишу запрос для нужных реквизитов нового справочника, добавляю справочник в план обмена и вставляю вызов обмена по этому справочнику в процедуру "ВыполнитьОбмен". В случае необходимости повторяю все это для справочников ссылочных реквизитов. Получается удобно, быстро и с минимальным количеством ошибок. Очередной обмен запускается, практически, без отладки.
Кто-то может не согласиться с таким подходом или счесть его недостаточно профессиональным. На вкус и цвет, как известно, товарищей нет. Если так, то спасибо за внимание и извините за беспокойство. Надеюсь, найдутся и те, кто оценит этот скромный труд.
Вот, собственно, и все. Предложенный код структурирован и документирован, в меру сил. Прошу коллег не судить строго. Если чего не понятно, можно спрашивать. Консультации, как говорится, входят в стоимость. Пример рабочий и даже реально используется. На указанных конфигурациях должен завестись сразу. Файлы расширений и обработка прилагаются для скачивания.
Буду очень рад, если данная статья и данный код окажутся для кого-то полезными на практике. Также буду благодарен за отзывы. В том числе и отрицательные.