Представляю написанную мной процедуру, которая, будучи своевременно встроена в программу 1С Предприятие 8.2, позволит отследить внесение изменений (запись/удаление) в любом регистре сведений (или в нескольких регистрах одновременно).
Процедура встраивается в конфигурацию путём добавления её в общий модуль, далее нужно создать подписку на событие ПередЗаписью требуемых регистров сведений и назначить эту процедуру обработчиком этого события.
Результатом работы процедуры являются записи в журнале регистрации, в которых кратко записывается суть производимых с регистром сведений изменений (одна-две строки в журнале регистрации на каждую транзакцию в регистре). При написании процедуры я руководствовался принципом : результат работы процедуры в журнале регистрации должен быть хорошо читаем и быстро осознаваем человеком.
Особенности реализации : если в наборе записей регистра сведений больше одной записи, то подробно протоколируется только первая запись набора и количество записей в наборе.
Программный код написан в стиле этюда : в тесте процедуры закомментирован код, который позволит ещё более усовершенствовать работу этой процедуры (в частности : получить информацию, которая будет вытеснена из регистра после записи набора). Мне это усовершенствование на практике пока не потребовалось, поэтому данная ветка на момент написания этой статьи не получила развитие.
О практической пользе этой процедуры. Эта процедура, будучи своевременно встроена в базу 1С Зарплата и управление персоналом 8.2, реально помогла мне отследить порчу конкретным пользователем штатного расписания в базе 1С Зарплата и Управление персоналом 8.2 и переклассифицировать инцидент «порча данных штатного расписания» из разряда «программный сбой» в «ошибку пользователя».
Процедура ЖурнализированиеРегистраСведенийПередЗаписью(Источник, Отказ, Замещение) Экспорт
НаборЗаписей = Источник;
ТаблицаДляПечати1 = НаборЗаписей.Выгрузить();
ОписаниеИзменений1 = «»;
СсылкаДляОтраженияВЖурналеРегистрации1 = Неопределено;
Если ТаблицаДляПечати1.Количество() = 0 Тогда
// в регистр пишется пустой набор записей, т.е. происходит удаление из регистра сведений по наложенному в наборе отбору
ЧтоПроисходит1 = «Удаление в регистре сведений»;
МетаданныеНабора = НаборЗаписей.Метаданные();
ИмяРегистра = МетаданныеНабора.ПолноеИмя();
// Формируем текст условия блока ГДЕ основного запроса,
// в соответствии с установленным отбором для набора записей
Запрос = Новый Запрос;
СписокПолейУсловияОтбораТекст = «»;
Итерация = 0;
Для каждого ЭлементОтбора Из НаборЗаписей.Отбор Цикл
Если не ЭлементОтбора.Использование Тогда
Продолжить;
КонецЕсли;
Если Итерация = 0 Тогда
ОписаниеИзменений1 = ОписаниеИзменений1 + «Отбор : «;
Иначе
СписокПолейУсловияОтбораТекст = СписокПолейУсловияОтбораТекст + » И «;
КонецЕсли;
СписокПолейУсловияОтбораТекст = СписокПолейУсловияОтбораТекст +» Набор.» + ЭлементОтбора.Имя + » = &» + ЭлементОтбора.Имя;
// здесь нужно сохранить в журнале регистрации значения отборов
ОписаниеИзменений1 = ОписаниеИзменений1 + «» + ЭлементОтбора.Имя + » = » + СокрЛП(Формат(ЭлементОтбора.Значение, «ДФ=dd.MM.yyyy»)) + «» + Символы.ПС;
Если СсылкаДляОтраженияВЖурналеРегистрации1 = Неопределено Тогда
Если ТипЗнч(ЭлементОтбора.Значение) <> Тип(«Дата») Тогда // дата в качестве представления объекта не нужна! лучше любой ссылочный тип
СсылкаДляОтраженияВЖурналеРегистрации1 = ЭлементОтбора.Значение;
КонецЕсли;
КонецЕсли;
Запрос.УстановитьПараметр(ЭлементОтбора.Имя, ЭлементОтбора.Значение);
Итерация = 1;
КонецЦикла;
Если Итерация = 1 Тогда
СписокПолейУсловияОтбораТекст = » ГДЕ » + СписокПолейУсловияОтбораТекст;
КонецЕсли;
Запрос.Текст = «ВЫБРАТЬ * ИЗ » + ИмяРегистра + » КАК Набор
| «+ СписокПолейУсловияОтбораТекст + «»;
// ТаблицаДляПечати1 = Запрос.Выполнить().Выгрузить();
// а тут лежат старые данные в регистре (которые перетираются!!!)
// их можно анализировать и печатать
Иначе // записывается непустой набор записей
ЧтоПроисходит1 = «Запись в регистр сведений»;
Для каждого Запись1 Из ТаблицаДляПечати1 Цикл
Для каждого Колонка1 Из ТаблицаДляПечати1.Колонки Цикл
ЗначениеКолонки1 = Запись1[Колонка1.Имя];
Если ЗначениеЗаполнено(ЗначениеКолонки1) Тогда // печатаем почти все значения непустых измерений, ресурсов, реквизитов!
ТипЗнач1 = ТипЗнч(ЗначениеКолонки1);
МожноПечататьПолноеЗначениеКолонки1 = Истина;
Если ТипЗнач1 = Тип(«Число») Тогда
Если ЗначениеКолонки1 > 1000 Тогда
// защита данных в журнале регистрации : не печатаем числовые значения колонок, если они превышают 1000 !
// например, это могут быть вилки окладов!
МожноПечататьПолноеЗначениеКолонки1 = Ложь;
КонецЕсли;
КонецЕсли;
Если МожноПечататьПолноеЗначениеКолонки1 Тогда
ОписаниеИзменений1 = ОписаниеИзменений1 + Колонка1.Имя + » = » + Формат(ЗначениеКолонки1, «ДФ=dd.MM.yyyy») + Символы.ПС;
Если СсылкаДляОтраженияВЖурналеРегистрации1 = Неопределено Тогда
Если ТипЗнач1 <> Тип(«Дата») Тогда
СсылкаДляОтраженияВЖурналеРегистрации1 = ЗначениеКолонки1;
КонецЕсли;
КонецЕсли;
Иначе
ОписаниеИзменений1 = ОписаниеИзменений1 + Колонка1.Имя + » > 1000″ + Символы.ПС;
КонецЕсли;
КонецЕсли;
КонецЦикла;
Прервать; // прочтём только первую запись
КонецЦикла;
Если ТаблицаДляПечати1.Количество() > 1 Тогда
// по остальным записям — укажем только полное количество записей
ОписаниеИзменений1 = «Всего записей набора = » + ТаблицаДляПечати1.Количество() + «; первая запись :» + Символы.ПС + ОписаниеИзменений1;
КонецЕсли;
КонецЕсли;
ЗаписьЖурналаРегистрации(ЧтоПроисходит1, УровеньЖурналаРегистрации.Предупреждение, МетаданныеНабора, СсылкаДляОтраженияВЖурналеРегистрации1, СокрЛП(ОписаниеИзменений1));
КонецПроцедуры
Какое значение передавать в параметре «Источник»?
Сама 1С передаст туда набор записей регистра сведений. Руками ничего передавать не нужно.
Было бы не плохо если бы он еще показывал запись до изменения! 🙂