Пример организации обмена данными с использованием плана обмена

Приложения «Офис» и «Склад1» работают на базе технологии . В каждом приложении есть элемент проекта Документ, в котором хранятся расходные накладные. Необходимо, чтобы добавление, изменение или удаление расходных накладных в приложении «Офис» фиксировались и отправлялись в приложение «Склад1», и наоборот. Обмен будет производиться с использованием файлов, которые предварительно будут помещаться в архив. Однако вы можете использовать иной способ передачи сообщений из системы в систему, например, использовать протоколы FTP или HTTP.

Конфигурация в среде разработки

Создайте подсистему Накладные. В ней создайте элемент проекта Документ и назовите его РасходнаяНакладная:
ВидЭлемента: Документ
Ид: aa3633a8-16ba-4162-a8b5-444f1ca8d498
Имя: РасходнаяНакладная
ОбластьВидимости: ВПроекте
КонтрольДоступа:
    Разрешения:
        Создание: РазрешеноАдминистраторам
        Чтение: РазрешеноАдминистраторам
        Изменение: РазрешеноАдминистраторам
        Удаление: РазрешеноАдминистраторам
Реквизиты:
    -
        Имя: Дата
        Тип: ДатаВремя
    -
        Имя: Номер
        Тип: Строка
    -
        Ид: 2b2377ec-2f3a-489d-bd12-dbc190661ab6
        Имя: Товар
        Тип: Строка
    -
        Ид: 46c47a26-70ae-4c0a-9b54-b13943ff1d99
        Имя: Количество
        Тип: Число
    -
        Ид: 0890a598-1ccc-433f-a987-47d600582ce7
        Имя: Цена
        Тип: Число
    -
        Ид: 1ac3eba6-5957-4984-812f-acf589eb4bec
        Имя: Сумма
        Тип: Число
    -
        Ид: c936cfc1-b7b0-49bc-bca7-26566a152ed8
        Имя: Поставщик
        Тип: Строка
Добавьте подсистему ОбменДанными. В ней создайте элемент проекта План обмена. План обмена описывает:
  • Элементы проекта, изменения данных которых должны учитываться в ходе обмена данными. Будут добавлены в режиме разработки.
  • Узлы (приложения, информационные базы) между которыми выполняется обмен данными. Будут добавлены в панели управления приложением после публикации проекта.

В свойстве Состав следует указать элементы проекта, изменения в данных которых необходимо регистрировать. Также вы можете указать дополнительные реквизиты, например, путь каталога обмена. Пример того, как может выглядеть описание элемента План обмена:

ВидЭлемента: ПланОбмена
Ид: 1f7dc34e-4bbc-438d-8688-c256ec45ffbd
Имя: Склады
ОбластьВидимости: ВПодсистеме
# Так как документ «РасходнаяНакладная» находится в подсистеме «Накладные», ее следует импортировать
Импорт:
    - Накладные
КонтрольДоступа:
    Разрешения:
        Создание: РазрешеноАдминистраторам
        Чтение: РазрешеноАдминистраторам
        Изменение: РазрешеноАдминистраторам
        Удаление: РазрешеноАдминистраторам
Представление: Наименование
Состав:
    -
        Элемент: РасходнаяНакладная
Реквизиты:
    -
        Ид: 64fe1683-c33e-4ee5-9b7d-e628fc34127e
        Имя: Наименование
        Тип: Строка
    -
        Ид: 299fb868-7591-4ef4-bfc0-1c5db56f3c36
        Имя: ПутьКаталогаОбмена
        Тип: Строка
Для элемента проекта План обмена следует создать модуль. В нем вы укажете код, который будет исполняться с сервера, но будет доступен с клиента:
@ВПодсистеме
@НаСервере @ДоступноСКлиента
метод ВыполнитьОбмен()
    ОбменДанными.ВыполнитьОбмен() // Метод из общего модуля ОбменДанными, исполняемого на сервере
;
Совет: Так как мы создали документ РасходнаяНакладная и план обмена ОбменДанными в разных подсистемах, то в подсистему, в которой у вас содержится план обмена, необходимо добавить описание подсистемы. Откройте описание подсистемы в текстовом редакторе и укажите подсистему, в которой вы создали документ, в качестве используемой:
# В примере документ «РасходнаяНакладная» содержится внутри подсистемы «Накладные»
Использование:
    - Накладные
Следующим шагом в подсистеме ОбменДанными необходимо создать общий модуль, который будет исполняться на сервере. В этом модуле будет располагаться основной код. Создайте общий модуль и назовите его ОбменДанными. В модуле следует прописать следующий код:
импорт Накладные

// Создадим запланированное задание для того, чтобы обмен осуществлялся регулярно один раз в десять минут.
@ВПроекте
метод ВключитьЗаданиеПоОбменуИзмениями()
    знч ЧастыйПовтор = Расписание.Периодическое(600с)
    знч Задание = ЗапланированныеЗадания.Создать(&ВыполнитьОбмен)
    Задание.Настроить(Ключ = "ВыполнениеОбменаИзменениями", Расписание = ЧастыйПовтор)
    Задание.Запланировать()
;

