План обмена

25.04.2018

Категория: 1С:Предприятие

Общие сведения об обмене данными

Механизмы обмена данными «1С:Предприятия» позволяют организовывать обмен информацией, хранимой в базе данных, с другими программными системами. К механизмам обмена данными могут быть отнесены:

  • Планы обмена,
  • XML-сериализация,
  • Средства чтения и записи документов XML.

При помощи планов обмена мы получаем информацию о том, какие элементы данных были изменены и в какой узел обмена их необходимо передать. Это возможно благодаря тому, что планы обмена содержат механизм регистрации изменений. Информация об измененных данных переносится с помощью сообщений, инфраструктура которых также поддерживается планами обмена.

Что такое план обмена

Для того чтобы существовала возможность обмена какими-либо данными с кем-либо, необходимо некоторым образом идентифицировать тех, с кем мы будем обмениваться, и для каждого из них описать перечень обмена. Обе эти задачи позволяет решать объект конфигурации «План обмена». Подобно тому, как элементами данных справочника являются элементы справочника, элементами данных плана обмена являются узлы плана обмена.

Каждый узел идентифицирует участника обмена по данному плану обмена. Кроме этого, в каждом плане обмена всегда существует один предопределенный узел, идентифицирующий данную информационную базу.

В обмене данными могут участвовать:

  • объекты базы данных: элементы справочников, документы и т. д.,
  • необъектные данные: наборы записей регистров, последовательностей, константы,
  • специальный объект встроенного языка – УдалениеОбъекта.

Для упрощения изложения в дальнейшем будем называть эти элементы информационных структур объектами обмена. Разработчик имеет возможность определить состав каждого плана обмена, указав объекты конфигурации, данные которых должны участвовать в обмене по данному плану. При описании состава данных плана обмена разработчик имеет возможность указать для каждого типа объектов признак Авторегистрация. Этот признак определяет, каким образом план обмена будет отслеживать изменения данных.

Возможность отслеживать изменения данных реализована в плане обмена за счет использования механизма регистрации изменений. Работа этого механизма базируется на том, что каждый из объектов обмена имеет свойство ОбменДанными, с помощью которого можно указать, для каких узлов необходимо производить регистрацию изменений этого объекта. Любые изменения объекта обмена сводятся в конечном итоге к записи или удалению объекта обмена.

Так вот, признак Авторегистрация, устанавливаемый при указании состава данных плана обмена, позволяет указать, что параметры обмена данными будут формироваться каждый раз самим механизмом регистрации изменений на основании информации, содержащейся в плане обмена. После автоматического заполнения параметров обмена разработчик имеет возможность внести изменения в сформированные таким образом параметры. Кроме этого, существует возможность отключить авторегистрацию изменений, и тогда параметры обмена данными нужно будет формировать полностью средствами встроенного языка.

Итак, при записи и удалении объектов обмена план обмена формирует записи регистрации изменений. Записи регистрации изменений хранятся в таблицах регистрации изменений, причем для каждого объекта обмена ведется своя таблица. При изменении объекта обмена в таблице регистрации изменений создается столько записей, сколько узлов-получателей указано в параметрах обмена данными у объекта обмена. Каждая запись при этом будет хранить ссылку на свой узел-получатель.

Кроме ссылки на узел обмена, для которого регистрируются изменения, каждая запись таблицы регистрации изменений хранит также номер сообщения, в котором изменение было передано в первый раз в этот узел. До тех пор, пока сообщение не будет передано в первый раз, это поле хранит NULL.

Поскольку сообщения передаются в рамках плана обмена от одного узла к другому, каждое сообщение точно ассоциировано с планом обмена, имеет уникальный номер и одного отправителя и получателя. За нумерацию сообщений отвечает инфраструктура сообщений. Благодаря этому записи регистрации изменений и имеют возможность хранить номера сообщений, в которых эти изменения были переданы первый раз.

Инфраструктура сообщений позволяет также получать подтверждения от узла-получателя о приеме сообщений. Такое подтверждение содержится в каждом сообщении, приходящем от узла-получателя в виде номера последнего принятого сообщения. Впоследствии, проанализировав номер последнего принятого сообщения и номера сообщений, содержащиеся в записях регистрации изменений, разработчик может удалить записи регистрации изменений, прием которых подтвержден получателем.

Добавление плана обмена

Раскроем ветвь «Общие» дерева объектов конфигурации и добавим новый объект конфигурации План обмена с именем Филиалы, представление объекта – Филиал. На закладке «Данные» создадим реквизит плана обмена Главный, имеющий тип Булево.

