gifts2017

Доработка механизма версионирования объектов

Опубликовал Александр Загребельный (hobi) в раздел Администрирование - Системное

Пара небольших доработок:
1. Исправлена ошибка построения отчета по версиям объектов для случая, когда реквизиты имеют составной тип.
2. Доработан механизм сохранения версий - при записи неизмененных объектов новые версии не создаются.

Ошибка построения отчета по версиям объектов присутствует в "Библиотеке стандартных подсистем" и, как следствие, в типовых конфигурациях.  В этом можно убедиться на конфигурации Бухгалтерия предприятия, редакция 3.0 (3.0.39.56), если включить версионирование документа "СчетФактураВыданный".

На Рис.1 отображен результат ошибочного построения. Этот же отчет после исправления ошибки показан на Рис.2.

В архивном файле публикации:
  - файл ""Общий модуль ВерсионированиеОбъектов_ Модуль.txt" " содержит исправленную функцию  ВерсионированиеОбъектов.РазборПредставленияОбъектаXML(), которая участвует в построении корректного отчета о версии объекта.
  - файл ""Общий модуль ВерсионированиеОбъектовСобытия_ Модуль.txt" " включает доработанный механизм сохранения версий объектов, неизмененные версии объектов не создаются.

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


Скачать файлы

Наименование Файл Версия Размер
Исправление 6
.rar 138,36Kb
09.05.15
6
.rar 138,36Kb Скачать

См. также

PowerTools от 1 000
Подписаться Добавить вознаграждение
Комментарии
1. Николай Терновой (ojiojiowka) 10.05.15 13:43
Вся суть инфостарта: народ жмется даже кусочек кода выложить просто так.
2. Роман Ложкин (webester) 10.05.15 14:12
(1)Это не вся суть, :) но такое иногда имеет место быть.
3. Александр Загребельный (hobi) 10.05.15 14:42
(1) ojiojiowka, кусочек не жалко, но он большой и их два :)
Кусочек общего модуля ВерсионированиеОбъектов с исправлением ошибки в функции РазборПредставленияОбъектаXML():

// +AZ 
Функция ПроверитьСоставныеТипы(ЧтениеXML,МетаданныеОбъекта,ИмяРеквизита,ЗначенияРеквизитов,ОписаниеРеквизита,УровеньЧтения,НовоеЗР,ВызовИзТЧ,ЗначениеПоля)
	
	ОписаниеРеквизита = МетаданныеОбъекта.Реквизиты.Найти(ИмяРеквизита);
	ЗначениеРеквизитаДобавлено = Ложь;
	Если ЧтениеXML.КоличествоАтрибутов() > 0 Тогда
		ЧтениеXML.Прочитать();  // Текст
		ТипЗР = Неопределено;
		ЗнРеквизита = Неопределено;
		//НовоеЗР = ЗначенияРеквизитов.Добавить();
		//НовоеЗР.НаименованиеРеквизита = ИмяРеквизита;
		// Переберем возможные типы реквизита и попытаемся среди них найти реквизит с правильной ссылкой
		Для каждого ОдинИзТипов Из ОписаниеРеквизита.Тип.Типы() Цикл
			ТипЗР = ОдинИзТипов;
			Попытка
				УИД = Новый УникальныйИдентификатор(ЧтениеXML.Значение);
				// если это не УИД, будет исключение
				ЗнРеквизита = XMLЗначение(ТипЗР, ЧтениеXML.Значение);
				Если  ЗнРеквизита.ПолучитьОбъект() <> Неопределено Тогда
					Прервать; // Это нужный тип из составного типа
				КонецЕсли;	  
			Исключение
				// Это не УИД
				ЗнРеквизита = ЧтениеXML.Значение;
				Прервать;
			КонецПопытки;
		КонецЦикла;
		Если ВызовИзТЧ Тогда
			ЗначениеПоля = ЗнРеквизита;
		Иначе	
			НовоеЗР = ЗначенияРеквизитов.Добавить();
			НовоеЗР.НаименованиеРеквизита = ИмяРеквизита;
			НовоеЗР.ЗначениеРеквизита = ЗнРеквизита;
			НовоеЗР.Тип = ТипЗР;
		КонецЕсли;	
		ЗначениеРеквизитаДобавлено = Истина;
		Если ЧтениеXML.Имя <> "Row" И ЧтениеXML.Имя <> "" Тогда
			УровеньЧтения = УровеньЧтения - 1;
		КонецЕсли;  
	КонецЕсли;
	Возврат ЗначениеРеквизитаДобавлено;