@ВПодсистеме
метод ВыполнитьОбмен()
    для Строка из Запрос{ВЫБРАТЬ Ссылка  ИЗ Склады ГДЕ ЭтотУзел == Ложь}.Выполнить()
        знч Узел = Строка.Ссылка.ЗагрузитьОбъект()
        если Узел.ПутьКаталогаОбмена.Пусто() 
            выбросить новый ИсключениеНедопустимоеСостояние("В узле не установлен ПутьКаталогаОбмена: %{Узел.Код}")
        ;
        ПрочитатьСообщениеОбмена(Узел)
        ЗаписатьСообщениеОбмена(Узел)
    ;
;

метод ПрочитатьСообщениеОбмена(Отправитель: Склады.Объект)
    знч ИмяФайлаZip = "%{Отправитель.Код}_%{Склады.ПолучитьЭтотУзел().ЗагрузитьОбъект().Код}.zip" 
    ПрочитатьСообщениеОбмена(Отправитель, ИмяФайлаZip, Отправитель.ПутьКаталогаОбмена)
;    

метод ПрочитатьСообщениеОбмена(Отправитель: Склады.Объект, ИмяФайлаZip: Строка, ПутьКаталогаОбмена: Строка)
    знч ПутьZipСообщенияОбмена = "$ПутьКаталогаОбмена\\$ИмяФайлаZip"
    знч ПутьКаталогаОбменаЧтение = "$ПутьКаталогаОбмена\\read"
    знч ПутьКаталогаОбменаЧтениеZip = "$ПутьКаталогаОбменаЧтение\\zip"
    знч ПутьКаталогаОбменаЧтениеРаспакован = "$ПутьКаталогаОбменаЧтение\\extracted"
    знч ПутьСообщенияОбменаЧтениеZip =  "$ПутьКаталогаОбменаЧтениеZip\\$ИмяФайлаZip"
    
    // Перемещение
    если новый Файл(ПутьZipСообщенияОбмена).Существует()
        Файлы.Переместить(
            ПутьZipСообщенияОбмена,
            ПутьСообщенияОбменаЧтениеZip,
            новый НастройкиКопированияФайлов()
                .ПропускатьСуществующие(Ложь)) 
    ;
    
    если новый Файл(ПутьСообщенияОбменаЧтениеZip).Существует()
        
        // Удаление разархивированных файлов        
        Файлы.Удалить(ПутьКаталогаОбменаЧтениеРаспакован, Истина)
        // Разархивирование
        знч ZipСообщенияОбмена = новый ФайлZip(ПутьСообщенияОбменаЧтениеZip)
        ZipСообщенияОбмена.ИзвлечьВсе(ПутьКаталогаОбменаЧтениеРаспакован)
    ; 
    
    // Чтение файла XML        
    если новый Файл(ПутьКаталогаОбменаЧтениеРаспакован).Существует()
        знч НайденныеФайлы = Файлы.Найти(ПутьКаталогаОбменаЧтениеРаспакован,
            новый НастройкиПоискаФайлов()
                .ИсключитьКаталоги(Истина)
                .ИсключитьСсылки(Истина)
                .ИмяСодержит("xml")) 
        для НайденныйФайл из НайденныеФайлы
            если НайденныйФайл.Расширение != "xml"
                продолжить
            ;
            
            ПрочитатьXmlСообщениеОбмена(Отправитель.Ссылка, НайденныйФайл.ОткрытьПотокЧтения())
        ;
    ;
;    

метод ПрочитатьXmlСообщениеОбмена(Отправитель: Склады.Ссылка, ПотокФайла: ПотокЧтения)
    знч ЧтениеXml = новый ЧтениеXml(ПотокФайла)
    исп ГрупповаяОперация.Начать(РежимЗагрузкиДанных = Истина)
    исп ОбработкаВходящегоСообщенияОбмена = Склады.СоздатьОбработкуВходящегоСообщения(ЧтениеXml, 
                                                                                      ДопустимыйНомерСообщенияОбмена.Больший)
    знч ОтправительСсылка = ОбработкаВходящегоСообщенияОбмена.Отправитель как Склады.Ссылка
    если ОтправительСсылка != Отправитель
        выбросить новый ИсключениеНедопустимоеСостояние("Отправитель в сообщении отличается от отправителя каталога")
    ;
    Склады.УдалитьРегистрациюИзмененийПоСообщению(ОтправительСсылка, ОбработкаВходящегоСообщенияОбмена.НомерПринятого)
    пока ЭтоИзменениеДанных(ЧтениеXml)
        ПрочитатьИзменениеДанных(ЧтениеXml, ОтправительСсылка)
    ;
;    

метод ПрочитатьИзменениеДанных(ЧтениеXml: ЧтениеXml, Отправитель: Склады.Ссылка)
    знч ИмяЭлемента = ЧтениеXml.Имя
    если ИмяЭлемента == "DocumentObject.Накладные"
        ПрочитатьИзменениеНакладной(ЧтениеXml, Отправитель)
    иначе если ИмяЭлемента == "ObjectDeletion"
        ПрочитатьУдалениеОбъекта(ЧтениеXml)
    иначе
        ЧтениеXml.Пропустить()   
    ;
;

