Версионирование регистров сведений

17.09.19

Разработка - Механизмы платформы 1С

Моя реализация механизма мониторинга изменений регистра сведений.

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

Для начала был создан независимый периодический регистр сведений  История изменения объектов с измерением ИмяТаблицыМетаданных (строка 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].Значение;
	УстановитьОтборПоРегистру();	
КонецПроцедуры

&НаСервере
Процедура УстановитьОтборПоРегистру()
	Список.Параметры.УстановитьЗначениеПараметра("ИмяТаблицыМетаданных",Регистр);
КонецПроцедуры

Из недостатков можно отметить, что запись изменений делается до записи регистра и в случае, когда запись в отслеживаемый регистр по каким-либо причинам при записи не попадает, фиксация изменений имеет место, и второй основной недостаток это то, что в случае удаления ссылочного объекта из измерений/ресурсов/реквизитов отслеживаемого регистра в отчет будут выводиться битые ссылки.

мониторинг изменений регистра сведений

См. также

Динамическое обновление - это зло?

Механизмы платформы 1С Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Копнем глубже в тему "Что же такое динамическое обновление" и почему оно может привести к проблемам. И может ли?

09.05.2022    27413    Infostart    83    

243

Совместимость работы со строками. Жизнь до 8.3.6 и после

Механизмы платформы 1С Платформа 1С v8.3 Бесплатно (free)

Немного о совместимости со старыми версиям платформы 1С в работе со строками.

21.02.2020    8401    Infostart    25    

69

Эволюция расширения конфигурации

Механизмы платформы 1С Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

С каждым днем всё больше возможностей появляется в расширении конфигурации, но не все до сих пор работают даже на платформе 8.3.6! Давайте окунемся в историю появления и эволюции расширения конфигурации, чтобы знать и понимать, когда можно применить тот или иной функционал!

06.02.2020    26774    Xershi    51    

223

Как работают управляемые формы и тонкий клиент 1С – взгляд "из-под капота"

Механизмы платформы 1С Управляемые формы Бесплатно (free)

Переход на управляемые формы перевернул процесс разработки на 1С, заставив программистов менять привычные подходы к описанию логики работы интерфейса. Руководитель компании «Цифровой Кот» Юрий Лазаренко в своем докладе на конференции Infostart Event 2019 Inception рассказал о том, как устроены управляемые формы и как правильно работать с тонким клиентом платформы 1С:Предприятие.

23.12.2019    25143    TitanLuchs    23    

100

30 задач. Странных и не очень

Математика и алгоритмы Механизмы платформы 1С Платформа 1С v8.3 Бесплатно (free)

30 задач на знание языка программирования 1С и некоторого поведения платформы. Маленьких. Странных и не очень.

02.12.2019    50764    Infostart    65    

164

Фишечки-рюшечки

Механизмы платформы 1С Платформа 1С v8.3 Бесплатно (free)

За годы работы с 1С собрался определенный багаж хитростей, который позволяет разрабатывать быстрее/эффективнее/качественнее. Поделюсь ими в данной статье.

06.11.2019    11204    mpeg1989    95    

66

ЧтениеДанных и ЗаписьДанных. Работа со строками

Механизмы платформы 1С Платформа 1С v8.3 Бесплатно (free)

Использование потоков и двоичных данных для работы со строками.

04.10.2019    23851    Yashazz    16    

76
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. kosmo0 108 19.09.19 12:13 Сейчас в теме
Технологическая платформа
Версия 8.3.11
История данных.
Реализован механизм отслеживания истории изменения объектов базы данных. Реализовано свойство История данных для следующих объектов конфигурации: справочники, документы, бизнес-процессы, задачи, регистры сведений.
Реализована возможность использовать историю изменений на уровне платформы, за счет чего упрощается разработка и сопровождение прикладного решения.

