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