КонецФункции
// -AZ 

Функция РазборПредставленияОбъектаXML(ДвоичныеДанные, Ссылка) Экспорт
	
	// Содержит имя метаданного измененного объекта.
	Перем ИмяОбъекта;
	
	// Содержит положение маркера в дереве XML.
	// Требуется для идентификации текущего элемента.
	Перем УровеньЧтения;
	
	// Содержат значения реквизитов справочников / документов.
	ЗначенияРеквизитов = Новый ТаблицаЗначений;
	
	ЗначенияРеквизитов.Колонки.Добавить("НаименованиеРеквизита");
	ЗначенияРеквизитов.Колонки.Добавить("ЗначениеРеквизита");
	ЗначенияРеквизитов.Колонки.Добавить("ТипРеквизита");
	ЗначенияРеквизитов.Колонки.Добавить("Тип");
	
	ТабличныеЧасти = Новый Соответствие;
	
	ЧтениеXML = Новый ЧтениеFastInfoSet;
	
	ЧтениеXML.УстановитьДвоичныеДанные(ДвоичныеДанные);
	
	// Уровень позиции маркера в иерархии XML:
	// 0 - уровень не задан
	// 1 - первый элемент (имя объекта)
	// 2 - описание реквизита или табличной части
	// 3 - описание строки табличной части
	// 4 - описание поля строки табличной части.
	УровеньЧтения = 0;
	
	МетаданныеОбъекта = Ссылка.Метаданные();
	ТабличныеЧастиМТД = МетаданныеОбъекта.ТабличныеЧасти;
	
	ТипЗначения = "";
	
	ТипЗначенияПоляТЧ = "";
	
	// Основной цикл разбора по XML.
	Пока ЧтениеXML.Прочитать() Цикл
		
		Если ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
			УровеньЧтения = УровеньЧтения + 1;
			Если УровеньЧтения = 1 Тогда // Указатель на первом элементе XML - корень XML.
				ИмяОбъекта = ЧтениеXML.Имя;
			ИначеЕсли УровеньЧтения = 2 Тогда // Указатель на втором уровне - это реквизит или имя табличной части.
				ИмяРеквизита = ЧтениеXML.Имя;
				ОписаниеРеквизита = Неопределено;
				НовоеЗР = Неопределено;
				ЗначениеПоля = Неопределено;
				ЗначениеРеквизитаДобавлено = ПроверитьСоставныеТипы(ЧтениеXML,МетаданныеОбъекта,ИмяРеквизита,ЗначенияРеквизитов,ОписаниеРеквизита,УровеньЧтения,НовоеЗР,Ложь,ЗначениеПоля);
				
				// Любой реквизит "может оказаться" табличной частью, поэтому на всякий случай его запомним.
				ИмяТабличнойЧасти = ИмяРеквизита;
				Если МетаданныеОбъекта.ТабличныеЧасти.Найти(ИмяТабличнойЧасти) <> Неопределено И ТабличныеЧасти[ИмяТабличнойЧасти] = Неопределено Тогда
					ТабличныеЧасти.Вставить(ИмяТабличнойЧасти, Новый ТаблицаЗначений);
				КонецЕсли;
				
				Если Не ЗначениеРеквизитаДобавлено Тогда
					НовоеЗР = ЗначенияРеквизитов.Добавить();
					НовоеЗР.НаименованиеРеквизита = ИмяРеквизита;
				КонецЕсли;
				
