Доброго времени суток!

Есть распределенная база(самописная 8.2 тонкий клиент), в ней есть документ "Акт закупа". Выгрузка из переферийных баз в папку на ftp производится автоматически, загрузка в центральную по кнопочке). По сути все документы стекаются из узловых баз в центральную.
Суть проблемы: Нужно настроить полный обмен таким образом, чтобы при возникновении коллизии данного документа программа его не загружала, а формировала некий отчет, содержащий сравнение измененных реквизитов документов, подвергшихся коллизии.
Т.е.
1. В базе узла С создали документ со значением реквизита Контрагент - "Вася".
2. Произошел обмен, документ "переехал" в базу узла А.
3. В базе узла С изменили контрагента на "Петя"
4. В базе узла А изменили контрагента на "Маша"
5. Произвели обмен, произошла коллизия и в обеих базах контрагент стал "Маша".
А нужно, чтобы он в данном случае не замещал документ, а оставлял его без изменений и в конце обмена формировал отчет о сравнении.
---
sergio_krf,

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

Про вознаграждения я не в курсе, топик-то бесплатный.
---
sergio_krf, А нужно, чтобы он в данном случае не замещал документ, тогда это будет не обмен.
А "сравнить, объединить с ..." в результате на складе А по накладной ХХХ отгрузят "Маше", на складе С - "Пете" а в центральной базе, т.е. в бухгалтерии, будут требовать деньги с "Васи", или я Вас не правильно понял?

PS. А не проще запретить изменять чужие накладные?
---
Все спасибо за ответы!
(2) Win98,
Почти правильно, но есть один нюанс, топология баз - однонаправленная звезда, т.е. в центральную базу из узлов стекаютсмя все даннные, из центраольной в узловые базы переезжают только регистры и справочники(и то не все). Узловые базы друг о друге представление не имеют вообще)))
Конечно сравнение, объединение было бы идеальным, но думается мне игра не стоит свеч)))
Т.е. задача минимум - при попытке загрузки из узловой базы в центральную, определить, что документы поменялись и там и там(если документ был изменен только в узловой базе, тогда всё ОК, замещаем), отказаться от записи данного документа и сформировать отчет со сравнением реквизитов))) По окнчанию обмена показать отчет)))
К сожалению специфика работы требует, сам понимаю, что бред(((
(3) extreme,
т.е. в данном случает мы отловим изменение в базе получателе или изменения в обеих базах?
Если в обеих, то как мне теперь эти данные выловить, чтобы сравнить)))
в данный момент имею в модуле обмена следующий код:

