Распределенная база

1. sergio_krf 14 25.12.12 20:08 Сейчас в теме
Доброго времени суток!

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

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

Про вознаграждения я не в курсе, топик-то бесплатный.
Остальные ответы
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
2. Win98 26.12.12 08:26 Сейчас в теме
(1) sergio_krf,
А нужно, чтобы он в данном случае не замещал документ,
тогда это будет не обмен.
А "сравнить, объединить с ..." в результате на складе А по накладной ХХХ отгрузят "Маше", на складе С - "Пете" а в центральной базе, т.е. в бухгалтерии, будут требовать деньги с "Васи", или я Вас не правильно понял?

PS. А не проще запретить изменять чужие накладные?
6. sergio_krf 14 26.12.12 12:58 Сейчас в теме
Все спасибо за ответы!
(2) Win98,
Почти правильно, но есть один нюанс, топология баз - однонаправленная звезда, т.е. в центральную базу из узлов стекаютсмя все даннные, из центраольной в узловые базы переезжают только регистры и справочники(и то не все). Узловые базы друг о друге представление не имеют вообще)))
Конечно сравнение, объединение было бы идеальным, но думается мне игра не стоит свеч)))
Т.е. задача минимум - при попытке загрузки из узловой базы в центральную, определить, что документы поменялись и там и там(если документ был изменен только в узловой базе, тогда всё ОК, замещаем), отказаться от записи данного документа и сформировать отчет со сравнением реквизитов))) По окнчанию обмена показать отчет)))
К сожалению специфика работы требует, сам понимаю, что бред(((
(3) extreme,
т.е. в данном случает мы отловим изменение в базе получателе или изменения в обеих базах?
Если в обеих, то как мне теперь эти данные выловить, чтобы сравнить)))
в данный момент имею в модуле обмена следующий код:
Процедура ЗаписатьОбменДанными()
	Попытка
		ТекДата = ТекущаяДата();
		ТекКодДБ = Константы.ПрефиксИБ.Получить(); 
		Если НЕ ЗначениеЗаполнено(ТекКодДБ) Тогда ТекКодДБ = "ХХХ" КонецЕсли;		
		НаборЗаписей = РегистрыСведений.ОбменДанными.СоздатьНаборЗаписей(); 
		НаборЗаписей.Отбор.Период.Установить(ТекДата); 
		НаборЗаписей.Отбор.КодСклада.Установить(Константы.ПрефиксИБ.Получить()); 
		НовЗапись = НаборЗаписей.Добавить(); 
		НовЗапись.КодСклада = ТекКодДБ;
		НовЗапись.ВремяОбмена = ТекДата; 
		НовЗапись.Период = ТекДата; 
		НаборЗаписей.Записать();
	Исключение
		Сообщить("косяк с установкой даты последнего автообмена");
	КонецПопытки;
КонецПроцедуры	

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

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

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


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

Процедура ВыполнитьКомандыЗапуска(Параметр)
	
	Текст = Новый ТекстовыйДокумент;
	Текст.Прочитать(Параметр);
	Стр = "";
	Для НомерСтроки = 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,
База написана с нуля, обмен в частности)))

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

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

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

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

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