метод ПрочитатьУдалениеОбъекта(ЧтениеXml: ЧтениеXml)
    ЧтениеXml.Следующий()
    НачатьЧтениеПоля(ЧтениеXml, "Ref")
    знч ТипСсылки = ЧтениеXml.ЗначениеАтрибута("type", "http://www.w3.org/2001/XMLSchema-instance")
    знч СсылкаИд = ЧтениеXml.ПрочитатьСодержимоеКакУуид()
    ЧтениеXml.Следующий()
    ЧтениеXml.Следующий()
    пер Ссылка: Сущность.Ключ? = Неопределено
    если ТипСсылки == "DocumentRef.Накладные"
        Ссылка = РасходнаяНакладная.ПолучитьСсылку(СсылкаИд)
    иначе
        возврат
    ;
    новый УдалениеОбъекта(Ссылка).Записать()
;

метод ЗаписатьСообщениеОбмена(Получатель: Склады.Объект)
    знч ИмяФайла ="%{Склады.ПолучитьЭтотУзел().ЗагрузитьОбъект().Код}_%{Получатель.Код}"
    знч ФайлСообщения = Файлы.СоздатьВременныйФайл(Суффикс = ".zip")

    // архивирование
    знч ZipСообщенияОбмена = новый ЗаписьZip(ФайлСообщения.ОткрытьПотокЗаписи())
    ZipСообщенияОбмена.Добавить(ЗаписатьXmlСообщениеОбмена(Получатель.Ссылка).ОткрытьПотокЧтения(), ИмяФайла + ".xml" )
    ZipСообщенияОбмена.Записать()
    
    // перемещение
    Файлы.Скопировать(
        ФайлСообщения.Путь,
        "%{Получатель.ПутьКаталогаОбмена}\\$ИмяФайла.zip",
        новый НастройкиКопированияФайлов()
            .ПропускатьСуществующие(Ложь)) 
;

метод ЗаписатьXmlСообщениеОбмена(Получатель: Склады.Ссылка):Файл
    исп ПотокЗаписи = новый СтроковыйПотокЗаписи()
    знч ЗаписьXml = новый ЗаписьXml(ПотокЗаписи)

    область
        исп ОбработкаИсходящегоСообщенияОбмена = Склады.СоздатьОбработкуИсходящегоСообщения(ЗаписьXml, Получатель)
        исп ВыборкаДанных = Склады.ВыбратьИзмененияВСообщение(Получатель, ОбработкаИсходящегоСообщенияОбмена.НомерСообщения, 
                                                              {Тип<РасходнаяНакладная.Объект>})
        для Данные из ВыборкаДанных  
            если Данные это РасходнаяНакладная.Объект
                ЗаписатьИзменениеНакладной(ЗаписьXml, Получатель, Данные как РасходнаяНакладная.Объект)                    
            иначе если Данные это УдалениеОбъекта
                ЗаписатьУдалениеОбъекта(ЗаписьXml, Данные как УдалениеОбъекта)
            ;
        ;
    ;
    знч СообщенияОбмена = Файлы.СоздатьВременныйФайл(УдалитьПослеОкончанияРаботы = Ложь)
    знч НастройкиЗаписиДанных = новый НастройкиЗаписиДанных()
    НастройкиЗаписиДанных.МеткаПорядкаБайтов = МеткаПорядкаБайтов.Добавлять
    исп ФайлПотокЗаписи = СообщенияОбмена.ОткрытьПотокЗаписи()
    знч ЗаписьДанных = новый ЗаписьДанных(ФайлПотокЗаписи, НастройкиЗаписиДанных)
    ЗаписьДанных.ЗаписатьСтроку(ПотокЗаписи.ВСтроку())
    возврат СообщенияОбмена
    
;

метод ЭтоИзменениеДанных(ЧтениеXml: ЧтениеXml): Булево
    возврат ЧтениеXml.ВидУзла == ВидУзлаXml.НачалоЭлемента
;