Этот реквизит понадобится нам для того, чтобы разрешать коллизии при обмене данными. Под коллизией понимается ситуация, когда один и тот же объект обмена данными был изменен одновременно в двух узлах. В этом случае мы будем анализировать значение реквизита Главный и принимать изменения только в том случае, если они сделаны в главном узле. В случае коллизии изменения, произведенные не в главном узле, мы будем отвергать.

Теперь определим состав объектов, участвующих в обмене. Для этого на закладке «Основные» нажмем кнопку «Состав».

Создадим основную форму узла и добавим обработчик события формы ПриСозданииНаСервере. Этот обработчик понадобится нам для того, чтобы запретить установку реквизита Главный для предопределенного узла, соответствующего данной информационной базе.

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    Если Объект.Ссылка = ПланыОбмена.Филиалы.ЭтотУзел() Тогда
        Элементы.Главный.Доступность = Ложь;
    КонецЕсли;
КонецПроцедуры

Затем создадим основную форму списка плана обмена, чтобы описать в ней некоторые действия по регистрации нового узла обмена.

Суть этих действий будет заключаться в том, что при регистрации нового узла обмена мы должны будем сформировать для него все необходимые записи регистрации изменений для всех объектов конфигурации, входящих в данный план обмена. Это будет своего рода начальная синхронизация узла обмена всеми данными обмена.

Для этого на закладке «Команды» создадим команду ЗарегистрироватьИзменения.

&НаСервереБезКонтекста
Процедура ЗарегистрироватьИзмененияНаСервере(Узел)
    // Регистрация изменений всех данных для узла
    ПланыОбмена.ЗарегистрироватьИзменения(Узел);
КонецПроцедуры

&НаКлиенте
Процедура ЗарегистрироватьИзменения(Команда)
    ЗарегистрироватьИзмененияНаСервере(Элементы.Список.ТекущаяСтрока);
КонецПроцедуры

Кнопка «Зарегистрировать изменения» должна быть доступна только в случае, если текущий узел не является предопределенным для данной информационной базы, иначе регистрация изменений невозможна. Чтобы обеспечить такое поведение кнопки, создадим в модуле формы списка функцию, выполняющуюся на сервере без контекста и возвращающую истину, если переданный в функцию узел является предопределенным.

&НаСервереБезКонтекста
Функция ПредопределенныйУзел(Узел)
    Возврат Узел = ПланыОбмена.Филиалы.ЭтотУзел();
КонецФункции

Затем в окне элементов формы выделим элемент Список, вызовем его палитру свойств и создадим обработчик события ПриАктивизацииСтроки.

&НаКлиенте
Процедура СписокПриАктивизацииСтроки(Элемент)
    Если ПредопределенныйУзел(Элемент.ТекущаяСтрока) Тогда
        Элементы.ФормаЗарегистрироватьИзменения.Доступность = Ложь;
    Иначе
        Элементы.ФормаЗарегистрироватьИзменения.Доступность = Истина;
    КонецЕсли;
КонецПроцедуры

Процедуры обмена данными

Для инициализации обмена данными мы используем обработку. Добавим новый объект конфигурации Обработка с именем ОбменДанными. На закладке Формы создадим основную форму обработки. В окне редактора форм на закладке «Команды» создадим команду формы ВыполнитьОбмен.

&НаКлиенте
Процедура ВыполнитьОбмен(Команда)
    ВыполнитьОбменНаСервере();
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ВыполнитьОбменНаСервере()
    ВыборкаУзлов = ПланыОбмена.Филиалы.Выбрать();
    Пока ВыборкаУзлов.Следующий() Цикл
        // Произвести обмен со всеми узлами, кроме этого
        Если ВыборкаУзлов.Ссылка = ПланыОбмена.Филиалы.ЭтотУзел() Тогда
            Продолжить;
        КонецЕсли;
        УзелОбъект = ВыборкаУзлов.Ссылка.ПолучитьОбъект();
        // Получить сообщение
        УзелОбъект.ПрочитатьСообщениеСИзменениями();
        // Сформировать сообщение
        УзелОбъект.ЗаписатьСообщениеСИзменениями();        
    КонецЦикла;
КонецПроцедуры

Алгоритм работы этой процедуры заключается в следующем: в цикле мы перебираем узлы, которые содержатся в плане обмена Филиалы, и для всех узлов, кроме себя самого, производим сначала чтение сообщений, поступивших из других узлов обмена. Затем мы формируем для них сообщения, предназначенные для передачи и содержащие измененные данные для этого узла.

