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

В отличие от классических СУБД, где история изменений ведётся на уровне транзакций, не предоставляет встроенных инструментов для прямого доступа к "старым" значениям реквизитов. Однако существуют обходные пути — от использования стандартных событий платформы до создания собственных механизмов логирования. В этой статье мы разберём все актуальные способы получения предыдущих значений реквизитов, включая их плюсы, минусы и нюансы реализации.

1. Использование события "ПередЗаписью" (BeforeWrite)

Самый очевидный и распространённый метод — перехват события ПередЗаписью у объекта метаданных. Это событие срабатывает непосредственно перед сохранением данных в базу, когда старое значение ещё доступно в памяти.

Преимущества метода:

  • 🔹 Простота реализации — не требует изменения конфигурации или доступа к серверу.
  • 🔹 Минимальные накладные расходы — логика выполняется только при изменении объекта.
  • 🔹 Гибкость — можно сохранять историю в любые хранилища (регистры сведений, отдельные таблицы и т.д.).

Пример кода для справочника Номенклатура:

Процедура ПередЗаписью(Отказ, Транзакция)

Если ЗначениеЗаполнено(Объект.Цена) И Объект.Цена <> Объект.Цена.Получить() Тогда

Сообщить("Старая цена: " + Объект.Цена.Получить() + ", Новая цена: " + Объект.Цена);

// Сохранение в регистр сведений "ИсторияИзмененийЦен"

Запись = РегистрыСведений.ИсторияИзмененийЦен.СоздатьМенеджерЗаписи();

Запись.Дата = ТекущаяДата();

Запись.Номенклатура = Объект.Ссылка;

Запись.СтараяЦена = Объект.Цена.Получить();

Запись.НоваяЦена = Объект.Цена;

Запись.Записать();

КонецЕсли;

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

⚠️ Внимание: Метод Получить() работает только для реквизитов, которые уже были прочитаны из базы. Если объект создаётся заново (не редактируется), старое значение будет пустым. В таких случаях требуется дополнительная проверка на Объект.ЭтоНовый().
💡

Для реквизитов типа "Строка" или "Число" можно использовать конструкцию Объект.Реквизит.ПолучитьИсходное() — она вернёт значение, которое было при последнем чтении объекта из базы, даже если оно не изменялось в текущей сессии.

2. Журнал регистрации изменений

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

Как включить журнал регистрации:

  1. Откройте конфигуратор и перейдите в Администрирование → Журналы регистрации.
  2. Создайте новый журнал или отредактируйте существующий.
  3. В настройках укажите, какие объекты и события нужно регистрировать (например, "Изменение" для справочника Контрагенты).
  4. Установите флаг "Регистрировать значения реквизитов".

После настройки все изменения будут записываться в таблицу _EventLog (для SQL-версии) или в файлы журналов (для файловой базы). Доступ к данным осуществляется через запрос:

Запрос = Новый Запрос;

Запрос.Текст =

"ВЫБРАТЬ

| ЖурналРегистрации.Дата,

| ЖурналРегистрации.Ссылка КАК Объект,

| ЖурналРегистрации.Данные КАК СтароеЗначение

|ИЗ

| ЖурналРегистрации.ИзменениеОбъектов КАК ЖурналРегистрации

|ГДЕ

| ЖурналРегистрации.Ссылка = &Ссылка

| И ЖурналРегистрации.Дата > ДОБАВИТЬМЕСЯЦ(ТЕКУЩАЯДАТА(), -1)

|УПОРЯДОЧИТЬ ПО

| Дата УБЫВ";

Запрос.УстановитьПараметр("Ссылка", СсылкаНаОбъект);

Результат = Запрос.Выполнить();

Параметр Описание Ограничения
Регистрация значений реквизитов Фиксирует старое и новое значение изменённого реквизита Не работает для объектов, изменённых программно без интерфейса
Хранение в SQL Данные хранятся в таблице _EventLog Требует прав на доступ к системным таблицам
Файловая база Журналы хранятся в файлах *.lgf Сложно парсить большие объёмы данных
⚠️ Внимание: Журнал регистрации может значительно увеличивать объём базы данных, если включена регистрация всех событий. Рекомендуется настраивать фильтры по типам объектов и периодам хранения.
📊 Какой метод получения старых значений вы используете чаще?
Событие ПередЗаписью
Журнал регистрации
Транзакции и временные таблицы
Собственный механизм логирования

3. Работа с транзакциями и временными таблицами

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

Алгоритм реализации:

  1. Начать транзакцию (НачатьТранзакцию()).
  2. Прочитать текущие значения реквизитов объекта и сохранить их во временную таблицу.
  3. Выполнить изменение объекта.
  4. Зафиксировать транзакцию (ЗафиксироватьТранзакцию()).
  5. Сравнить новые значения с сохранёнными во временной таблице.

Пример кода:

Процедура ИзменитьЦенуНоменклатуры(Ссылка, НоваяЦена)

НачатьТранзакцию();

// Сохраняем старое значение

СтараяЦена = Ссылка.Цена;