метод ЗаписатьУдалениеОбъекта(ЗаписьXml: ЗаписьXml, УдалениеОбъекта: УдалениеОбъекта)
    знч Ссылка = УдалениеОбъекта.Ссылка
    пер СсылкаИд: Ууид? = Неопределено
    пер ТипСсылки = ""
    
    если Ссылка это РасходнаяНакладная.Ссылка
        ТипСсылки = "DocumentRef.Накладные"
        СсылкаИд = (Ссылка как РасходнаяНакладная.Ссылка).Ид
    иначе
        возврат
    ;
    ЗаписьXml.ЗаписатьНачалоЭлемента("ObjectDeletion")
    ЗаписьXml.ЗаписатьАтрибут("xmlns", "http://v8.1c.ru/data")
    // Ref начало
    ЗаписьXml.ЗаписатьНачалоЭлемента("d4p1:Ref")
    ЗаписьXml.ЗаписатьАтрибут("xmlns", "")
    ЗаписьXml.ЗаписатьАтрибут("xmlns:d4p1", "http://v8.1c.ru/data")
    ЗаписьXml.ЗаписатьАтрибут("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
    
    ЗаписьXml.ЗаписатьАтрибут("xsi:type", ТипСсылки)
    ЗаписьXml.ЗаписатьУуид(СсылкаИд)
    // Ref начало
    ЗаписьXml.ЗаписатьКонецЭлемента()
        
    ЗаписьXml.ЗаписатьКонецЭлемента()
;

//-----------------------------------------------------------------------------
// Обработка Расходной накладной
метод ПрочитатьИзменениеНакладной(ЧтениеXml: ЧтениеXml, Отправитель: Склады.Ссылка)
    ЧтениеXml.Следующий()
    знч Ref = ПрочитатьПолеУуид(ЧтениеXml, "Ref")
    знч Ссылка = РасходнаяНакладная.ПолучитьСсылку(Ref)
    пер Объект: РасходнаяНакладная.Объект
    знч НайденныйОбъект = Ссылка.ЗагрузитьОбъект(Ложь)
    если НайденныйОбъект это Неопределено
        Объект = новый РасходнаяНакладная.Объект(Ref) 
    иначе
        Объект = НайденныйОбъект как РасходнаяНакладная.Объект
    ;
    Объект.Дата = ПрочитатьПолеДатаВремя(ЧтениеXml, "Дата")
    Объект.Номер = ПрочитатьПолеСтрока(ЧтениеXml, "Номер")
    Объект.Товар = ПрочитатьПолеСтрока(ЧтениеXml, "Description")
    Объект.Количество = ПрочитатьПолеЧисло(ЧтениеXml, "Количество")
    Объект.Цена = ПрочитатьПолеЧисло(ЧтениеXml, "Цена")
    Объект.Сумма = ПрочитатьПолеЧисло(ЧтениеXml, "Сумма")
    Объект.Поставщик = ПрочитатьПолеСтрока(ЧтениеXml, "Поставщик")
    ЧтениеXml.Пропустить() 
    Объект.Записать()
;

метод ЗаписатьИзменениеНакладной(ЗаписьXml: ЗаписьXml, Получатель: Склады.Ссылка, Данные: РасходнаяНакладная.Объект)
    ЗаписьXml.ЗаписатьНачалоЭлемента("DocumentObject.Накладные")
    
    ЗаписатьПолеУуид(ЗаписьXml, "Ref", Данные.Ссылка.Ид)
    ЗаписатьПолеДатаВремя(ЗаписьXml, "Дата", Данные.Дата)
    ЗаписатьПолеСтрока(ЗаписьXml, "Номер", Данные.Номер)
    ЗаписатьПолеСтрока(ЗаписьXml, "Description", Данные.Товар)
    ЗаписатьПолеЧисло(ЗаписьXml, "Количество", Данные.Количество)
    ЗаписатьПолеЧисло(ЗаписьXml, "Цена", Данные.Цена)
    ЗаписатьПолеЧисло(ЗаписьXml, "Сумма", Данные.Сумма)
    ЗаписатьПолеСтрока(ЗаписьXml, "Поставщик", Данные.Поставщик)
    
    ЗаписьXml.ЗаписатьКонецЭлемента()
;

//-----------------------------------------------------------------------------
// Чтение
метод ПрочитатьПолеУуид(ЧтениеXml: ЧтениеXml, ИмяПоля: Строка): Ууид?
    НачатьЧтениеПоля(ЧтениеXml, ИмяПоля)
    знч Значение = ЧтениеXml.ПрочитатьСодержимоеКакУуид()
    ЧтениеXml.Следующий()
    возврат Значение
;

метод ПрочитатьПолеЧисло(ЧтениеXml: ЧтениеXml, ИмяПоля: Строка): Число
    НачатьЧтениеПоля(ЧтениеXml, ИмяПоля)
    знч Значение = ЧтениеXml.ПрочитатьСодержимоеКакЧисло()
    ЧтениеXml.Следующий()
    возврат Значение
;

метод ПрочитатьПолеДатаВремя(ЧтениеXml: ЧтениеXml, ИмяПоля: Строка): ДатаВремя
    НачатьЧтениеПоля(ЧтениеXml, ИмяПоля)
    знч Значение = ЧтениеXml.ПрочитатьСодержимоеКакДатаВремя()
    ЧтениеXml.Следующий()
    возврат Значение
;

метод ПрочитатьПолеСтрока(ЧтениеXml: ЧтениеXml, ИмяПоля: Строка): Строка
    НачатьЧтениеПоля(ЧтениеXml, ИмяПоля)
    ЧтениеXml.Следующий()
    пер Значение = ""
    если ЧтениеXml.ВидУзла == ВидУзлаXml.Текст
        Значение = ЧтениеXml.Значение
        ЧтениеXml.Следующий()
    ;
    ЧтениеXml.Следующий()
    возврат Значение
;

метод НачатьЧтениеПоля(ЧтениеXml: ЧтениеXml, ИмяПоля: Строка)
    если ЧтениеXml.ЛокальноеИмя != ИмяПоля
        выбросить новый ИсключениеНедопустимоеСостояние("Не найдено поле: %ИмяПоля")
    ;
;


//-----------------------------------------------------------------------------
// Запись
метод ЗаписатьПолеСтрока(ЗаписьXml: ЗаписьXml, ИмяПоля: Строка, Значение: Строка? = Неопределено)
    ЗаписьXml.ЗаписатьНачалоЭлемента(ИмяПоля)
    если Значение != Неопределено
        ЗаписьXml.ЗаписатьТекст(Значение как Строка)
    ;
    ЗаписьXml.ЗаписатьКонецЭлемента()    
;

метод ЗаписатьПолеЧисло(ЗаписьXml: ЗаписьXml, ИмяПоля: Строка, Значение: Число)
    ЗаписьXml.ЗаписатьНачалоЭлемента(ИмяПоля)
    ЗаписьXml.ЗаписатьЧисло(Значение)
    ЗаписьXml.ЗаписатьКонецЭлемента()    
;

метод ЗаписатьПолеДатаВремя(ЗаписьXml: ЗаписьXml, ИмяПоля: Строка, Значение: ДатаВремя)
    ЗаписьXml.ЗаписатьНачалоЭлемента(ИмяПоля)
    ЗаписьXml.ЗаписатьДатаВремя(Значение)
    ЗаписьXml.ЗаписатьКонецЭлемента()    
;

метод ЗаписатьПолеУуид(ЗаписьXml: ЗаписьXml, ИмяПоля: Строка, Значение: Ууид?, 
                       Аттрибуты: Соответствие<Строка, Строка>? = Неопределено)
    ЗаписьXml.ЗаписатьНачалоЭлемента(ИмяПоля)
    если Аттрибуты != Неопределено
        для КлючЗначение из Аттрибуты
            ЗаписьXml.ЗаписатьАтрибут(КлючЗначение.Ключ, КлючЗначение.Значение)
        ;
    ;
    если Значение != Неопределено
        ЗаписьXml.ЗаписатьУуид(Значение)
    ;
    ЗаписьXml.ЗаписатьКонецЭлемента()    
;
Для того чтобы фоновые задания запустились автоматически, добавьте в проект модуль проекта и напишите в нем следующий код:
импорт ОбменДанными

@НастройкаПриложения(Ид="ЗапускРегламентныхЗаданий", Номер = 1)
метод ВключитьЗапланированныеЗадания()
    ОбменДанными.ВключитьЗаданиеПоОбменуИзмениями()
;
Метод ВключитьЗапланированныеЗадания запустит фоновые задания. Данный метод будет вызван автоматически при первой публикации проекта.

После того как вы выполнили все шаги по созданию проекта, его следует опубликовать. Для базы «Склад1» выполните аналогичные настройки.

Настройка в режиме исполнения

  1. Откройте приложение «Офис» и перейдите в план обмена Склады:
    • Узел «Офис» будет создан автоматически. Кликните на него и укажите код системы.
    • Добавьте узел «Склад1». Укажите для него код системы и путь к каталогу обмена.
  2. Откройте приложение «Склад1» и перейдите в план обмена Склады:
    • Узел «Склад1» будет создан автоматически. Кликните на него и укажите код системы.
    • Добавьте узел «Офис». Укажите для него код системы и путь к каталогу обмена.
Важно: Коды систем должны совпадать в обоих приложениях. Код системы — это основной реквизит, по которому идентифицируются узлы плана обмена.

Выполнение обмена

Рассмотрим более подробно процесс обмена данными:

  1. Откройте приложение «Офис» в режиме исполнения. Создайте расходную накладную.
    • Как только вы ее создадите, автоматически будет зарегистрирован факт изменения расходной накладной для узла «Склад1». Информация об изменениях будет записана в таблицу регистрации изменений элемента проекта Документ.
    • Фоновым заданием, будет запущен обмен сообщениями и осуществлена отправка:
      метод ВыполнитьОбмен()
          для Строка из Запрос{ВЫБРАТЬ Ссылка  ИЗ Склады ГДЕ ЭтотУзел == Ложь}.Выполнить()
              знч Узел = Строка.Ссылка.ЗагрузитьОбъект()
              если Узел.ПутьКаталогаОбмена.Пусто() 
                  выбросить новый ИсключениеНедопустимоеСостояние("В узле не установлен ПутьКаталогаОбмена: %{Узел.Код}")
              ;
              ПрочитатьСообщениеОбмена(Узел)
              ЗаписатьСообщениеОбмена(Узел)
          ;
      ;
      Если для офиса есть непрочитанное сообщение, оно также будет прочитано в результате вызова данного метода. Более подробно чтение сообщений будет рассмотрено далее на примере приложения «Склад1».
      • Будет создано сообщение для узла «Склад1»:
        исп ОбработкаИсходящегоСообщенияОбмена = Склады.СоздатьОбработкуИсходящегоСообщения(ЗаписьXml, Получатель)
        • Сформируется номер сообщения:
          • Из таблицы плана обмена будет выбран узел плана обмена «Склад1».
          • В таблицу плана обмена, в узел «Склад1», в поле НомерОтправленного, будет записан новый номер отправленного сообщения — последний сформированный номер сообщения + 1 (например, номер 101)
        • В XML будет записан заголовок сообщения.
      • Сформируется тело сообщения. Способ формирования тела сообщения определяется разработчиком проекта:
        метод ЗаписатьСообщениеОбмена(Получатель: Склады.Объект)
            знч ИмяФайла ="%{Склады.ПолучитьЭтотУзел().ЗагрузитьОбъект().Код}_%{Получатель.Код}"
            знч ФайлСообщения = Файлы.СоздатьВременныйФайл(Суффикс = ".zip")
        
            // архивирование
            знч ZipСообщенияОбмена = новый ЗаписьZip(ФайлСообщения.ОткрытьПотокЗаписи())
            ZipСообщенияОбмена.Добавить(ЗаписатьXmlСообщениеОбмена(Получатель.Ссылка).ОткрытьПотокЧтения(), ИмяФайла + ".xml" )
            ZipСообщенияОбмена.Записать()
            
            // перемещение
            Файлы.Скопировать(
                ФайлСообщения.Путь,
                "%{Получатель.ПутьКаталогаОбмена}\\$ИмяФайла.zip",
                новый НастройкиКопированияФайлов()
                    .ПропускатьСуществующие(Ложь)) 
        ;
        
        метод ЗаписатьXmlСообщениеОбмена(Получатель: Склады.Ссылка): Файл
            исп ПотокЗаписи = новый СтроковыйПотокЗаписи()
            знч ЗаписьXml = новый ЗаписьXml(ПотокЗаписи)
        
            область
                исп ОбработкаИсходящегоСообщенияОбмена = Склады.СоздатьОбработкуИсходящегоСообщения(ЗаписьXml, Получатель)
                исп ВыборкаДанных = Склады.ВыбратьИзмененияВСообщение(Получатель, ОбработкаИсходящегоСообщенияОбмена.НомерСообщения, 
                                                                      {Тип<РасходнаяНакладная.Объект>})
                для Данные из ВыборкаДанных   
                    если Данные это РасходнаяНакладная.Объект
                        ЗаписатьИзменениеНакладной(ЗаписьXml, Получатель, Данные как РасходнаяНакладная.Объект)                    
                    иначе если Данные это УдалениеОбъекта
                        ЗаписатьУдалениеОбъекта(ЗаписьXml, Данные как УдалениеОбъекта)
                    ;
                ;
            ;
            знч СообщенияОбмена = Файлы.СоздатьВременныйФайл(УдалитьПослеОкончанияРаботы = Ложь)
            знч НастройкиЗаписиДанных = новый НастройкиЗаписиДанных()
            НастройкиЗаписиДанных.МеткаПорядкаБайтов = МеткаПорядкаБайтов.Добавлять
            исп ФайлПотокЗаписи = СообщенияОбмена.ОткрытьПотокЗаписи()
            знч ЗаписьДанных = новый ЗаписьДанных(ФайлПотокЗаписи, НастройкиЗаписиДанных)
            ЗаписьДанных.ЗаписатьСтроку(ПотокЗаписи.ВСтроку())
            возврат СообщенияОбмена
            
        ;
        • Формируется выборка измененных данных для передачи их в нужный узел плана обмена:
          исп ВыборкаДанных = Склады.ВыбратьИзмененияВСообщение(Получатель, НомерСообщения)

          При этом в запись таблицы регистрации изменений, в поле НомерСообщения, проставляется номер сообщения обмена данными, в котором должны передаваться изменения. Номер сообщения в записи таблицы регистрации проставляется для того, чтобы при подтверждении приема сообщения, в котором передавались изменения, соответствующие записи регистрации изменений были удалены и в дальнейшем изменения больше не передавались. Если сообщение было отправлено, но до получения подтверждения о получении в объект (в примере — запись документа) были внесены дополнительные изменения, то номер сообщения (к примеру, номер 5) стирается. Таким образом, когда будет получено подтверждение о получении сообщения с номером 5, запись из таблицы регистрации изменений, в которой содержатся уже новые изменения, еще не отправленные, удалена не будет. При формировании нового сообщения для отправки, данной записи будет присвоен новый номер сообщения.

        • Формируется XML, после чего изменения данных записываются в тело сообщения:
          метод ЗаписатьИзменениеНакладной(ЗаписьXml: ЗаписьXml, Получатель: Склады.Ссылка, Данные: РасходнаяНакладная.Объект)
              ЗаписьXml.ЗаписатьНачалоЭлемента("DocumentObject.Накладные")
              
              ЗаписатьПолеУуид(ЗаписьXml, "Ref", Данные.Ссылка.Ид)
              ЗаписатьПолеДатаВремя(ЗаписьXml, "Дата", Данные.Дата)
              ЗаписатьПолеСтрока(ЗаписьXml, "Номер", Данные.Номер)
              ЗаписатьПолеСтрока(ЗаписьXml, "Description", Данные.Товар)
              ЗаписатьПолеЧисло(ЗаписьXml, "Количество", Данные.Количество)
              ЗаписатьПолеЧисло(ЗаписьXml, "Цена", Данные.Цена)
              ЗаписатьПолеЧисло(ЗаписьXml, "Сумма", Данные.Сумма)
              ЗаписатьПолеСтрока(ЗаписьXml, "Поставщик", Данные.Поставщик)
              
              ЗаписьXml.ЗаписатьКонецЭлемента()
          ;
          
          метод ЗаписатьПолеСтрока(ЗаписьXml: ЗаписьXml, ИмяПоля: Строка, Значение: Строка? = Неопределено)
              ЗаписьXml.ЗаписатьНачалоЭлемента(ИмяПоля)
              если Значение != Неопределено
                  ЗаписьXml.ЗаписатьТекст(Значение как Строка)
              ;
              ЗаписьXml.ЗаписатьКонецЭлемента()    
          ;
          
          метод ЗаписатьПолеЧисло(ЗаписьXml: ЗаписьXml, ИмяПоля: Строка, Значение: Число)
              ЗаписьXml.ЗаписатьНачалоЭлемента(ИмяПоля)
              ЗаписьXml.ЗаписатьЧисло(Значение)
              ЗаписьXml.ЗаписатьКонецЭлемента()    
          ;
          
          метод ЗаписатьПолеДатаВремя(ЗаписьXml: ЗаписьXml, ИмяПоля: Строка, Значение: ДатаВремя)
              ЗаписьXml.ЗаписатьНачалоЭлемента(ИмяПоля)
              ЗаписьXml.ЗаписатьДатаВремя(Значение)
              ЗаписьXml.ЗаписатьКонецЭлемента()    
          ;
          
          метод ЗаписатьПолеУуид(ЗаписьXml: ЗаписьXml, ИмяПоля: Строка, Значение: Ууид?, Аттрибуты: Соответствие<Строка, Строка>? = Неопределено)
              ЗаписьXml.ЗаписатьНачалоЭлемента(ИмяПоля)
              если Аттрибуты != Неопределено
                  для КлючЗначение из Аттрибуты
                      ЗаписьXml.ЗаписатьАтрибут(КлючЗначение.Ключ, КлючЗначение.Значение)
                  ;
              ;
              если Значение != Неопределено
                  ЗаписьXml.ЗаписатьУуид(Значение)
              ;
              ЗаписьXml.ЗаписатьКонецЭлемента()    
          ;
      • Сообщение записывается в файл.
  2. В это время, в приложении «Склад1» в режиме исполнения:
    • Фоновым заданием, будет запущен обмен сообщениями, в результате чего сообщение, отправленное из офиса на склад, будет принято:
      метод ВыполнитьОбмен()
          для Строка из Запрос{ВЫБРАТЬ Ссылка  ИЗ Склады ГДЕ ЭтотУзел == Ложь}.Выполнить()
              знч Узел = Строка.Ссылка.ЗагрузитьОбъект()
              если Узел.ПутьКаталогаОбмена.Пусто() 
                  выбросить новый ИсключениеНедопустимоеСостояние("В узле не установлен ПутьКаталогаОбмена: %{Узел.Код}")
              ;
              ПрочитатьСообщениеОбмена(Узел)
              ЗаписатьСообщениеОбмена(Узел)
          ;
      ;
      • Сообщение обмена будет считано (в примере — будет считан файл с диска):
        метод ПрочитатьСообщениеОбмена(Отправитель: Склады.Объект)
            знч ИмяФайлаZip = "%{Отправитель.Код}_%{Склады.ПолучитьЭтотУзел().ЗагрузитьОбъект().Код}.zip" 
            ПрочитатьСообщениеОбмена(Отправитель, ИмяФайлаZip, Отправитель.ПутьКаталогаОбмена)
        ;    
        
        метод ПрочитатьСообщениеОбмена(Отправитель: Склады.Объект, ИмяФайлаZip: Строка, ПутьКаталогаОбмена: Строка)
            знч ПутьZipСообщенияОбмена = "$ПутьКаталогаОбмена\\$ИмяФайлаZip"
            знч ПутьКаталогаОбменаЧтение = "$ПутьКаталогаОбмена\\read"
            знч ПутьКаталогаОбменаЧтениеZip = "$ПутьКаталогаОбменаЧтение\\zip"
            знч ПутьКаталогаОбменаЧтениеРаспакован = "$ПутьКаталогаОбменаЧтение\\extracted"
            знч ПутьСообщенияОбменаЧтениеZip =  "$ПутьКаталогаОбменаЧтениеZip\\$ИмяФайлаZip"
            
            // перемещение
            если новый Файл(ПутьZipСообщенияОбмена).Существует()
                Файлы.Переместить(
                    ПутьZipСообщенияОбмена,
                    ПутьСообщенияОбменаЧтениеZip,
                    новый НастройкиКопированияФайлов()
                        .ПропускатьСуществующие(Ложь)) 
            ;
            
            если новый Файл(ПутьСообщенияОбменаЧтениеZip).Существует()
                
                // удаление разархивированных        
                Файлы.Удалить(ПутьКаталогаОбменаЧтениеРаспакован, Истина)
                // разархивирование
                знч ZipСообщенияОбмена = новый ФайлZip(ПутьСообщенияОбменаЧтениеZip)
                ZipСообщенияОбмена.ИзвлечьВсе(ПутьКаталогаОбменаЧтениеРаспакован)
            ; 
            
            // чтение        
            если новый Файл(ПутьКаталогаОбменаЧтениеРаспакован).Существует()
                знч НайденныеФайлы = Файлы.Найти(ПутьКаталогаОбменаЧтениеРаспакован,
                    новый НастройкиПоискаФайлов()
                        .ИсключитьКаталоги(Истина)
                        .ИсключитьСсылки(Истина)
                        .ИмяСодержит("xml")) 
                для НайденныйФайл из НайденныеФайлы
                    если НайденныйФайл.Расширение != "xml"
                        продолжить
                    ;
                    
                    ПрочитатьXmlСообщениеОбмена(Отправитель.Ссылка, НайденныйФайл.ОткрытьПотокЧтения())
                ;
            ;
        ; 
      • Тело сообщения будет прочитано. Каким образом считывать тело сообщения, разработчику следует определить самостоятельно:
        метод ПрочитатьXmlСообщениеОбмена(Отправитель: Склады.Ссылка, ПотокФайла: ПотокЧтения)
            знч ЧтениеXml = новый ЧтениеXml(ПотокФайла)
            исп ГрупповаяОперация.Начать(РежимЗагрузкиДанных = Истина)
            исп ОбработкаВходящегоСообщенияОбмена = Склады.СоздатьОбработкуВходящегоСообщения(ЧтениеXml, ДопустимыйНомерСообщенияОбмена.Больший)
            знч ОтправительСсылка = ОбработкаВходящегоСообщенияОбмена.Отправитель как Склады.Ссылка
            если ОтправительСсылка != Отправитель
                выбросить новый ИсключениеНедопустимоеСостояние("Отправитель в сообщении отличается от отправителя каталога")
            ;
            Склады.УдалитьРегистрациюИзмененийПоСообщению(ОтправительСсылка, ОбработкаВходящегоСообщенияОбмена.НомерПринятого)
            пока ЭтоИзменениеДанных(ЧтениеXml)
                ПрочитатьИзменениеДанных(ЧтениеXml, ОтправительСсылка)
            ;
        ;    
        
        метод ЭтоИзменениеДанных(ЧтениеXml: ЧтениеXml): Булево
            возврат ЧтениеXml.ВидУзла == ВидУзлаXml.НачалоЭлемента
        ;
        
        метод ПрочитатьИзменениеДанных(ЧтениеXml: ЧтениеXml, Отправитель: Склады.Ссылка)
            знч ИмяЭлемента = ЧтениеXml.Имя
            если ИмяЭлемента == "DocumentObject.Накладные"
                ПрочитатьИзменениеНакладной(ЧтениеXml, Отправитель)
            иначе если ИмяЭлемента == "ObjectDeletion"
                ПрочитатьУдалениеОбъекта(ЧтениеXml)
            иначе
                ЧтениеXml.Пропустить()   
            ;
        ;
        • Будет обработан заголовок сообщения:
          • Будет проверено, что Получатель — «Склад1».
          • Будет проверено, что полученное сообщение имеет больший номер, чем номер сообщения, полученного ранее.
        • Затем будут обработаны полученные изменения данных:
          • Данные из XML будут считаны и записаны в базу данных приложения:
            метод ПрочитатьИзменениеНакладной(ЧтениеXml: ЧтениеXml, Отправитель: Склады.Ссылка)
                ЧтениеXml.Следующий()
                знч Ref = ПрочитатьПолеУуид(ЧтениеXml, "Ref")
                знч Ссылка = РасходнаяНакладная.ПолучитьСсылку(Ref)
                пер Объект: РасходнаяНакладная.Объект
                знч НайденныйОбъект = Ссылка.ЗагрузитьОбъект(Ложь)
                если НайденныйОбъект это Неопределено
                    Объект = новый РасходнаяНакладная.Объект(Ref) 
                иначе
                    Объект = НайденныйОбъект как РасходнаяНакладная.Объект
                ;
                Объект.Дата = ПрочитатьПолеДатаВремя(ЧтениеXml, "Дата")
                Объект.Номер = ПрочитатьПолеСтрока(ЧтениеXml, "Номер")
                Объект.Товар = ПрочитатьПолеСтрока(ЧтениеXml, "Description")
                Объект.Количество = ПрочитатьПолеЧисло(ЧтениеXml, "Количество")
                Объект.Цена = ПрочитатьПолеЧисло(ЧтениеXml, "Цена")
                Объект.Сумма = ПрочитатьПолеЧисло(ЧтениеXml, "Сумма")
                Объект.Поставщик = ПрочитатьПолеСтрока(ЧтениеXml, "Поставщик")
                ЧтениеXml.Пропустить() 
                Объект.Записать()
            ;
      • В таблицу плана обмена, для узла «Офис», в поле НомерПринятого, будет записан номер полученного сообщения (в примере — номер 101).
    • Затем будет сформировано и отправлено сообщение обмена для узла «Офис»:
      • Процесс отправки будет выглядеть аналогично тому, что был рассмотрен для приложения «Офис».
      • В таблицу плана обмена, для узла «Офис», в поле НомерОтправленного, будет записан номер отправленного сообщения (например, 102).
  3. Затем в приложении «Офис» в режиме исполнения:
    • Фоновым заданием, будет принято сообщение, отправленное из склада в офис:
      • Процесс отправки будет выглядеть аналогично тому, что был рассмотрен для приложения «Склад1».
      • Из заголовка сообщения будет считан номер последнего полученного складом сообщения (номер 101).
      • Из таблицы регистрации изменений будут удалены факты регистрации изменений данных для объектов, удовлетворяющих следующим условиям:
        • Узел = «Склад1».
        • НомерСообщения <= 101.