Как в 1С 8.3 создать журнал регистрации изменений документа?

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

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

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

---

Вариант 1. Встроенный платформенный механизм «История данных»

Начиная с версии платформы 1С 8.3.11, разработчикам доступен мощный встроенный механизм — История данных. Это наиболее современный и производительный способ версионирования, работающий на уровне СУБД из коробки.

Рассмотрим ключевые преимущества этого механизма:

  1. Асинхронность работы. Запись версии происходит не в момент транзакции проведения документа, а асинхронно. Платформа регистрирует лишь сам факт изменения, а регламентное задание обрабатывает очередь изменений в фоновом режиме. Это исключает просадки производительности и снижает риски взаимных блокировок.
  2. Настройка до реквизита. Вы можете указать, какие именно реквизиты или табличные части нужно версионировать, а какие (например, служебные) можно игнорировать для экономии места.
  3. Поддержка расширений. Механизм можно легко включить даже для объектов, заимствованных из расширений конфигурации.

Проанализируем процесс включения механизма «История данных» по шагам:

  1. Откройте конфигуратор и найдите нужный документ.
  2. Перейдите на вкладку «Основные» в окне свойств документа.
  3. В свойстве «История данных» выберите значение «Использовать».
  4. Нажмите кнопку «История данных...» для тонкой настройки полей, если требуется исключить из версионирования определенные реквизиты.

Для разработчиков платформа предоставляет гибкий программный интерфейс. Например, мы можем использовать событие ОбработкаФормированияПоВерсии в модуле объекта, чтобы программно вмешаться в процесс записи версии и добавить сопутствующие данные (например, IP-адрес пользователя или информацию из внешних систем интеграции):


Процедура ОбработкаФормированияПоВерсии(Версия, ИсточникДанных, Отказ)
    
    // Пример добавления дополнительных данных в историю
    ДополнительныеДанные = Новый Структура;
    ДополнительныеДанные.Вставить("ИмяКомпьютера", ИмяКомпьютера());
    ДополнительныеДанные.Вставить("IPАдрес", "192.168.1.105"); // Пример статического или полученного программно IP
    
    ЗаписьИсторииДанных = Новый ЗаписьИсторииДанных;
    ЗаписьИсторииДанных.ДобавитьДополнительныеДанные(ДополнительныеДанные);
    
КонецПроцедуры

---

Вариант 2. Подсистема БСП «Версионирование объектов»

Если ваша конфигурация разработана на базе БСП (Библиотеки стандартных подсистем), вам не нужно изобретать велосипед. БСП содержит готовую подсистему «Версионирование объектов».

Принцип работы БСП заключается в следующем:

В момент записи объекта срабатывает подписка на событие ПриЗаписи. Система сериализует весь документ в формат XML (или Fastinfoset) и записывает его в регистр сведений ВерсииОбъектов. Для пользователя доступен удобный интерфейс сравнения версий, где подсвечиваются измененные реквизиты.

Однако у этого метода есть существенный недостаток: запись происходит синхронно внутри транзакции сохранения документа. Если у вас интенсивный документооборот или выполняются массовые групповые обработки, это может вызвать сильное замедление работы системы.

---

Вариант 3. Создание собственного регистра сведений (для легких конфигураций)

Если вы разрабатываете небольшую конфигурацию с нуля и не хотите перегружать ее тяжелыми подсистемами БСП, лучшим выбором будет создание собственного простого регистра изменений.

Выясним структуру нашего будущего регистра сведений. Создадим непериодический независимый регистр сведений ЖурналИзмененийДокументов со следующей структурой:

Измерения:

Ресурсы:

Реквизиты:

Разберем по шагам алгоритм сравнения данных в модуле объекта документа. Нам необходимо перехватить событие ПередЗаписью, сравнить текущие реквизиты объекта с теми, что уже сохранены в базе данных, и записать различия.

Посмотрим на пример реализации кода в модуле документа:


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    
    // Проверяем, что документ уже был записан ранее
    Если ЭтоНовый() Тогда
        Возврат; 
    КонецЕсли;
    
    // Получаем состояние документа из базы данных до внесения текущих изменений
    ДокументИзБазы = Ссылка.ПолучитьОбъект();
    ТаблицаИзменений = Новый ТаблицаЗначений;
    ТаблицаИзменений.Колонки.Добавить("ИмяРеквизита");
    ТаблицаИзменений.Колонки.Добавить("ЗначениеБыло");
    TaблицаИзменений.Колонки.Добавить("ЗначениеСтало");
    
    // 1. Сравниваем основные реквизиты шапки документа
    Для Каждого Реквизит Из Метаданные().Реквизиты Цикл
        ИмяРеквизита = Реквизит.Имя;
        ТекущееЗначение = ЭтотОбъект[ИмяРеквизита];
        СтароеЗначение = ДокументИзБазы[ИмяРеквизита];
        
        Если ТекущееЗначение <> СтароеЗначение Тогда
            НоваяСтрока = ТаблицаИзменений.Добавить();
            НоваяСтрока.ИмяРеквизита = ИмяРеквизита;
            НоваяСтрока.ЗначениеБыло = СтароеЗначение;
            НоваяСтрока.ЗначениеСтало = ТекущееЗначение;
        КонецЕсли;
    КонецЦикла;
    
    // Если обнаружены изменения, записываем их в наш регистр
    Если ТаблицаИзменений.Количество() > 0 Тогда
        ЗаписатьЛогИзменений(ТаблицаИзменений);
    КонецЕсли;
    
КонецПроцедуры

Процедура ЗаписатьЛогИзменений(ТаблицаИзменений)
    
    Для Каждого СтрокаИзменения Из ТаблицаИзменений Цикл
        НаборЗаписей = РегистрыСведений.ЖурналИзмененийДокументов.СоздатьНаборЗаписей();
        
        // Устанавливаем отбор, чтобы не затереть другие логи
        НаборЗаписей.Отбор.СсылкаНаОбъект.Установить(Ссылка);
        
        НоваяЗапись = НаборЗаписей.Добавить();
        НоваяЗапись.СсылкаНаОбъект = Ссылка;
        НоваяЗапись.ИмяРеквизита = СтрокаИзменения.ИмяРеквизита;
        НоваяЗапись.ЗначениеБыло = СтрокаИзменения.ЗначениеБыло;
        НоваяЗапись.ЗначениеСтало = СтрокаИзменения.ЗначениеСтало;
        НоваяЗапись.Пользователь = ПараметрыСеанса.ТекущийПользователь;
        НоваяЗапись.ДатаИзменения = ТекущаяДата();
        
        // Записываем лог без замещения старых записей по другим реквизитам
        НаборЗаписей.Записать(Ложь);
    КонецЦикла;
    
КонецПроцедуры

---

Критически важный нюанс: Обработка табличных частей

Разберем ситуацию с табличными частями. Простое сравнение строк по индексу (строка 1, строка 2...) работать корректно не будет. Если пользователь удалит вторую строку из десяти, то все строки с третьей по десятую сместятся вверх. При обычном переборе индексов алгоритм решит, что изменились абсолютно все строки ниже удаленной.

Чтобы избежать этой проблемы, рекомендуется использовать скрытый реквизит табличной части (например, уникальный ключ строки типа УникальныйИдентификатор или системный КлючСвязи). При сравнении строк табличной части нужно сопоставлять данные именно по этому ключу, а не по физическому номеру строки в таблице.

---

Рекомендации по выбору решения

← На главную