Каталог решений - Набор функций, возвращающих данные из истории версий объекта или "Кто виноват?" оптом

Набор функций, возвращающих данные из истории версий объекта или "Кто виноват?" оптом

Набор функций, возвращающих данные из истории версий объекта или "Кто виноват?" оптом

В наличии

В рамках одного проекта понадобился функционал который бы сообщал ответ на так интересующий вопрос — «КТО ПОСЛЕДНИЙ МЕНЯЛ?!?» в отчете для большого количества объектов сразу. Найти решения не удалось на просторах интернета и по этому было принято решение сделать «Своё».
Задумка очень проста — на входе ссылка на объект и интересующий нас реквизит, а на выходе все подноготная (кто, когда, из какого значения в какое). Хотел бы поделиться данными функциями с сообществом.

Категория:

Описание

Хотелось бы отметить, что программирование в наше время, призвано облегчить работу пользователям и уменьшить количество кликов совершаемых ими до минимума, это и может послужить ответом на вопрос "Почему нельзя просто посмотреть сравнение версий объектов ткнув туда, потом туда, затем туда и потом ещё два раза".

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

Итак начнём с самого простого —

Первая функция ПолучитьИзменившегоРеквизитОбъектаНаДату(СсылкаПоиска,Дата,ИмяРеквизита)

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

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

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

Казалось бы — задача выполнена и можно остановиться, но как быть с табличными частями? Ответом на этот вопрос и послжит

Вторая функция ПолучитьИзменившегоСтрокуТЧОбъектаНаДату