// +AZ 
				//Если ЧтениеXML.КоличествоАтрибутов() > 0 Тогда
				//	Пока ЧтениеXML.ПрочитатьАтрибут() Цикл
				//		Если ЧтениеXML.ТипУзла = ТипУзлаXML.Атрибут 
				//		   И ЧтениеXML.Имя = "xsi:type" Тогда
				//			НовоеЗР.ТипРеквизита = ЧтениеXML.Значение;
				//			
				//			XMLТип = ЧтениеXML.Значение;
				//			
				//			Если Лев(XMLТип, 3) = "xs:" Тогда
				//				НовоеЗР.Тип = ИзXMLТипа(Новый ТипДанныхXML(Прав(XMLТип, СтрДлина(XMLТип)-3), "http://www.w3.org/2001/XMLSchema"));
				//			Иначе
				//				НовоеЗР.Тип = ИзXMLТипа(Новый ТипДанныхXML(XMLТип, ""));
				//			КонецЕсли;
				//			
				//		КонецЕсли;
				//	КонецЦикла;
				//КонецЕсли;
// -AZ 
				
				Если Не ЗначениеЗаполнено(НовоеЗР.Тип) Тогда
// +AZ ОписаниеРеквизита получены значение выше по коду 					
//                  ОписаниеРеквизита = МетаданныеОбъекта.Реквизиты.Найти(НовоеЗР.НаименованиеРеквизита);
// -AZ					
					Если ОписаниеРеквизита = Неопределено Тогда
						
						НаименованиеРеквизита = ПолучитьПредставлениеРеквизитаНаЯзыке(НовоеЗР.НаименованиеРеквизита);
						
						Если ОбщегоНазначения.ЭтоСтандартныйРеквизит(МетаданныеОбъекта.СтандартныеРеквизиты, НаименованиеРеквизита) Тогда
							
							ОписаниеРеквизита = МетаданныеОбъекта.СтандартныеРеквизиты[НаименованиеРеквизита];
							
						КонецЕсли;
						
					КонецЕсли;
					
					Если ОписаниеРеквизита <> Неопределено
						И ОписаниеРеквизита.Тип.Типы().Количество() = 1 Тогда
						НовоеЗР.Тип = ОписаниеРеквизита.Тип.Типы()[0];
					КонецЕсли;
					
				КонецЕсли;
				
			ИначеЕсли (УровеньЧтения = 3) И (ЧтениеXML.Имя = "Row") Тогда // Указатель на поле табличной части.
				Если ТабличныеЧасти[ИмяТабличнойЧасти] = Неопределено Тогда
					ТабличныеЧасти.Вставить(ИмяТабличнойЧасти, Новый ТаблицаЗначений);
				КонецЕсли;
				
				ТабличныеЧасти[ИмяТабличнойЧасти].Добавить();
			ИначеЕсли УровеньЧтения = 4 Тогда // Указатель на поле табличной части.
				
				ТипЗначенияПоляТЧ = "";
				
				ИмяПоляТЧ = ЧтениеXML.Имя; // 
// +AZ 
				ОписаниеРеквизита = Неопределено;
				НовоеЗР = Неопределено;
				ЗначениеПоля = Неопределено;
				ЗначениеРеквизитаДобавлено = ПроверитьСоставныеТипы(ЧтениеXML,МетаданныеОбъекта,ИмяПоляТЧ,ЗначенияРеквизитов,ОписаниеРеквизита,УровеньЧтения,НовоеЗР,Истина,ЗначениеПоля);
// -AZ 
				Таблица   = ТабличныеЧасти[ИмяТабличнойЧасти];
				Если Таблица.Колонки.Найти(ИмяПоляТЧ)= Неопределено Тогда
					Таблица.Колонки.Добавить(ИмяПоляТЧ);
				КонецЕсли;
				