Процедуры записи и чтения данных

Сами процедуры записи и чтения данных обмена мы разместим в модуле объекта План обмена Филиалы.

Процедура ЗаписатьСообщенияСИзменениями() Экспорт
    
    // ЭтотОбъект — это объект очередного узла, полученный в цикле
    // процедуры ВыполнитьОбменНаСервере() модуля формы обработки
    // ОбменДанными. А Ссылка — это ссылка на ЭтотОбъект. Например,
    // в ПланеОбмена есть два узла, кроме текущего: ПервыйФилиал
    // и ВторойФилиал. Мы в данный момент работаем с узлом
    // ПервыйФилиал. Из этой информационной базы нам надо получить
    // все зарегистрированные изменения объектов обмена для узла
    // ПервыйФилиал и записать их в XML-файл.

    Сообщение = Новый СообщениеПользователю();
    Сообщение.Текст = "----- Выгрузка в узел " + Строка(ЭтотОбъект) + " -----";
    Сообщение.Сообщить();
    
    // Сформировать имя временного файла
    Каталог = КаталогВременныхФайлов();
    ИмяФайла = Каталог + ?(Прав(Каталог, 1) = "\","", "\") + "Message-" +
        СокрЛП(ПланыОбмена.Филиалы.ЭтотУзел().Код) + "-" + СокрЛП(Ссылка.Код) +
        ".xml";
        
    // Создать объект записи XML
    // *** ЗаписьXML-документов
    ЗаписьXML = Новый ЗаписьXML();
    ЗаписьXML.ОткрытьФайл(ИмяФайла);
    ЗаписьXML.ЗаписатьОбъявлениеXML();

    // *** Инфраструктура сообщений
    ЗаписьСообщения = ПланыОбмена.СоздатьЗаписьСообщения();
    ЗаписьСообщения.НачатьЗапись(ЗаписьXML, Ссылка);
    Сообщение = Новый СообщениеПользователю();
    Сообщение.Текст = " Номер сообщения: " + ЗаписьСообщения.НомерСообщения;
    Сообщение.Сообщить();
    
    // Получить выборку измененных данных
    // *** Механизм регистрации изменений
    ВыборкаИзменений = ПланыОбмена.ВыбратьИзменения(
        ЗаписьСообщения.Получатель,
        ЗаписьСообщения.НомерСообщения
    );
    
    // ВыбратьИзменения(<Узел>, <НомерСообщения>, <ФильтрВыборки>)
    // 
    // Формирует выборку «измененные данные» для передачи их в тот или
    // иной узел плана обмена. При этом в процессе выборки изменений,
    // в записи регистрации изменений проставляется номер сообщения
    // обмена данными, в котором должны передаваться изменения. Номер
    // сообщения в записи регистрации проставляется для того, чтобы
    // при подтверждении приема сообщения, в котором передавались
    // изменения, соответствующие записи регистрации изменений были
    // удалены и в дальнейшем изменения больше не передавались.
    
    Пока ВыборкаИзменений.Следующий() Цикл
        // Записать данные в сообщение *** XML-сериализация
        ЗаписатьXML(ЗаписьXML, ВыборкаИзменений.Получить());
    КонецЦикла;

    ЗаписьСообщения.ЗакончитьЗапись();
    ЗаписьXML.Закрыть();

    Сообщение = Новый СообщениеПользователю();
    Сообщение.Текст = "----- Конец выгрузки -----";
    Сообщение.Сообщить()
        
КонецПроцедуры
Процедура ПрочитатьСообщениеСИзменениями() Экспорт

    Каталог = КаталогВременныхФайлов();

    // Сформировать имя файла
    ИмяФайла = Каталог + ?(Прав(Каталог, 1) = "\", "", "\") +
        "Message-" + СокрЛП(Ссылка.Код) + "-" +
        СокрЛП(ПланыОбмена.Филиалы.ЭтотУзел().Код) + ".xml";
    Файл = Новый Файл(ИмяФайла);
    Если Не Файл.Существует() Тогда
        Возврат;
    КонецЕсли;
    
    // *** Чтение документов XML    
    // Попытаться открыть файл
    ЧтениеXML = Новый ЧтениеXML();
    Попытка 
        ЧтениеXML.ОткрытьФайл(ИмяФайла);
    Исключение 
        Сообщение = Новый СообщениеПользователю();
        Сообщение.Текст = "Невозможно открыть файл обмена данными.";
        Сообщение.Сообщить();
        Возврат;
    КонецПопытки;

    Сообщение = Новый СообщениеПользователю();
    Сообщение.Текст = "----- Загрузка из " + Строка(ЭтотОбъект) + " -----";
    Сообщение.Сообщить();
    Сообщение = Новый СообщениеПользователю();
    Сообщение.Текст = " – Считывается файл " + ИмяФайла;
    Сообщение.Сообщить();
    
    // Загрузить из найденного файла
    // *** Инфраструктура сообщений
    ЧтениеСообщения = ПланыОбмена.СоздатьЧтениеСообщения();

    // Читать заголовок сообщения обмена данными – файла XML
    ЧтениеСообщения.НачатьЧтение(ЧтениеXML);
    
    // Сообщение предназначено не для этого узла
    Если ЧтениеСообщения.Отправитель <> Ссылка Тогда
        ВызватьИсключение "Неверный узел";
    КонецЕсли;
    
    // Допустим, ПланОбмена Филиалы включает три узла:
    // 1. ЦентральныйОфис, код 001
    // 2. ПервыйФилиал, код 002
    // 3. ВторойФилиал, код 003
    // Имена XML-файлов обмена будут такими
    // 1. Message-001-002.xml, ЦентральныйОфис->ПервыйФилиал
    // 2. Message-001-003.xml, ЦентральныйОфис->ВторойФилиал
    // 3. Message-002-001.xml, ПервыйФилиал->ЦентральныйОфис
    // 4. Message-002-003.xml, ПервыйФилиал->ВторойФилиал
    // 5. Message-003-001.xml, ВторойФилиал->ЦентральныйОфис
    // 6. Message-003-002.xml, ВторойФилиал->ПервыйФилиал
    // И пусть мы сейчас работаем в информационной базе
    // ЦентральногоОфиса. А в каталоге врменнных файлов у
    // нас шесть файлов. Мы должны прочитать только файлы
    // Message-002-001.xml и Message-003-001.xml, каждый на
    // очередной итерации цикла в процедуре модуля формы
    // обработки ОбменДанными. И сейчас читаем Message-002-001.xml,
    // отправитель ПервыйФилиал. Если
    // ЧтениеСообщения.Отправитель <> СсылкаНаПервыйФилиал
    // мы должны такое сообщение игнорировать. Потому как
    // непонятно, почему в имени файла правильный отправитель,
    // а в самом XML Отправитель неправильный.
    
        // Удаляем регистрацию изменений для узла отправителя сообщения.
    // *** Служба регистрации изменений
    ПланыОбмена.УдалитьРегистрациюИзменений(
        ЧтениеСообщения.Отправитель,
        ЧтениеСообщения.НомерПринятого
    );
    
     // Читаем данные из сообщения *** XML-сериализация
    Пока ВозможностьЧтенияXML(ЧтениеXML) Цикл
        // Читаем очередное значение 
        Данные = ПрочитатьXML(ЧтениеXML);
        
        // Не переносим изменение, полученное в главный из неглавного,
        // если есть регистрация изменения
        //
        // Мы сейчас работает с конкретной информационной базой, в этой
        // базе зарегистрированы какие-то изменения каких-то объектов
        // обмена для каких-то узлов. Здесь мы проверяем, зарегистрированы
        // ли изменения для узла-отправителя объектов обмена, которые
        // получены при чтении очередной порции данных. Если это так, и
        // и отправитель не главный узел, мы отвергаем эти изменения.
        // Это логично — чем изменения одного не главного узла лучше
        // изменений другого не главного узла (т.е. изменения в нашей
        // базе)? И тем более отвергаем изменения, если наш узел — главный.
        Если
            ПланыОбмена.ИзменениеЗарегистрировано(
                ЧтениеСообщения.Отправитель,
                Данные
            )
            И Не ЧтениеСообщения.Отправитель.Главный
        Тогда
            Сообщение = Новый СообщениеПользователю();
            Сообщение.Текст = " – Изменения отклонены";
            Сообщение.Сообщить();
            Продолжить;
        КонецЕсли;
        
        // Записать полученные данные
        Данные.ОбменДанными.Отправитель = ЧтениеСообщения.Отправитель;
        Данные.ОбменДанными.Загрузка = Истина;
        Данные.Записать();

    КонецЦикла;

    ЧтениеСообщения.ЗакончитьЧтение();
    ЧтениеXML.Закрыть();
    УдалитьФайлы(ИмяФайла);

    Сообщение = Новый СообщениеПользователю();
    Сообщение.Текст = "----- Конец загрузки -----";
    Сообщение.Сообщить();

КонецПроцедуры
Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.