```1c
    Процедура ЗаписатьОбменДанными()
    Попытка
    ТекДата = ТекущаяДата();
    ТекКодДБ = Константы.ПрефиксИБ.Получить();
    Если НЕ ЗначениеЗаполнено(ТекКодДБ) Тогда ТекКодДБ = "ХХХ" КонецЕсли;
    НаборЗаписей = РегистрыСведений.ОбменДанными.СоздатьНаборЗаписей();
    НаборЗаписей.Отбор.Период.Установить(ТекДата);
    НаборЗаписей.Отбор.КодСклада.Установить(Константы.ПрефиксИБ.Получить());
    НовЗапись = НаборЗаписей.Добавить();
    НовЗапись.КодСклада = ТекКодДБ;
    НовЗапись.ВремяОбмена = ТекДата;
    НовЗапись.Период = ТекДата;
    НаборЗаписей.Записать();
    Исключение
    Сообщить("косяк с установкой даты последнего автообмена");
    КонецПопытки;
    КонецПроцедуры

    Процедура ЗаписатьСообщение(Путь,Узел) Экспорт
    Попытка
    ЗаписьXML = Новый ЗаписьXML();
    ЗаписьXML.ОткрытьФайл(Путь);
    ЗаписьXML.ЗаписатьОбъявлениеXML();
    ЗаписьСообщения = ПланыОбмена.СоздатьЗаписьСообщения();
    ЗаписьСообщения.НачатьЗапись(ЗаписьXML, Узел);
    ПланыОбмена.ЗаписатьИзменения(ЗаписьСообщения,0);
    ЗаписьСообщения.ЗакончитьЗапись();
    ЗаписьXML.Закрыть();
    Исключение
    Сообщить(ОписаниеОшибки());
    КонецПопытки;
    КонецПроцедуры

    Процедура ПрочитатьСообщение(Путь) Экспорт
    Попытка
    ЗаписатьОбменДанными();
    ЧтениеСообщения = ПланыОбмена.СоздатьЧтениеСообщения();
    ЧтениеXML = Новый ЧтениеXML;
    ЧтениеXML.ОткрытьФайл(Путь);
    ЧтениеСообщения.НачатьЧтение(ЧтениеXML);
    ПланыОбмена.ПрочитатьИзменения(ЧтениеСообщения,0);
    ЧтениеСообщения.ЗакончитьЧтение();
    ЧтениеXML.Закрыть();
    Исключение
    Сообщить(ОписаниеОшибки());
    КонецПопытки;
    КонецПроцедуры

    Процедура ПриОтправкеДанныхПодчиненному(ЭлементДанных, ОтправкаЭлемента, СозданиеНачальногоОбраза)
    ТипДанных = ТипЗнч(ЭлементДанных);
    Если ТипДанных = Тип("ДокументОбъект.АктЗакупа") Тогда
    //Если ЭлементДанных.Склад <> Склад Тогда
    Если СписокСкладов.Найти(ЭлементДанных.Склад,"Склад") = Неопределено Тогда
    ОтправкаЭлемента = ОтправкаЭлементаДанных.Удалить;
    КонецЕсли;
    ИначеЕсли ТипДанных = Тип("ДокументОбъект.Перемещение") Тогда
    //Если ЭлементДанных.Склад <> Склад и ЭлементДанных.СкладПолучатель <> Склад Тогда
    Если СписокСкладов.Найти(ЭлементДанных.Склад,"Склад") = Неопределено и СписокСкладов.Найти(ЭлементДанных.СкладПолучатель,"Склад") = Неопределено Тогда
    ОтправкаЭлемента = ОтправкаЭлементаДанных.Удалить;
    КонецЕсли;
    ИначеЕсли ТипДанных = Тип("КонстантаМенеджерЗначения.ПрефиксИБ") Тогда
    ОтправкаЭлемента = ОтправкаЭлементаДанных.Игнорировать;
    ИначеЕсли ТипДанных = Тип("ДокументОбъект.КорректировкаДисконтныхКарт") Тогда
    ОтправкаЭлемента = ОтправкаЭлементаДанных.Удалить;
    КонецЕсли;
    КонецПроцедуры
```

а в модуле приложения, собственно она и рулит балом