// функция определяет кто, когда и как последний изменял реквизит строки табличной части переданной ссылки до указанной даты//
// Параметры:
//  СсылкаПоиска  - ссылка для которой необходимо определить 
//  Дата  - Дата по которую проверяются версии объектов
//  ИмяТЧ  - имя табличной части проверяется изменение
//  ИмяРеквизитаТЧ - имя колонки значение которой проверяется
//  СтруктураОтбораСтрокиТЧ - структура с параметрами ИменаКолонок - имя колонки по которой будет поиск, Значение - искомое значение. 
//                            К табличной части по этим данным применится функция Найти(<Значение>, <Колонки>) для определения строки.
// Возвращаемое значение:
//   Структура   - Объект,ДатаВерсии,АвторВерсии,ЗначениеДо,ЗначениеПосле,ИндексСтрокиТЧ
//
Функция ПолучитьИзменившегоСтрокуТЧОбъектаНаДату(СсылкаПоиска,Дата,ИмяТЧ,ИмяРеквизитаТЧ,СтруктураОтбораСтрокиТЧ)экспорт 	
	Если 	СсылкаПоиска.ПолучитьОбъект().Метаданные().ТабличныеЧасти.Найти(ИмяТЧ) 	= Неопределено
			или СсылкаПоиска.ПолучитьОбъект().Метаданные().ТабличныеЧасти[ИмяТЧ].реквизиты.найти(ИмяРеквизитаТЧ)  = неопределено
			или	СтруктураОтбораСтрокиТЧ.Свойство("ИменаКолонок") 				= ложь 
			или СтруктураОтбораСтрокиТЧ.Свойство("Значение") 					= ложь Тогда
			
		Возврат новый Структура("Объект,ДатаВерсии,АвторВерсии,ЗначениеДо,ЗначениеПосле,ИндексСтрокиТЧ");
		
	КонецЕсли; 
	Запр = новый Запрос("ВЫБРАТЬ
	                  
	                    |	ВерсииОбъектов.ТабличныеДокументы КАК ТабличныеДокументы,
	                    |	ВерсииОбъектов.НомерВерсии КАК Версия,
	                    |	ВерсииОбъектов.АвторВерсии КАК АвторВерсии,
	                    |	ВерсииОбъектов.ДатаВерсии КАК ДатаВерсии
	                    |ИЗ
	                    |	РегистрСведений.ВерсииОбъектов КАК ВерсииОбъектов
	                    |ГДЕ
	                    |	ВерсииОбъектов.ДатаВерсии <= &ДатаВерсии
	                    |	И ВерсииОбъектов.Объект = &Объект
	                    |
	                    |УПОРЯДОЧИТЬ ПО
	                    |	Версия");
	Запр.УстановитьПараметр("Объект",СсылкаПоиска);
	Запр.УстановитьПараметр("ДатаВерсии",Дата);
	ВыполненныйЗапрос = Запр.Выполнить();
	Если ВыполненныйЗапрос.Пустой() Тогда
	
		Возврат новый Структура("Объект,ДатаВерсии,АвторВерсии,ЗначениеДо,ЗначениеПосле,ИндексСтрокиТЧ");	
	
	КонецЕсли; 
	Результат = ВыполненныйЗапрос.Выгрузить();
	
	ПоследняяВерсия=неопределено;
	
	Для каждого РезультатСтрока Из Результат Цикл
		
		ДвоичныеДанные = ВерсионированиеОбъектов.СведенияОВерсииОбъекта(СсылкаПоиска, РезультатСтрока.Версия).ВерсияОбъекта;
		
		Если ДвоичныеДанные = неопределено Тогда
		
			продолжить;
		
		КонецЕсли; 
		
		Объект = ВосстановитьОбъектПоXML(ДвоичныеДанные,"");
		Попытка
		
			СтрокаТЧ = Объект[ИмяТЧ].найти(СтруктураОтбораСтрокиТЧ.Значение,СтруктураОтбораСтрокиТЧ.ИменаКолонок);	
		
		Исключение
		    Возврат новый Структура("Объект,ДатаВерсии,АвторВерсии,ЗначениеДо,ЗначениеПосле,ИндексСтрокиТЧ");
		КонецПопытки; 
		Если СтрокаТЧ = неопределено Тогда
		
			продолжить;
		
		КонецЕсли; 
		ЗначениеСтроки = СтрокаТЧ[ИмяРеквизитаТЧ];
		
		Если ПоследняяВерсия	=	неопределено Тогда
			
		    ПоследняяВерсия = новый Структура("Объект,ДатаВерсии,АвторВерсии,ЗначениеДо,ЗначениеПосле,ИндексСтрокиТЧ");
			ЗаполнитьЗначенияСвойств(ПоследняяВерсия,РезультатСтрока);
			ПоследняяВерсия.Объект=Объект;	
			ПоследняяВерсия.ЗначениеДо = "";
			ПоследняяВерсия.ЗначениеПосле = ЗначениеСтроки;
			ПоследняяВерсия.ИндексСтрокиТЧ = СтрокаТЧ.НомерСтроки;
			продолжить;
			
		КонецЕсли; 
		Если ЗначениеСтроки <> ПоследняяВерсия.ЗначениеПосле Тогда
			
			ЗаполнитьЗначенияСвойств(ПоследняяВерсия,РезультатСтрока);
			ПоследняяВерсия.Объект = Объект;
			ПоследняяВерсия.ЗначениеДо = ПоследняяВерсия.ЗначениеПосле;
			ПоследняяВерсия.ЗначениеПосле = ЗначениеСтроки;
			ПоследняяВерсия.ИндексСтрокиТЧ = СтрокаТЧ.НомерСтроки;
			
		КонецЕсли; 
	КонецЦикла;
	Если ПоследняяВерсия	=	неопределено Тогда
			
		    Возврат новый Структура("Объект,ДатаВерсии,АвторВерсии,ЗначениеДо,ЗначениеПосле,ИндексСтрокиТЧ");	
			
	КонецЕсли;
	Возврат ПоследняяВерсия;
КонецФункции // ПолучитьИзменившегоРеквизитОбъектаНаДату()

Тут может возникнуть ряд вопросов на которые я постараюсь ответить сразу:

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

Теперь нам понадобится функция которая вернет одно конкретное значение, до текущего момента мы получали обратно структуры данных, а её использовать в обращении через СКД не очень то и удобно. Для этого есть 

Третья функция ПолучитьУказанныеДанныеПоИзменениямРеквизитаИзИсторииОбъекта

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

Данная функция добавляет универсальности всему механизму, так-как позволяет обращаться в одну точку и в зависимости от набора параметров возвращает конкретное значение, это будет удобно для использования в СКД. 

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

Функция ВосстановитьОбъектПоXML(ДанныеОбъекта, ТекстСообщенияОбОшибке = "")экспорт
	
	УстановитьПривилегированныйРежим(Истина);
	
	ДвоичныеДанные = ДанныеОбъекта;
	Если ТипЗнч(ДанныеОбъекта) = Тип("Структура") Тогда
		ДвоичныеДанные = ДанныеОбъекта.Объект;
	КонецЕсли;
	
	ЧтениеFastInfoSet = Новый ЧтениеFastInfoSet;
	ЧтениеFastInfoSet.УстановитьДвоичныеДанные(ДвоичныеДанные);
	
	Попытка
		Объект = ПрочитатьXML(ЧтениеFastInfoSet);
	Исключение
		ЗаписьЖурналаРегистрации(НСтр("ru = 'Версионирование'", ОбщегоНазначенияКлиентСервер.КодОсновногоЯзыка()),
			УровеньЖурналаРегистрации.Ошибка,,, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
		ТекстСообщенияОбОшибке = НСтр("ru = 'Не удалось перейти на выбранную версию.
											|Возможная причина: версия объекта была записана в другой версии программы.
											|Техническая информация об ошибке: %1'");
		ТекстСообщенияОбОшибке = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(ТекстСообщенияОбОшибке, КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
		Возврат Неопределено;
	КонецПопытки;
	
	Возврат Объект;
	
КонецФункции

Как разработчик этого всего заявляю, что метод довольно варварский, и работает "со скрипом", это связанно с тем, что у 1с всё хранится в хранилище данных в виде двоичного кода и метод получить всё это — развернуть через XML что в цикле даст огромный труд, а если сложить это всё с тем, что у нас будут корячиться запросы в цикле — то можно смело ставить два на экзамене, но в защиту хотелось бы сказать, что это расширяет функционал СКД и позволит получать сведения о интересующих нас негодяях пользователях в отчетах и "давать по рукам" в режиме online и оптом.

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

Разработка и тестирование велись на 1С:Предприятие 8.3 (8.3.13.1644) в конфигурации 1С:ERP Управление предприятием 2 (2.4.5.143)

Для функционирования необходимо подключенная к конфигурации БСП Версионирования объетков и включенная в использовании.

has been added to your cart:
Оформление заказа