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

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)

Эта небольшая статья - некоторого рода шпаргалка по файловым потокам: как и зачем с ними работать, какие преимущества это дает.

23.06.2024    8151    bayselonarrend    20    

156

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

Пример использования «Сервисов интеграции» без подключения к Шине и без обменов.

13.03.2024    6328    dsdred    18    

80

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

Все мы используем массивы в своем коде. Это один из первых объектов, который дают ученикам при прохождении обучения программированию. Но умеем ли мы ими пользоваться? В этой статье я хочу показать все методы массива, а также некоторые фишки в работе с массивами.

24.01.2024    19269    YA_418728146    26    

72

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

Язык программирования 1С содержит много нюансов и особенностей, которые могут приводить к неожиданным для разработчика результатам. Сталкиваясь с ними, программист начинает лучше понимать логику платформы, а значит, быстрее выявлять ошибки и видеть потенциальные узкие места своего кода там, где позже можно было бы ещё долго медитировать с отладчиком в поисках источника проблемы. Мы рассмотрим разные примеры поведения кода 1С. Разберём результаты выполнения и ответим на вопросы «Почему?», «Как же так?» и «Зачем нам это знать?». 

06.10.2023    24213    SeiOkami    48    

135

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

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

28.08.2023    15253    YA_418728146    7    

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

Случайно не подходит для решения подобных задач?
2. KonS 77 20.09.19 12:57 Сейчас в теме
(1)для случаев когда конфигурация позволяет включить режим совместимости с указанным релизом платформы и выше подходит.
3. Cmapnep 19 22.09.19 20:40 Сейчас в теме
1. Что будет при удалении записи? В подписке старых данных уже нет и нет никакой возможности выяснить что было в той записи, которая была удалена. Фактически регистрируется просто факт того, что кто-то что-то удалил - это имеет смысл только в каких-то очень простых случаях
2. Что будет если в записи регистра сведений изменится значение в измерении, т.е. отборе? Фактически будет 2 записи - 1-ая об удалении чего-то, а 2-ая о появлении новой записи
Также общее замечание - подход автора к динамическому формированию текста запроса весьма затрудняет понимание того, что хотел автор
4. KonS 77 23.09.19 21:33 Сейчас в теме
(3)1 процедура перед записью - ей передается новый набор, запросом из базы выбирается существующий набор, сравниваются и, в случае если по отбору набора записей новый набор пустой фиксируется удаление набора записей с указанием значений удаляемых записей. Для обозначенных в задаче целей этого более чем достаточно.
2 как и работает механизм регистра в принципе - по одному набору измерений удаляется, по другому добавляется.
Статическое указание запросов несколько ускоряет работу, значительно увеличивает объем кода и сужает сферу применения.
5. Cmapnep 19 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 77 24.09.19 14:35 Сейчас в теме
вы вероятно невнимательно читаете. давайте ещё раз
1 перед записью выбирается набор из по переданным измерениям, сравнивается с записываемым набором и в лог сохраняются изменения, т е записи не совпадающие по значениям ресурсов и реквизитов. значения измерений берутся либо из считанного набора либо из нового, таким образом фиксируется что было добавлено/изменено/удалено
2 Я вам так и написал - по одному набору измерений фиксируется удаление, по другому добавление. по аналогии как работает механизм менеджера записи.

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

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

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

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