```1c
    Процедура ВыполнитьКомандыЗапуска(Параметр)

    Текст = Новый ТекстовыйДокумент;
    Текст.Прочитать(Параметр);
    Стр = ";
    Для НомерСтроки = 1 По Текст.КоличествоСтрок() Цикл
    Стр = Стр+Текст.ПолучитьСтроку(НомерСтроки);
    КонецЦикла;

    ПотокXML = Новый ЧтениеXML;
    ПотокXML.УстановитьСтроку(Стр);

    // Выполняем разбор параметра запуска
    ПотокXML.Прочитать();
    Если ПотокXML.ТипУзла <> ТипУзлаXML.НачалоЭлемента ИЛИ ПотокXML.Имя <> "Commands" Тогда
    ВызватьИсключение "Ошибка разбора списка команд:
    |ожидается элемент 'Commands'";
    КонецЕсли;

    // Читаем последовательно команды и исполняем их
    Пока ПотокXML.Прочитать() Цикл
    Если ПотокXML.ТипУзла = ТипУзлаXML.КонецЭлемента И ПотокXML.Имя = "Commands" Тогда

    // список команд закончился, прерываем цикл
    Прервать;
    КонецЕсли;
    Если ПотокXML.ТипУзла <> ТипУзлаXML.НачалоЭлемента Тогда
    ВызватьИсключение "Ошибка разбора списка команд:
    |очередная команда не обнаружена.";
    КонецЕсли;
    Если ПотокXML.Имя = "ReadChanges" ИЛИ ПотокXML.Имя = "WriteChanges" Тогда
    Команда = ПотокXML.Имя;

    // Читаем параметры команды обмена данными
    ПотокXML.Прочитать();

    // Имя плана обмена
    Если ПотокXML.ТипУзла <> ТипУзлаXML.НачалоЭлемента ИЛИ ПотокXML.Имя <> "ExchangePlan" Тогда
    ВызватьИсключение "Ошибка разбора списка команд:
    |ожидается элемент 'ExchangePlan'";
    КонецЕсли;

    // Читаем значение
    ИмяПланаОбмена = ПрочитатьXML(ПотокXML,Тип("Строка"));
    Сообщить(ИмяПланаОбмена);
    // Получаем менеджер плана обмена
    Если Метаданные.НайтиПоПолномуИмени("ПланОбмена." + ИмяПланаОбмена) = Неопределено Тогда
    ВызватьИсключение "План обмена '" + ИмяПланаОбмена + "' не обнаружен.";
    КонецЕсли;
    ПланОбмена = ПланыОбмена[ИмяПланаОбмена];
    // Путь файла обмена
    Если ПотокXML.ТипУзла <> ТипУзлаXML.НачалоЭлемента ИЛИ ПотокXML.Имя <> "Path" Тогда
    ВызватьИсключение "Ошибка разбора списка команд:
    |ожидается элемент 'Path'";
    КонецЕсли;

    // Читаем значение
    Путь = ПрочитатьXML(ПотокXML,Тип("Строка"));
    Сообщить(Путь);

    // Код узла плана обмена
    Если ПотокXML.ТипУзла <> ТипУзлаXML.НачалоЭлемента ИЛИ ПотокXML.Имя <> "Node" Тогда
    ВызватьИсключение "Ошибка разбора списка команд:
    |ожидается элемент 'Node'";
    КонецЕсли;

    // Читаем значение
    КодУзла = ПрочитатьXML(ПотокXML,Тип("Строка"));
    Сообщить(КодУзла);

    // Ищем узел плана обмена
    УзелСсылка = ПланОбмена.НайтиПоКоду(КодУзла);
    Если УзелСсылка.Пустая() Тогда
    ВызватьИсключение "Узел план обмена '" + ИмяПланаОбмена + "' не обнаружен: " + КодУзла;
    КонецЕсли;
    Узел = УзелСсылка.ПолучитьОбъект();

    // Производим вызов команды
    Если Команда = "ReadChanges" Тогда

    // Выполняем чтение сообщения для указанного узла
    Узел.ПрочитатьСообщение(Путь);
    Иначе

    // Выполняем запись сообщения для указанного узла
    Узел.ЗаписатьСообщение(Путь,УзелСсылка);

    КонецЕсли;
    Иначе
    ВызватьИсключение "Ошибка разбора списка команд,
    |неизвестная команда '" + ПотокXML.Имя + "'";
    КонецЕсли;
    КонецЦикла;

    // все команды выполнены - завершаем работу
    Отказ = Истина;

    КонецПроцедуры
```

(5) AlexiyI,
База написана с нуля, обмен в частности)))