// +AZ 
				Если ЗначениеРеквизитаДобавлено Тогда
				    ПоследняяСтрока = ТабличныеЧасти[ИмяТабличнойЧасти].Получить(ТабличныеЧасти[ИмяТабличнойЧасти].Количество()-1);
				    ПоследняяСтрока[ИмяПоляТЧ] = ЗначениеПоля;
				КонецЕсли;	
				//Если ЧтениеXML.КоличествоАтрибутов() > 0 Тогда
				//	Пока ЧтениеXML.ПрочитатьАтрибут() Цикл
				//		Если ЧтениеXML.ТипУзла = ТипУзлаXML.Атрибут 
				//		   И ЧтениеXML.Имя = "xsi:type" Тогда
				//			XMLТип = ЧтениеXML.Значение;
				//			
				//			Если Лев(XMLТип, 3) = "xs:" Тогда
				//				ТипЗначенияПоляТЧ = ИзXMLТипа(Новый ТипДанныхXML(Прав(XMLТип, СтрДлина(XMLТип)-3), "http://www.w3.org/2001/XMLSchema"));
				//			Иначе
				//				ТипЗначенияПоляТЧ = ИзXMLТипа(Новый ТипДанныхXML(XMLТип, ""));
				//			КонецЕсли;
				//			
				//		КонецЕсли;
				//	КонецЦикла;
				//КонецЕсли;
// -AZ 
				
			КонецЕсли;
		ИначеЕсли ЧтениеXML.ТипУзла = ТипУзлаXML.КонецЭлемента Тогда
			УровеньЧтения = УровеньЧтения - 1;
			ТипЗначения = "";
		ИначеЕсли ЧтениеXML.ТипУзла = ТипУзлаXML.Текст Тогда
			Если (УровеньЧтения = 2) Тогда // значение реквизита
				Попытка
					НовоеЗР.ЗначениеРеквизита = ?(ЗначениеЗаполнено(НовоеЗР.Тип), XMLЗначение(НовоеЗР.Тип, ЧтениеXML.Значение), ЧтениеXML.Значение);
				Исключение
					НовоеЗР.ЗначениеРеквизита = ЧтениеXML.Значение;
				КонецПопытки;
			ИначеЕсли (УровеньЧтения = 4) Тогда // значение реквизита
				ПоследняяСтрока = ТабличныеЧасти[ИмяТабличнойЧасти].Получить(ТабличныеЧасти[ИмяТабличнойЧасти].Количество()-1);
				
				Если ТипЗначенияПоляТЧ = "" Тогда
					ОписаниеРТЧ = Неопределено;
					Если ТабличныеЧастиМТД.Найти(ИмяТабличнойЧасти) <> Неопределено Тогда
						ОписаниеРТЧ = ТабличныеЧастиМТД[ИмяТабличнойЧасти].Реквизиты.Найти(ИмяПоляТЧ);
						
						Если ОписаниеРТЧ <> Неопределено
						   И ОписаниеРТЧ.Тип.Типы().Количество() = 1 Тогда
							ТипЗначенияПоляТЧ = ОписаниеРТЧ.Тип.Типы()[0];
						КонецЕсли;
					КонецЕсли;					
				КонецЕсли;
				
				ПоследняяСтрока[ИмяПоляТЧ] = ?(ЗначениеЗаполнено(ТипЗначенияПоляТЧ), XMLЗначение(ТипЗначенияПоляТЧ, ЧтениеXML.Значение), ЧтениеXML.Значение);
				
			КонецЕсли;
		КонецЕсли;
	КонецЦикла;
	
	// Й-й этап: из списка реквизитов исключаем табличные части.
	Для Каждого Элемент Из ТабличныеЧасти Цикл
		ЗначенияРеквизитов.Удалить(ЗначенияРеквизитов.Найти(Элемент.Ключ));
	КонецЦикла;
	// ТабличныеЧастиМТД
	Для Каждого ЭлементСоответствия Из ТабличныеЧасти Цикл
		Таблица = ЭлементСоответствия.Значение;
		Если Таблица.Колонки.Количество() = 0 Тогда
			ТаблицаМТД = ТабличныеЧастиМТД.Найти(ЭлементСоответствия.Ключ);
			Если ТаблицаМТД <> Неопределено Тогда
				Для Каждого ОписаниеКолонки Из ТаблицаМТД.Реквизиты Цикл
					Если Таблица.Колонки.Найти(ОписаниеКолонки.Имя)= Неопределено Тогда
						Таблица.Колонки.Добавить(ОписаниеКолонки.Имя);
					КонецЕсли;
				КонецЦикла;
			КонецЕсли;
		КонецЕсли;
	КонецЦикла;
	
	Результат = Новый Структура;
	Результат.Вставить("Реквизиты", ЗначенияРеквизитов);
	Результат.Вставить("ТабличныеЧасти", ТабличныеЧасти);
	
	Возврат Результат;
	
