Как реализовать детальное логирование конкретных изменений справочника в Журнале регистрации?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Стандартный механизм версионирования объектов в 1С (через БСП или платформенную "Историю данных") отлично справляется с хранением истории изменений. Однако часто перед разработчиком ставится задача: сделать так, чтобы конкретные изменения (какой реквизит был изменен, какое значение было «до» и какое стало «после») отображались непосредственно в Журнале регистрации (ЖР). Это удобно для оперативного аудита без открытия специализированных отчетов.

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

Постановка задачи и выбор стратегии

Основная сложность заключается в том, что стандартная запись Журнала регистрации при событии "Данные.Изменение" фиксирует только сам факт записи объекта. Чтобы понять, что именно изменилось, нам необходимо:

  1. Получить состояние объекта до момента записи.
  2. Сравнить его с текущим (измененным) состоянием.
  3. Сформировать текстовое описание или структуру с отличиями.
  4. Записать эти данные в ЖР только в том случае, если транзакция записи завершилась успешно.

Рассмотрим по шагам, как это реализовать наиболее эффективно.

Шаг 1. Получение данных «До» и «После»

Для того чтобы вычислить разницу, нам нужно два набора данных. Текущие данные находятся в свойствах объекта (ЭтотОбъект). Старые данные необходимо прочитать из базы данных, используя ссылку на этот же объект. Важно помнить, что если объект новый (ЭтоНовый() = Истина), то сравнивать не с чем — мы просто фиксируем создание.

Проанализируем оптимальное место для кода сравнения. Лучше всего использовать обработчик ПередЗаписью в модуле объекта или в Подписке на события. Почему не ПриЗаписи? Потому что в момент ПриЗаписи данные в базе уже могут быть обновлены (в зависимости от режима), и получить «старую» версию через Ссылка.ПолучитьОбъект() может быть затруднительно.

Шаг 2. Универсальный алгоритм сравнения реквизитов

Вместо того чтобы вручную прописывать сравнение каждого поля (Наименование, ИНН, Склад), мы воспользуемся коллекцией метаданных. Это сделает наш код универсальным для любого справочника.

Рассмотрим пример кода, который можно разместить в общем модуле для вызова из подписки на событие ПередЗаписью:


Процедура ОпределитьРазницуРеквизитов(Источник, Отказ) Экспорт

    Если Источник.ОбменДанными.Загрузка Или Источник.ЭтоНовый() Тогда
        Возврат;
    КонецЕсли;

    // Читаем состояние объекта из базы данных напрямую
    СтарыйОбъект = Источник.Ссылка.ПолучитьОбъект();
    СписокИзменений = Новый СписокЗначений;

    // Обходим основные реквизиты через метаданные
    Для Каждого Реквизит Из Источник.Метаданные().Реквизиты Цикл
        ИмяРеквизита = Реквизит.Имя;
        
        ЗначениеНовое = Источник[ИмяРеквизита];
        ЗначениеСтарое = СтарыйОбъект[ИмяРеквизита];

        Если ЗначениеНовое <> ЗначениеСтарое Тогда
            ТекстИзменения = СтрШаблон("Изменен реквизит '%1': Было '%2', Стало '%3'", 
                Реквизит.Синоним, ЗначениеСтарое, ЗначениеНовое);
            СписокИзменений.Добавить(ТекстИзменения);
        КонецЕсли;
    КонецЦикла;

    // Сохраняем результат для последующей записи в ЖР
    Если СписокИзменений.Количество() > 0 Тогда
        Источник.ДополнительныеСвойства.Вставить("СписокИзмененийДляЖР", СписокИзменений);
    КонецЕсли;

КонецПроцедуры

В этом примере мы используем ДополнительныеСвойства объекта. Это критически важный момент: это хранилище «живет» в течение всей транзакции. Мы записываем туда изменения ПередЗаписью, но в сам Журнал регистрации отправим их только ПослеЗаписи.

Шаг 3. Запись в Журнал регистрации

Почему нельзя писать в ЖР сразу в ПередЗаписью? Если после выполнения этого кода система выдаст ошибку (например, сработает проверка заполнения или произойдет сбой СУБД), запись в ЖР останется, а данные в базе не изменятся. Это приведет к дезинформации пользователей.

Поэтому используем обработчик ПослеЗаписи:


Процедура ЗаписатьИзмененияВЖурнал(Источник, ПараметрыЗаписи) Экспорт

    Если Источник.ДополнительныеСвойства.Свойство("СписокИзмененийДляЖР") Тогда
        
        СписокИзменений = Источник.ДополнительныеСвойства.СписокИзмененийДляЖР;
        ТекстСообщения = "";
        
        Для Каждого ЭлементСписка Из СписокИзменений Цикл
            ТекстСообщения = ТекстСообщения + ЭлементСписка.Значение + Символы.ПС;
        КонецЦикла;

        ЗаписьЖурналаРегистрации(
            "Изменение реквизитов справочника",
            УровеньЖурналаРегистрации.Информация,
            Источник.Метаданные(),
            Источник.Ссылка,
            ТекстСообщения
        );
        
    КонецЕсли;

КонецПроцедуры

Сравнение табличных частей

Сравнение табличных частей (ТЧ) — более трудоемкий процесс. Простого цикла по метаданным недостаточно, так как строки могут добавляться, удаляться или меняться местами. Проанализируем один из подходов: использование временных таблиц.

Выясним причину сложности: в ТЧ нет уникальных идентификаторов строк, которые сохраняются между сессиями. Поэтому обычно используют следующий алгоритм:

  1. Выгружаем ТЧ текущего объекта и ТЧ объекта из базы в две таблицы значений.
  2. Сравниваем количество строк.
  3. Если количество совпадает, проверяем каждое поле в каждой строке по индексу.
  4. Если количество не совпадает или важен состав, можно использовать метод JSON-сериализации для сравнения всей ТЧ целиком, но это затруднит чтение лога человеком.

Использование сериализации JSON для лога

Для более продвинутых систем рекомендуется записывать изменения не в виде простого текста, а в формате JSON. Рассмотрим, чем это полезно. Если в будущем вам потребуется построить отчет «Кто и когда менял поле Цена», программно прочитать JSON из комментария ЖР гораздо проще, чем парсить обычный текст.


// Пример формирования структуры для JSON
ИзмененияСтруктура = Новый Структура;
ИзмененияСтруктура.Вставить("ИмяПоля", "Склад");
ИзмененияСтруктура.Вставить("СтароеЗначение", Строка(ЗначениеСтарое));
ИзмененияСтруктура.Вставить("НовоеЗначение", Строка(ЗначениеНовое));

ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, ИзмененияСтруктура);
РезультатСтрокой = ЗаписьJSON.Закрыть();

Риски и ограничения при работе с Журналом регистрации

Необходимо понимать, что Журнал регистрации не предназначен для хранения больших объемов данных. Проанализируем потенциальные проблемы:

Альтернатива: Встроенная "История данных" платформы

Если ваша конфигурация работает на платформе 8.3.11 и выше, рассмотрите возможность включения стандартного свойства метаданных История данных. Она работает на уровне платформы и хранит изменения в специальных таблицах БД, что гораздо производительнее Журнала регистрации. Получить разницу между версиями из Истории данных можно программно с помощью менеджера ИсторияДанных, и уже эти готовые данные (разницу) при необходимости транслировать в ЖР.

Подведем итог: использование связки ПередЗаписью (для анализа), ДополнительныеСвойства (для передачи данных) и ЗаписьЖурналаРегистрации в событии ПослеЗаписи является наиболее надежным способом реализации ТЗ по детальному логированию в Журнал регистрации.

← На главную