Проблема только в том, что обмен и подключение оборудования были не моей задачей, а стало моей((( Вот только опыта работы с обменами у меня нет(((
---
В модуле плана обмена у вас в методе ПрочитатьСообщение(Путь) целиком читаются все пришедшие данные конструкцией ПланыОбмена.ПрочитатьИзменения(ЧтениеСообщения,0). Это упрощенный подход - при таком использовании вы безусловно принимаете все входящие данные.

Я чуть ранее показал вам, как можно переписать метод чтение сообщения, при котором происходит разбор каждого пришедшего объекта (документа, элемента справочника, набора записей регистра). В этом случае вы имеете возможность проанализировать входящий объект и при желании сравнить его с объектом, который есть в текущей базе.

Кроме того, конструкцией

```1c
    Если ПланыОбмена.ИзменениеЗарегистрировано(ЧтениеСообщения.Отправитель, Данные) Тогда
    // были одновременные изменения, что-то предпринимаем;
    Иначе
    Данные.Записать();
    КонецЕсли;
```

можно проверить, что на этот объект (например, пришедший снаружи документ) в нашей базе тоже зарегистрировано изменение для выгрузки в ту базу, из которой сейчас происходит загрузка (то есть у нас объект тоже менялся). Это как раз означает, что объект одновременно изменен и в той базе, и в нашей. Тут уж принимайте решение - можно вообще не принимать объект (не вызывать Данные.Записать()) либо, например (как это делал я в своем случае), брать из пришедшего объекта только некоторые поля (те, которые "разрешено" менять в периферийной базе), а остальные поля объекта "Данные" заполнять из нашей (центральной) базы.

Логика проверок такая:
а) если к нам в сообщении пришел объект, значит, он был изменен в той базе, из которой пришло сообщение.
б) если по этому объекту в нашей базе метод ИзменениеЗарегистрировано() с указанием узла "той" базы выдает Истину - значит, в нашей базе этот объект тоже менялся.

То есть: приходит "Накладная №1, покупатель - Вася, количество - 10", в коде мы его получаем в виде объекта "Данные". Смотрим методом ИзменениеЗарегистрировано() - у нас в базе эта накладная тоже менялась. Читаем содержимое накладной из нашей базы - "Накладная №1, покупатель - Петя, количество 5". Различаются покупатели и количество.
Можем ругнуться или сохранить в логе эту ситуацию и вообще не принять входящий объект, а можем по своей внутренней логике решить, что количество - это их право, а вот покупателя менять нельзя, поэтому в объекте "Данные" заменяем значение покупателя на то, которое в нашей базе (Петя) и спокойно записываем себе скорректированный входящий объект.
---
extreme,
Спасибо, всё начинает проясняться)
Если я правильно понимаю, то мне нужно модифицировать процедуру следующим образом

```1c
    Процедура ПрочитатьСообщение(Путь,ТЗ) Экспорт
    Попытка
    ЗаписатьОбменДанными();
    ЧтениеСообщения = ПланыОбмена.СоздатьЧтениеСообщения();
    ЧтениеXML = Новый ЧтениеXML;
    ЧтениеXML.ОткрытьФайл(Путь);

    ЧтениеСообщения = ПланыОбмена.СоздатьЧтениеСообщения();
    ЧтениеСообщения.НачатьЧтение(ЧтениеXML);
    Пока ВозможностьЧтенияДанных(ЧтениеXML) Цикл
    Данные = ПрочитатьДанные(ЧтениеXML);

    Если ТипЗнч(Данные) = Тип("ДокументОбъект.АктЗакупа") Тогда

    Если Не Данные.ЭтоНовый() Тогда
    СсылкаНаУзел = ПланыОбмена.Приемка.НайтиПоКоду("Опт");

    Если ПланыОбмена.ИзменениеЗарегистрировано(СсылкаНаУзел, Данные) Тогда
    //ТЗ.КонтрагентТекущая = ? а как получить текущий???
    ТЗ.КонтрагентНовый = Данные.Контрагент;
    Иначе
    Данные.Записать();
    КонецЕсли;
    КонецЕсли;
    КонецЕсли;

    КонецЦикла;

    Исключение
    Сообщить(ОписаниеОшибки());
    КонецПопытки;
    КонецПроцедуры
```

Вот только вопрос, как мне получить объект этой базы (другими словами в Данные у меня есть загружаемый объект, а как получить текущий)?
---
sergio_krf,
1. непонятно, зачем получать узел плана обмена по наименованию - он указан в ЧтениеСообщения.Отправитель; вам надо именно отправителя полученного сообщения подставлять в метод ИзменениеЗарегистрировано(), чтобы понять, что в текущей базе зарегистрировано изменение объекта для выгрузки в ту базу (которая отправитель сообщения, которое вы сейчас разбираете)

2. затрудняюсь понять, что такое "ТЗ" в приведенном коде и для чего она

3. не уверен, что сработает проверка "Данные" на ЭтоНовый() - потестируйте в отладке. У меня применялся другой способ.

Данные - это пришедший в сообщении объект, он существует в памяти, но с нашей базой пока никак не связан. Мы можем получить из нашей базы наш экземпляр, если он существует, и затем с этими двумя экземплярами работать - сравнивать, что-то менять в объекте Данные на основе нашего экземпляра и т.д.
Посмотрел у себя в коде, ищу экземпляр в своей базе так:

```1c
    УникальныйИдентификатор = Данные.Ссылка.УникальныйИдентификатор();
    Найденный = Документы[Данные.Метаданные().Имя].ПолучитьСсылку(УникальныйИдентификатор);
    Если ЗначениеЗаполнено(Найденный) Тогда
    // у нас объект в базе с такой ссылкой есть, можно с ним работать
    // НашОбъект = Найденный.ПолучитьОбъект();
    Иначе
    // к нам пришел совсем новый объект, сравнивать не с чем
    КонецЕсли;
```

Заметим, что конструкция не универсальна, зависит от типа объекта - справочник, объект и т.д. У меня всё равно по разным объектам были разные условия обмена, поэтому прописано в явном виде. Можно, наверное, в более общем виде написать, но мне лень :)

4. Надо учесть момент: если мы при приеме объекта снаружи решили в нем что-то изменить (например, нам пришла накладная не с тем покупателем, как в нашей базе и мы собираемся его в объекте исправить), надо обязательно потом этот объект включить в обратную выгрузку (чтобы он ушел обратно в "ту" базу с нужным нам значением)

У меня было так:

```1c
    // флаг мОтправлятьИзменениеНазад выставляется, если принятый объект мы насильно корректируем
    Если мОтправлятьИзменениеНазад Тогда
    Данные.ОбменДанными.Отправитель = ПланыОбмена.Подхосты.ПустаяСсылка();
    // тогда при записи объекта в нашу базу он будет считаться принятым не из "той" базы, а неизвестно откуда, и следовательно для "той" базы попадет в обратную выгрузку
    Иначе
    // сейчас засомневался в необходимости этой строки - по идее, значение по умолчанию уже должно быть присвоено, надо проверить - может, она лишняя
    Данные.ОбменДанными.Отправитель = ЧтениеСообщения.Отправитель;
    КонецЕсли;
```

5. Мы после нескольких лет использования, к счастью, перевели региональные филиалы на работу через сервер терминалов в единой базе и весь этот сложный условный обмен я успешно грохнул :)
---
extreme,Большое спасибо за помощь!
1 - Туплю)))
2 - Тз - таблица значений в которой будет храниться описание метаданных этого гребаного документа, для сравнения и вывода отчета после загрузки)))
3 - ЭтоНовый() работает))) Я уже домучался да UIDа)))
4 - а вот в обратную строну никакие изменения отправлять не надо, им этого знать не требуется)))
5 - к сожалению опять же из-за специфики работы этого делать нельзя, т.к. удаленные точки-источник первички (которая может меняться и нам об этом надо знать), а в центральной вносят много нуансов, которые любой ценой должны быть скрыты от точек приемки)))