// Вносим изменения

Объект = Ссылка.ПолучитьОбъект();

Объект.Цена = НоваяЦена;

Объект.Записать();

ЗафиксироватьТранзакцию();

// Теперь можно использовать СтараяЦена

Если СтараяЦена <> НоваяЦена Тогда

Сообщить("Цена изменена с " + СтараяЦена + " на " + НоваяЦена);

КонецЕсли;

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

Этот метод подходит для управляемых форм, где логика изменения данных контролируется программно. Однако он не сработает, если объект изменяется через неуправляемый интерфейс или внешние обработки.

Что делать, если транзакция прерывается?

Если транзакция прерывается (например, из-за ошибки), данные во временной таблице останутся неактуальными. В таких случаях рекомендуется:

1. Использовать блоки Попытка...Исключение для отката изменений.

2. Вести лог операций в отдельной таблице с пометкой о статусе (успех/ошибка).

3. Для SQL-баз настраивать триггеры на уровне СУБД.

4. Создание собственного механизма логирования

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

  • 📌 Регистр сведений для хранения истории (даты, пользователя, старое/новое значение).
  • 📌 Подписку на события объектов (создание, изменение, удаление).
  • 📌 Интерфейс для просмотра истории (отчёт или обработка).
  • 📌 Очистку устаревших записей (по дате или количеству).

Пример структуры регистра сведений "ИсторияИзмененийРеквизитов":

Измерение/Ресурс Тип данных Описание
ДатаИзменения ДатаВремя Когда было внесено изменение
Пользователь СправочникСсылка.Пользователи Кто внёс изменение
Объект ЛюбаяСсылка Ссылка на изменённый объект
Реквизит Строка(100) Имя реквизита (например, "Цена")
СтараяЗначение Произвольный Значение реквизита до изменения

Для автоматической записи в регистр можно использовать подписку на событие:

Процедура ПриИзмененииОбъекта(Объект, СтароеЗначение, НовоеЗначение) Экспорт

Запись = РегистрыСведений.ИсторияИзмененийРеквизитов.СоздатьМенеджерЗаписи();

Запись.ДатаИзменения = ТекущаяДата();

Запись.Пользователь = ТекущийПользователь();

Запись.Объект = Объект.Ссылка;

Запись.Реквизит = "Цена"; // Или передавать динамически

Запись.СтараяЗначение = СтароеЗначение;

Запись.НоваяЗначение = НовоеЗначение;

Запись.Записать();

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

Создать регистр сведений для истории|Добавить измерения (Дата, Пользователь, Объект)|Определить ресурсы (СтараяЗначение, НоваяЗначение)|Написать обработчик события ПередЗаписью|Протестировать на реальных данных-->

5. Использование версионирования объектов

В некоторых конфигурациях (например, 1С:ERP или 1С:Управление холдингом) предусмотрено версионирование объектов. Этот механизм автоматически сохраняет копии документов или справочников при каждом изменении, позволяя восстановить любое предыдущее состояние.

Как включить версионирование:

  1. В конфигураторе откройте свойства объекта метаданных (например, справочника Контрагенты).
  2. На вкладке "Прочее" установите флаг "Вести версии объектов".
  3. Укажите количество хранимых версий (по умолчанию — 10).
  4. Настройте права доступа к просмотру версий для ролей.

Чтобы получить предыдущую версию объекта, используйте метод ПолучитьВерсию():

// Получаем текущий объект

ТекущийКонтрагент = Справочники.Контрагенты.НайтиПоНаименованию("ООО Ромашка");

// Получаем его версию от вчера

ВерсияКонтрагента = ТекущийКонтрагент.ПолучитьВерсию(ДобавитьДень(ТекущаяДата(), -1));

// Сравниваем реквизиты

Сообщить("Текущий ИНН: " + ТекущийКонтрагент.ИНН);

Сообщить("ИНН вчера: " + ВерсияКонтрагента.ИНН);

⚠️ Внимание: Версионирование увеличивает объём базы данных пропорционально количеству изменений. В крупных системах это может привести к замедлению работы. Рекомендуется ограничивать количество хранимых версий (например, последние 5–10 изменений).
💡

Версионирование — единственный встроенный механизм 1С, который позволяет восстановить полное состояние объекта (включая все реквизиты и табличные части) на любую дату в прошлом.

6. Анализ логов SQL-сервера (для опытных администраторов)

Если вы работаете с 1С на SQL-сервере (Microsoft SQL Server, PostgreSQL), можно извлечь историю изменений прямо из транзакционных логов базы данных. Этот метод требует глубоких знаний SQL и прав доступа к серверу, но даёт максимальную точность.

Для Microsoft SQL Server можно использовать:

  • 🔧 Функцию fn_dblog() — возвращает записи из журнала транзакций.
  • 🔧 Change Data Capture (CDC) — встроенный механизм отслеживания изменений.
  • 🔧 Триггеры — настраиваемые процедуры, срабатывающие при изменении данных.

Пример запроса для извлечения истории изменений поля Price в таблице справочника Номенклатура:

-- Пример для MS SQL (требует прав sysadmin)

SELECT

[Current LSN],

[Transaction ID],

[Operation] (1 = LOP_MODIFY_ROW),

[Context],

[AllocUnitName] AS TableName,

CASE

WHEN [Operation] = 1 THEN 'Изменение'

ELSE 'Другое'

END AS OperationType,

CONVERT(VARCHAR(MAX), [Description]) AS ChangeDetails

FROM

fn_dblog(NULL, NULL)

WHERE

[AllocUnitName] LIKE '%_Reference16%' -- Таблица справочника Номенклатура

AND [Operation] = 1 -- Только операции изменения

ORDER BY

[Current LSN] DESC;

Для PostgreSQL подойдёт расширение pgAudit или настройка WAL (Write-Ahead Logging). Однако этот метод имеет ограничения:

  • 🚫 Требует прямого доступа к SQL-серверу (не всегда возможно в облачных решениях).
  • 🚫 Логи могут очищаться при резервном копировании или перезапуске сервера.
  • 🚫 Сложно парсить данные, если структура таблиц 1С изменялась.
💡

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

7. Внешние инструменты и обработки

Если вам нужна история изменений "здесь и сейчас" без глубокой доработки конфигурации, можно воспользоваться готовыми обработками от сообщества 1С:

  • 📦 "История изменений объектов" (бесплатная обработка с Инфостарта).
  • 📦 "Аудит изменений" (платное решение с расширенными отчётами).
  • 📦 "1С:Логгирование" (тиражное решение для комплексного аудита).

Пример работы с обработкой "История изменений объектов":

  1. Скачайте и подключите обработку к базе.
  2. В настройках укажите, какие объекты и реквизиты нужно отслеживать.
  3. Настройте период хранения истории (по умолчанию — 30 дней).
  4. Используйте интерфейс обработки для просмотра изменений по датам, пользователям или объектам.

Преимущества внешних инструментов:

  • ✅ Не требуют изменения конфигурации (работают "сверху").
  • ✅ Часто имеют удобный интерфейс с фильтрами и отчётами.
  • ✅ Поддерживают экспорт данных в Excel или JSON.

⚠️ Внимание: Перед установкой сторонних обработок проверьте их совместимость с вашей версией платформы и конфигурации. Некоторые решения могут конфликтовать с обновлениями 1С.

FAQ: Частые вопросы по работе со старыми значениями реквизитов

Можно ли получить историю изменений для документа, который уже удалён?

Если в базе включён журнал регистрации или версионирование, то да. В журнале регистрации сохраняются ссылки на удалённые объекты (помеченные как "Удаление"). Версионирование также хранит копии объектов, но только если они не были физически очищены из базы (например, через "Поиск и замена ссылок").

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

Как отследить, кто именно изменил реквизит, если в базе несколько пользователей?

Для этого нужно:

  1. Включить регистрацию имени пользователя в журнале регистрации или собственном механизме логирования.
  2. Использовать функцию ТекущийПользователь() в обработчиках событий.
  3. В SQL-базах можно анализировать поле UserName в системных таблицах (например, _EventLog).

Пример кода для сохранения пользователя:

Запись.Пользователь = ПользователиИнформационнойБазы.ТекущийПользователь();
Что делать, если событие "ПередЗаписью" не срабатывает при программном изменении объекта?

Событие ПередЗаписью не вызывается, если объект изменяется через:

  • Прямые SQL-запросы (метод ВыполнитьSQL()).
  • Массовые операции (например, групповой перенос данных).
  • Внешние обработки, которые обходят стандартные механизмы записи.

Решения:

  • Использовать транзакционные блоки (см. раздел 3).
  • Настраивать триггеры на уровне SQL (для SQL-версий).
  • Заменять программные изменения на вызов методов объекта (например, Объект.Записать() вместо прямого SQL).
Как получить историю изменений для реквизита табличной части?

Для табличных частей логика аналогична обычным реквизитам, но требует учёта конкретной строки. Пример для документа ПоступлениеТоваровУслуг:

Процедура ПередЗаписью(Отказ)

Для Каждого Строка Из Объект.Товары Цикл

Если Строка.Цена <> Строка.Цена.Получить() Тогда

Сообщить("Старая цена товара " + Строка.Номенклатура +

": " + Строка.Цена.Получить());

КонецЕсли;

КонецЦикла;

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

Для журнала регистрации нужно включить отслеживание изменений табличных частей в его настройках.

Можно ли откатить изменение реквизита автоматически, если оно неверное?

Да, для этого нужно:

  1. В обработчике ПередЗаписью сохранить старое значение.
  2. После записи объекта сравнить новое значение с допустимыми критериями.
  3. Если изменение некорректно — откатить его программно:
Если НоваяЦена < 0 Тогда

Объект.Цена = СтараяЦена; // Восстанавливаем старое значение

Объект.Записать();

Сообщить("Цена не может быть отрицательной! Значение откачено.");

КонецЕсли;

Для сложных правил валидации лучше использовать подписки на события или бизнес-сервер 1С:EDT.