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