Возникла ситуация, когда с некоторой периодичностью стали пропадать записи из регистра границы запрета изменения информации. Расследование не дало результатов, гугл решения подходящего не подсказал, поэтому пришлось делать свой механизм регистрации изменений.
Для начала был создан независимый периодический регистр сведений История изменения объектов с измерением ИмяТаблицыМетаданных (строка 200) и ресурсами Изменения (Хранилище значения), Ответственный (Пользователь) и подписка события перед записью регистра сведений, изменения которого хотим отследить.
Листинг процедуры регистрации изменений:
Процедура ИсторияИзмененияРегистраСведений_ПередЗаписью(Источник, Отказ, Замещение) Экспорт
МД = Источник.Метаданные();
Текст = "";
ТекстЗапроса = "ВЫБРАТЬ" + ?(МД.ПериодичностьРегистраСведений=Метаданные.СвойстваОбъектов.ПериодичностьРегистраСведений.Непериодический,"",Символы.ПС + "Таб.Период,") + ?(ПолучитьТекстПоРеквизитам(МД,"Измерения",Текст),Текст,"") + ?(ПолучитьТекстПоРеквизитам(МД,"Ресурсы",Текст),","+Текст,"") + ?(ПолучитьТекстПоРеквизитам(МД,"Реквизиты",Текст),"," + Текст,"");
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("Таб",Источник.Выгрузить());
СтрокаУсловий = "ИСТИНА";
СтрокаСоединений = "ИСТИНА";
Для Каждого ЭлементОтбора ИЗ Источник.Отбор Цикл
Если ЭлементОтбора.Использование Тогда
СтрокаУсловий = СтрокаУсловий + "
| И Таб." + ЭлементОтбора.Имя + " = &" + ЭлементОтбора.Имя;
Запрос.УстановитьПараметр(ЭлементОтбора.Имя,ЭлементОтбора.Значение);
КонецЕсли;
СтрокаСоединений = СтрокаСоединений + "
| И втТекущийНабор." + ЭлементОтбора.Имя + " = втНовыйНабор." + ЭлементОтбора.Имя;
КонецЦикла;
Запрос.Текст = ТекстЗапроса + "
| ПОМЕСТИТЬ втНовыйНабор
|ИЗ
| &Таб КАК Таб;
|"+ТекстЗапроса + "
| ПОМЕСТИТЬ втТекущийНабор
|ИЗ
| РегистрСведений." + МД.Имя + " КАК Таб
|ГДЕ " + СтрокаУсловий + ";
|ВЫБРАТЬ " + ?(МД.ПериодичностьРегистраСведений=Метаданные.СвойстваОбъектов.ПериодичностьРегистраСведений.Непериодический,"",Символы.ПС + "ISNULL(втТекущийНабор.Период,втНовыйНабор.Период) КАК Период,") + "
| " + ?(ПолучитьТекстПоРеквизитам(МД,"Измерения",Текст,"ISNULL(втТекущийНабор.%1,втНовыйНабор.%1) КАК %1"),Текст,"") + ?(ПолучитьТекстПоРеквизитам(МД,"Ресурсы",Текст,"втТекущийНабор.%1 КАК %1"),","+Текст,"") + ?(ПолучитьТекстПоРеквизитам(МД,"Реквизиты",Текст,"втТекущийНабор.%1 КАК %1"),"," + Текст,"")+?(ПолучитьТекстПоРеквизитам(МД,"Ресурсы",Текст,"втНовыйНабор.%1 КАК НовоеЗначение%1"),","+Текст,"") + ?(ПолучитьТекстПоРеквизитам(МД,"Реквизиты",Текст,"втНовыйНабор.%1 КАК НовоеЗначение%1"),"," + Текст,"")+",
| ВЫБОР КОГДА втТекущийНабор." + МД.Измерения[0].Имя + " IS NULL ТОГДА ""Добавление""
| КОГДА втНовыйНабор." + МД.Измерения[0].Имя + " IS NULL ТОГДА ""Удаление""
| ИНАЧЕ ""Изменение"" КОНЕЦ КАК Действие
|ИЗ
| втТекущийНабор
| ПОЛНОЕ СОЕДИНЕНИЕ втНовыйНабор ПО " + СтрокаСоединений + "
|ГДЕ
| ИСТИНА " + ?(ПолучитьТекстПоРеквизитам(МД,"Ресурсы",Текст," И НЕ ISNULL(втТекущийНабор.%1,Неопределено) = ISNULL(втНовыйНабор.%1,Неопределено)", " "),Текст,"") + ?(ПолучитьТекстПоРеквизитам(МД,"Реквизиты",Текст," И НЕ ISNULL(втТекущийНабор.%1,Неопределено) = ISNULL(втНовыйНабор.%1,Неопределено)"," "),Текст,"");
РезультатЗапроса = Запрос.Выполнить();
Если НЕ РезультатЗапроса.Пустой() Тогда
ОбщиеПроцедурыПривилегированный.ЗарегистрироватьИзменениеРегистра(ПараметрыСеанса.ТекущийПользователь,МД.Имя,ТекущаяДата(),ПоместитьВоВременноеХранилище(Новый ХранилищеЗначения(РезультатЗапроса.Выгрузить())));
КонецЕсли;
КонецПроцедуры
Функция ПолучитьТекстПоРеквизитам(МД,Коллекция,Текст,ШаблонПараметра = "Таб.%1",Запятая = ",")
Текст = "";
Для Каждого Элемент ИЗ МД[Коллекция] Цикл;
Текст = Текст + "
| " + СтрЗаменить(ШаблонПараметра,"%1",Элемент.Имя) + Запятая;
КонецЦикла;
Текст = Лев(Текст,СтрДлина(Текст)-1);
Возврат НЕ ПустаяСтрока(Текст);
КонецФункции
и процедура в общем привилегированном модуле, чтобы не давать простым смертным доступа к регистру изменений
Процедура ЗарегистрироватьИзменениеРегистра(Пользователь,ИмяТаблицы,Дата,АдресИзменений)Экспорт
МЗ = РегистрыСведений.РК_ИсторияИзмененияРегистров.СоздатьМенеджерЗаписи();
МЗ.Активность = Истина;
МЗ.ИмяТаблицыМетаданных = ИмяТаблицы;
МЗ.Изменения = ПолучитьИзВременногоХранилища(АдресИзменений);
МЗ.Ответственный = Пользователь;
МЗ.Период = Дата;
МЗ.Записать(Истина);
КонецПроцедуры
Для вывода в формате отчета на форме списка регистра поместил табличное поле, заполняемое по требованию данными текущих изменений
Функция СформироватьОтчетПоСтроке(КлючЗаписи)
Изменения.Очистить();
Запись = РегистрыСведений.РК_ИсторияИзмененияРегистров.СоздатьМенеджерЗаписи();
ЗаполнитьЗначенияСвойств(Запись,КлючЗаписи);
Запись.Прочитать();
ТаблицаИзменений = Запись.Изменения.Получить();
СтруктураВнешнихДанных = Новый Структура("ТаблицаИзменений",ТаблицаИзменений);
СКД = Новый СхемаКомпоновкиДанных();
Источник = СКД.ИсточникиДанных.Добавить();
Источник.Имя = "ИсточникДанных";
Источник.СтрокаСоединения = "";
Источник.ТипИсточникаДанных = "Local";
НаборДанных = СКД.НаборыДанных.Добавить(Тип("НаборДанныхОбъектСхемыКомпоновкиДанных"));
НаборДанных.Имя = "ТаблицаИзменений";
НаборДанных.ИсточникДанных = "ИсточникДанных";
НаборДанных.ИмяОбъекта = "ТаблицаИзменений";
Для Каждого тКолонка ИЗ ТаблицаИзменений.Колонки Цикл
ДобавленноеПоле = НаборДанных.Поля.Добавить(Тип("ПолеНабораДанныхСхемыКомпоновкиДанных"));
ДобавленноеПоле.ПутьКДанным = тКолонка.Имя;
ДобавленноеПоле.Поле = тКолонка.Имя;
ДобавленноеПоле.ТипЗначения = тКолонка.ТипЗначения;
ВыбранноеПоле = СКД.ВариантыНастроек[0].Настройки.Выбор.Элементы.Добавить(Тип("ВыбранноеПолеКомпоновкиДанных"));
ВыбранноеПоле.Использование = Истина;
ВыбранноеПоле.Поле = Новый ПолеКомпоновкиДанных(тКолонка.Имя);
ВыбранноеПоле.Заголовок = тКолонка.Заголовок;
КонецЦикла;
ЭлементСтруктуры = СКД.ВариантыНастроек[0].Настройки.Структура.Добавить(Тип("ГруппировкаКомпоновкиДанных"));
ЭлементСтруктуры.Использование = Истина;
ЭлементСтруктуры.ПоляГруппировки.Элементы.Добавить(Тип("АвтоПолеГруппировкиКомпоновкиДанных")).Использование = Истина;
ЭлементСтруктуры.Выбор.Элементы.Добавить(Тип("АвтоВыбранноеПолеКомпоновкиДанных")).Использование = Истина;
Настройки = СКД.НастройкиПоУмолчанию;
ДанныеРасшифровкиСКД = Новый ДанныеРасшифровкиКомпоновкиДанных;
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
ПроцессорКомпоновкиДанных = Новый ПроцессорКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СКД,Настройки, ДанныеРасшифровкиСКД);
ПроцессорКомпоновкиДанных.Инициализировать(МакетКомпоновки,СтруктураВнешнихДанных ,ДанныеРасшифровкиСКД);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(Изменения);
ПроцессорВывода.Вывести(ПроцессорКомпоновкиДанных);
КонецФункции
ну и для красоты фильтр по регистрам, для которых есть отображение в истории
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
Запрос = Новый Запрос;
Запрос.Текст = "Выбрать различные ИмяТаблицыМетаданных ИЗ РегистрСведений.ИсторияИзмененияРегистров";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Элементы.Регистр.СписокВыбора.Добавить(Выборка.ИмяТаблицыМетаданных,Метаданные.РегистрыСведений[Выборка.ИмяТаблицыМетаданных].Синоним);
КонецЦикла;
Регистр = Элементы.Регистр.СписокВыбора[0].Значение;
УстановитьОтборПоРегистру();
КонецПроцедуры
&НаСервере
Процедура УстановитьОтборПоРегистру()
Список.Параметры.УстановитьЗначениеПараметра("ИмяТаблицыМетаданных",Регистр);
КонецПроцедуры
Из недостатков можно отметить, что запись изменений делается до записи регистра и в случае, когда запись в отслеживаемый регистр по каким-либо причинам при записи не попадает, фиксация изменений имеет место, и второй основной недостаток это то, что в случае удаления ссылочного объекта из измерений/ресурсов/реквизитов отслеживаемого регистра в отчет будут выводиться битые ссылки.