КонецФункции
...Показать Скрыть
4. Иван Петров (dgolovanov) 12.05.15 09:01
(1) ojiojiowka, что-то твоих публикаций бесплатных не видно. Жлоб, или показать нечего?
5. Алекс Ю (AlexO) 12.05.15 09:11
(3) hobi, на баг-треккер 1С отправили "привет"?
6. Александр Загребельный (hobi) 12.05.15 12:30
(5) AlexO, не отправил.
О проблеме на Инфостарте рассказал, здесь живое обсуждение :)
7. Алекс Ю (AlexO) 12.05.15 12:46
(6) hobi, можете и не отправлять ))
Все равно получите дежурное "мы приняли к сведению". Разве что так, моральное удовлетворение...
8. Павел Романов (Pawlick) 20.10.15 04:06
У меня КА 1.1, платформа 8.3.5.1517.

Функция НеСовпадаетСПредыдущейВерсией хоть убей всегда возвращает ИСТИНА, потому как у меня всегда
Источник = Источник.Ссылка = Источник.Ссылка .ПолучитьОбъект(), даже если объект был изменен.

Попытка версионирования происходит ПриЗаписи объекта (может в БСП по другому?).
04.00 - понять уже ничего не могу...

Получаю XML строкой для заведомо неизмененного объекта - всё равно:
Строка1 <> Строка2,
хотя визуально файлы одинаковые. Может потому что получены из разных временных файлов?...

Короче закончилось вот чем:

Функция ОбъектИзменен(Источник, ПоследнийНомерВерсии )Экспорт
	
	Запрос = Новый Запрос;
	Запрос.Текст = "ВЫБРАТЬ
	|	ВерсииОбъектов.ВерсияОбъекта
	|ИЗ
	|	РегистрСведений.ВерсииОбъектов КАК ВерсииОбъектов
	|ГДЕ
	|	ВерсииОбъектов.Объект = &Ссылка
	|	И ВерсииОбъектов.НомерВерсии = &НомерВерсии";
	Запрос.УстановитьПараметр("Ссылка", Источник.Ссылка);
	Запрос.УстановитьПараметр("НомерВерсии", Число(ПоследнийНомерВерсии));
	Выборка = Запрос.Выполнить().Выбрать();
	Выборка.Следующий();
	
	ВерсияОбъекта = Выборка.ВерсияОбъекта.Получить();
	ФайлПредыдущейВерсии = ПолучитьИмяВременногоФайла();	
	ВерсияОбъекта.Записать(ФайлПредыдущейВерсии);
	
	ФайлТекущейВерсии = ПолучитьИмяВременногоФайла();
	ЗаписьXML = Новый ЗаписьXML;
	ЗаписьXML.ОткрытьФайл(ФайлТекущейВерсии); 
	ЗаписьXML.ЗаписатьОбъявлениеXML();
	ЗаписатьXML(ЗаписьXML, Источник, НазначениеТипаXML.Явное);
	ЗаписьXML.Закрыть();
	ДвоичныеДанные = Новый ДвоичныеДанные(ФайлТекущейВерсии);
	ДвоичныеДанные.Записать(ФайлТекущейВерсии);
	
	СравнениеФайлов = Новый СравнениеФайлов;
	СравнениеФайлов.ПервыйФайл = ФайлПредыдущейВерсии;
	СравнениеФайлов.ВторойФайл = ФайлТекущейВерсии;
	СравнениеФайлов.СпособСравнения = СпособСравненияФайлов.ТекстовыйДокумент;
	ФайлыРавны = СравнениеФайлов.Сравнить();
	
	УдалитьФайлы(ФайлПредыдущейВерсии);
	УдалитьФайлы(ФайлТекущейВерсии);
	
	Возврат НЕ ФайлыРавны ;
		
КонецФункции
...Показать Скрыть