Случайно не подходит для решения подобных задач?
+
2. KonS 75 20.09.19 12:57 Сейчас в теме
(1)для случаев когда конфигурация позволяет включить режим совместимости с указанным релизом платформы и выше подходит.
+
3. Cmapnep 18 22.09.19 20:40 Сейчас в теме
1. Что будет при удалении записи? В подписке старых данных уже нет и нет никакой возможности выяснить что было в той записи, которая была удалена. Фактически регистрируется просто факт того, что кто-то что-то удалил - это имеет смысл только в каких-то очень простых случаях
2. Что будет если в записи регистра сведений изменится значение в измерении, т.е. отборе? Фактически будет 2 записи - 1-ая об удалении чего-то, а 2-ая о появлении новой записи
Также общее замечание - подход автора к динамическому формированию текста запроса весьма затрудняет понимание того, что хотел автор
+
4. KonS 75 23.09.19 21:33 Сейчас в теме
(3)1 процедура перед записью - ей передается новый набор, запросом из базы выбирается существующий набор, сравниваются и, в случае если по отбору набора записей новый набор пустой фиксируется удаление набора записей с указанием значений удаляемых записей. Для обозначенных в задаче целей этого более чем достаточно.
2 как и работает механизм регистра в принципе - по одному набору измерений удаляется, по другому добавляется.
Статическое указание запросов несколько ускоряет работу, значительно увеличивает объем кода и сужает сферу применения.
+
5. Cmapnep 18 23.09.19 22:03 Сейчас в теме
(4) Я указал конкретные проблемы, а в ответ получил описание принципа работы...
Вероятно вы просто не поняли моих замечаний - попробую разжевать:
1.
процедура перед записью - ей передается новый набор, запросом из базы выбирается существующий набор
- "существующий набор" получается по отборам нового набора, а если новый набор пустой, что и имеет место при удалении, то просто констатируется факт удаления, но не понятно что было удалено. Это и есть проблема №1. Надеюсь так понятней...
2. Вторая проблема вот в чем - была запись, например, такая Изм1=А, Изм2=Б, Рес1=1, которую некий злоумышленник отредактировал, заменив в ней значение Изм2 так, что запись приобрела вид Изм1=А, Изм2=ББ, Рес1=1. При записи в подписке приведенный код зарегистрирует 2 факта: 1 - об удалении записи, причем что за запись удалилась будет неизвестно (см. описание п.1) и 2 - о появлении новой записи Изм1=А, Изм2=ББ, Рес1=1. Вы так и задумывали?

Прочтите внимательно "подход автора к динамическому формированию текста запроса". Речь идет о том КАК формируется текст запроса. Каждому, как говорится, свое, но могу только предложить изучить подход к решению аналогичных задач, принятый в типовых конфигурациях.
+
6. KonS 75 24.09.19 14:35 Сейчас в теме
вы вероятно невнимательно читаете. давайте ещё раз
1 перед записью выбирается набор из по переданным измерениям, сравнивается с записываемым набором и в лог сохраняются изменения, т е записи не совпадающие по значениям ресурсов и реквизитов. значения измерений берутся либо из считанного набора либо из нового, таким образом фиксируется что было добавлено/изменено/удалено
2 Я вам так и написал - по одному набору измерений фиксируется удаление, по другому добавление. по аналогии как работает механизм менеджера записи.

Динамическое формирование запроса как в типовых решениях мне не показалось тут удобным и, в силу универсальности, не будет легкочитаемым в принципе.
+
7. Cmapnep 18 26.09.19 10:45 Сейчас в теме
(6) Действительно, я был невнимателен - алгоритм выполняется не при записи, а перед и там отборы заполнены старыми значениями.
Достаточно ортодоксальный подход, но вероятно имеет право на жизнь. Приношу извинения

Есть еще пара мелких проблем:
- В начале обработчика подписки нужно добавить проверку на тип источника, чтобы исключить тип "РегистрСведенийНаборЗаписей.РК_ИсторияИзмененияРегистров"
- В запросе нужно исключить реквизиты типа "ХранилищеЗначения", т.к. они вызывают ошибку в операциях сравнения

И есть вопрос - почему история сохраняется только по одной записи в РС - той, что изменялась последней? В чем тут смысл?
+
8. KonS 75 26.09.19 11:46 Сейчас в теме
- Предполагается что конкретные выбранные регистры будут прописаны в подписке (что прописано в сопроводительном тексте) и проверка несколько бессмысленна. будет иметь смысл если заморачиваться с пользовательским интерфейсом настройки для выбора списка отслеживаемых регистров.
- тут ваша правда. замечание так же справедливо и для ресурсов и реквизитов типа строка неограниченной длины

Тут вы наверно тоже пропустили, что РС История изменения регистров периодический, соответственно для сочетания пользователь+РС сохранится одно значение в один момент времени.
+
9. KonS 75 26.09.19 16:00 Сейчас в теме
(8)upd одно изменение РС в один момент времени конечно же. пользователь это ресурс
+
10. Simonov_NPM 03.08.20 10:28 Сейчас в теме
Добавил в РС РК_ИсторияИзмененияРегистров еще одно измерение со значением УникальныйИдентификатор, пишу для каждой записи новый в него. Это нужно когда например перезаписываешь данные, иначе когда в одну секунду идет удаление и запись, эти операции записываются как одна последняя
+
Оставьте свое сообщение