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