Еще раз большое спасибо, начинает взлетать, теперь осталось отладить)))

p.s. А как выдать вознаграждение?)
---
sergio_krf,

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

Про вознаграждения я не в курсе, топик-то бесплатный.
---
extreme,
У нас основная проблема в том, что в одной физической базе должна быть одна версия документа, а во второй другаю, но в пространстве распределенной базы (логически одна база) это должен быть один документ, как не крути)))
Значит я как-то криво его назначил)))

P.S. С наступающим!)
---
sergio_krf, Вам виднее ситуация, конечно. Мне идеологически не дает покоя мысль, что один и тот же объект будет в разных версиях :) "Не то здесь что-то..."
Для себя я бы рассматривал дальнейшие варианты - типа делать разные объекты (НакладнаяПериферийная и НакладнаяЦентральная) и формировать из одного другой. При этом в периферийной базе был бы только один объект, а в центральной - оба.
Или один объект, но с двумя наборами отдельных реквизитов.

Но, повторюсь, это абстрактное размышление - по конкретной предметной области, возможно, Ваш вариант лучше.
---
extreme,
Да мне тоже это не нравится, но использование разных объектов не обеспечит той уникальности, которую обеспечит UID, а появление дублю или неверная связь - смерти подобно (очень большой документооборот и очень "одаренные" пользователи в переферийных базах, потом концов будет не свести), т.к. часть документов потом выгружается в УТ, а часть в БП. Про наборы реквизитов я тоже думал, но ведь узловых баз уже сейчас 20, а что будет дальше?))) в этом случае слишком много избыточности))) особенно учитывая тот факт, что из ряда переферийных баз выгрузка идет через GPRS))) Поэтому, как мне кажется из всех зол было выбрано меньшее)))
---
В модуле плана обмена можно реализовать свой обработчик файла сообщения и там проверять, примерно так:

```1c
    ЧтениеXML = Новый ЧтениеXML;
    ЧтениеXML.ОткрытьФайл(ИмяФайла);
    ЧтениеСообщения = ПланыОбмена.СоздатьЧтениеСообщения();
    ЧтениеСообщения.НачатьЧтение(ЧтениеXML);
    Пока ВозможностьЧтенияДанных(ЧтениеXML) Цикл
    Если ПланыОбмена.ИзменениеЗарегистрировано(ЧтениеСообщения.Отправитель, Данные) Тогда
    // были одновременные изменения, что-то предпринимаем;
    Иначе
    Данные.Записать();
    КонецЕсли;
    КонецЦикла;
```
---
Прошу прощения, забыл добавить строку внутри цикла

```1c
    Данные = ПрочитатьДанные(ЧтениеXML);
```
---
Если база написана не с нуля: Есть обработка "Регистрация изменений для обмена" (наверное, не во всех конфах), показывает, какие данные будут перенесены в другую базу, т.е. отображает данные регистра сведений (не помню как называется). Можно перед загрузкой сравнивать значения этого регистра из обеих баз и выполнять нужные махинации (программно или вручную удалять регистрацию изменений). Еще есть рег. сведений "Коллизии при обмене", и, может, его нужно копать.