То есть: приходит "Накладная №1, покупатель - Вася, количество - 10", в коде мы его получаем в виде объекта "Данные". Смотрим методом ИзменениеЗарегистрировано() - у нас в базе эта накладная тоже менялась. Читаем содержимое накладной из нашей базы - "Накладная №1, покупатель - Петя, количество 5". Различаются покупатели и количество.
Можем ругнуться или сохранить в логе эту ситуацию и вообще не принять входящий объект, а можем по своей внутренней логике решить, что количество - это их право, а вот покупателя менять нельзя, поэтому в объекте "Данные" заменяем значение покупателя на то, которое в нашей базе (Петя) и спокойно записываем себе скорректированный входящий объект.
sergio_krf; +1 Ответить
8. sergio_krf 14 27.12.12 10:09 Сейчас в теме
(7) extreme,
Спасибо, всё начинает проясняться)
Если я правильно понимаю, то мне нужно модифицировать процедуру следующим образом
Процедура ПрочитатьСообщение(Путь,ТЗ) Экспорт 
	Попытка
		ЗаписатьОбменДанными();
		ЧтениеСообщения = ПланыОбмена.СоздатьЧтениеСообщения();
		ЧтениеXML = Новый ЧтениеXML;
		ЧтениеXML.ОткрытьФайл(Путь);
		
		ЧтениеСообщения = ПланыОбмена.СоздатьЧтениеСообщения();
		ЧтениеСообщения.НачатьЧтение(ЧтениеXML);
		Пока ВозможностьЧтенияДанных(ЧтениеXML) Цикл
			Данные = ПрочитатьДанные(ЧтениеXML);
			
			Если ТипЗнч(Данные) = Тип("ДокументОбъект.АктЗакупа") Тогда 
				
				Если Не Данные.ЭтоНовый() Тогда 
					СсылкаНаУзел = ПланыОбмена.Приемка.НайтиПоКоду("Опт"); 
					
					Если ПланыОбмена.ИзменениеЗарегистрировано(СсылкаНаУзел, Данные) Тогда 
						//ТЗ.КонтрагентТекущая = ? а как получить текущий???
						ТЗ.КонтрагентНовый = Данные.Контрагент;
					Иначе 
						Данные.Записать();
					КонецЕсли; 
				КонецЕсли; 
			КонецЕсли; 
			
			
		КонецЦикла;
		
		
		
	Исключение
		Сообщить(ОписаниеОшибки());
	КонецПопытки;
КонецПроцедуры
Показать

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

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

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

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

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

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

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


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

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

p.s. А как выдать вознаграждение?)
11. extreme 28.12.12 10:28 Сейчас в теме
(10) sergio_krf,

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

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

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

Но, повторюсь, это абстрактное размышление - по конкретной предметной области, возможно, Ваш вариант лучше.
14. sergio_krf 14 28.12.12 15:25 Сейчас в теме
(13) extreme,
Да мне тоже это не нравится, но использование разных объектов не обеспечит той уникальности, которую обеспечит UID, а появление дублю или неверная связь - смерти подобно (очень большой документооборот и очень "одаренные" пользователи в переферийных базах, потом концов будет не свести), т.к. часть документов потом выгружается в УТ, а часть в БП. Про наборы реквизитов я тоже думал, но ведь узловых баз уже сейчас 20, а что будет дальше?))) в этом случае слишком много избыточности))) особенно учитывая тот факт, что из ряда переферийных баз выгрузка идет через GPRS))) Поэтому, как мне кажется из всех зол было выбрано меньшее)))
3. extreme 26.12.12 10:25 Сейчас в теме
В модуле плана обмена можно реализовать свой обработчик файла сообщения и там проверять, примерно так:
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл(ИмяФайла);
ЧтениеСообщения = ПланыОбмена.СоздатьЧтениеСообщения();
ЧтениеСообщения.НачатьЧтение(ЧтениеXML);
Пока ВозможностьЧтенияДанных(ЧтениеXML) Цикл
	Если ПланыОбмена.ИзменениеЗарегистрировано(ЧтениеСообщения.Отправитель, Данные) Тогда
		// были одновременные изменения, что-то предпринимаем;
	Иначе
		Данные.Записать();
	КонецЕсли;
КонецЦикла;
Показать
sergio_krf; +1 Ответить
4. extreme 26.12.12 10:26 Сейчас в теме
Прошу прощения, забыл добавить строку внутри цикла
	Данные = ПрочитатьДанные(ЧтениеXML);
5. AlexiyI 26.12.12 10:33 Сейчас в теме
Если база написана не с нуля: Есть обработка "Регистрация изменений для обмена" (наверное, не во всех конфах), показывает, какие данные будут перенесены в другую базу, т.е. отображает данные регистра сведений (не помню как называется). Можно перед загрузкой сравнивать значения этого регистра из обеих баз и выполнять нужные махинации (программно или вручную удалять регистрацию изменений). Еще есть рег. сведений "Коллизии при обмене", и, может, его нужно копать.
Оставьте свое сообщение

Для получения уведомлений об ответах подключите телеграм бот:
Инфостарт бот