Как получить значение поля до его изменения в 1С:Предприятии?

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

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

Использование дополнительных реквизитов формы для хранения старого значения

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

Разберем этот подход по шагам:

  1. Создание дополнительного реквизита формы:
    В управляемой форме нам потребуется создать новый реквизит. Пусть у нас есть поле ПолеДляОтслеживания типа Число. Мы создадим реквизит формы, например, СтароеЗначениеПоляДляОтслеживания, того же типа (Число). Этот реквизит не обязательно должен быть видимым для пользователя на форме. Он будет служить внутренней переменной для хранения состояния.
  2. Инициализация старого значения при открытии формы:
    Чтобы наш вспомогательный реквизит содержал актуальное "старое" значение при первом изменении поля, его необходимо инициализировать при создании или открытии формы. Для этого мы можем использовать серверные обработчики формы. Если форма открывается для нового объекта, старое значение будет пустым или значением по умолчанию. Если же форма открывается для существующего объекта, мы должны прочитать его текущее значение из базы данных и присвоить его нашему вспомогательному реквизиту.
    
    &НаСервере
    Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
        // Инициализация реквизита формы для хранения старого значения
        Если Объект.Ссылка.Пустая() Тогда
            // Для нового объекта старое значение можно установить в значение по умолчанию или 0
            СтароеЗначениеПоляДляОтслеживания = 0; // Или другое значение по умолчанию
        Иначе
            // Для существующего объекта получаем текущее значение из базы данных
            // Предполагаем, что "Объект" - это основной реквизит формы, привязанный к данным объекта
            СтароеЗначениеПоляДляОтслеживания = Объект.ПолеДляОтслеживания; 
        КонецЕсли;
    КонецПроцедуры
    
    &НаСервере
    Процедура ПриЧтенииНаСервере(ТекущийОбъект)
        // Обновляем старое значение при каждом чтении объекта с сервера
        СтароеЗначениеПоляДляОтслеживания = ТекущийОбъект.ПолеДляОтслеживания;
    КонецПроцедуры
    

    Обработчик ПриЧтенииНаСервере также важен, так как он гарантирует, что при перезагрузке данных объекта (например, после записи или обновления) вспомогательный реквизит будет актуализирован.

  3. Обработка изменения поля и вывод сообщения:
    Теперь, когда у нас есть механизм хранения старого значения, нам нужно обрабатывать событие ПриИзменении для нашего основного поля (ПолеДляОтслеживания) на форме. Это событие обычно выполняется на клиенте.
    
    &НаКлиенте
    Процедура ПолеДляОтслеживанияПриИзменении(Элемент)
        // Получаем новое значение из элемента формы
        НовоеЗначение = Элемент.Значение;
    
        // Сравниваем новое значение со старым, хранящимся в реквизите формы
        Если НовоеЗначение <> СтароеЗначениеПоляДляОтслеживания Тогда
            Сообщение = Новый СообщениеПользователю;
            Сообщение.Текст = "Вы изменили значение: было " + СтароеЗначениеПоляДляОтслеживания + ", стало " + НовоеЗначение;
            Сообщение.Сообщить();
    
            // После сообщения обновляем старое значение в реквизите формы
            // Это важно, чтобы при следующем изменении "старым" было только что установленное значение.
            СтароеЗначениеПоляДляОтслеживания = НовоеЗначение;
        КонецЕсли;
    КонецПроцедуры
    

    Таким образом, каждый раз при изменении поля пользователь будет видеть сообщение о том, какое значение было до текущей правки. После вывода сообщения мы обновляем СтароеЗначениеПоляДляОтслеживания новым значением, готовясь к следующему возможному изменению.

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

Использование клиентских переменных модуля формы

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

Разберем этот подход:

  1. Объявление клиентской переменной:
    В модуле формы, в секции объявления переменных, объявляем клиентскую переменную.
    
    // В модуле формы, в верхней части
    Перем мСтароеЗначениеПоляДляОтслеживанияНаКлиенте Экспорт;
    

    Ключевое слово Экспорт здесь необязательно, если переменная используется только внутри модуля формы, но может быть полезно для отладки или если к ней нужно обращаться из других модулей (что для данной задачи нетипично).

  2. Инициализация переменной:
    Инициализацию можно произвести так же, как и для реквизита формы, в обработчиках ПриСозданииНаСервере или ПриЧтенииНаСервере. Однако, поскольку переменная клиентская, прямое присвоение из серверного контекста невозможно. Мы должны передать значение на клиент.
    
    &НаСервере
    Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
        Если НЕ Объект.Ссылка.Пустая() Тогда
            // Читаем значение на сервере и передаем его на клиент
            мСтароеЗначениеПоляДляОтслеживанияНаКлиенте = Объект.ПолеДляОтслеживания;
        Иначе
            мСтароеЗначениеПоляДляОтслеживанияНаКлиенте = 0; // Значение по умолчанию для нового объекта
        КонецЕсли;
    КонецПроцедуры
    
    &НаСервере
    Процедура ПриЧтенииНаСервере(ТекущийОбъект)
        // Обновляем значение при каждом чтении объекта с сервера
        мСтароеЗначениеПоляДляОтслеживанияНаКлиенте = ТекущийОбъект.ПолеДляОтслеживания;
    КонецПроцедуры
    

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

  3. Обработка изменения поля на клиенте:
    Логика обработки изменения будет аналогична случаю с реквизитами формы:
    
    &НаКлиенте
    Процедура ПолеДляОтслеживанияПриИзменении(Элемент)
        НовоеЗначение = Элемент.Значение;
    
        Если НовоеЗначение <> мСтароеЗначениеПоляДляОтслеживанияНаКлиенте Тогда
            Сообщение = Новый СообщениеПользователю;
            Сообщение.Текст = "Вы изменили значение: было " + мСтароеЗначениеПоляДляОтслеживанияНаКлиенте + ", стало " + НовоеЗначение;
            Сообщение.Сообщить();
    
            мСтароеЗначениеПоляДляОтслеживанияНаКлиенте = НовоеЗначение;
        КонецЕсли;
    КонецПроцедуры
    

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

Использование специфичных событий ввода (ОкончаниеВводаТекста, ОбработкаВыбора)

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

Рассмотрим два таких события:

  1. Событие ОкончаниеВводаТекста:
    Это событие возникает, когда пользователь заканчивает ввод текста в поле ввода. В момент вызова этого события сам элемент формы (или реквизит, к которому он привязан) еще содержит старое значение, а новое, введенное пользователем, передается в параметре Текст. Это дает нам прекрасную возможность для сравнения.
    
    &НаКлиенте
    Процедура ПолеДляОтслеживанияОкончаниеВводаТекста(Элемент, Текст, СтандартнаяОбработка)
        // Старое значение находится в самом элементе (или в его источнике данных)
        СтароеЗначение = Элемент.Значение; 
        // Новое значение передается в параметре "Текст"
        НовоеЗначение = Текст;
    
        Если СтароеЗначение <> НовоеЗначение Тогда
            Сообщение = Новый СообщениеПользователю;
            Сообщение.Текст = "Вы изменили значение: было " + СтароеЗначение + ", стало " + НовоеЗначение;
            Сообщение.Сообщить();
            // Здесь можно также выполнить дополнительную логику, например, отменить изменение:
            // СтандартнаяОбработка = Ложь; // Если мы хотим, чтобы новое значение не применялось
        КонецЕсли;
    КонецПроцедуры
    

    Это событие идеально подходит для полей с типом "Строка", "Число" или "Дата", которые пользователь вводит вручную. Оно позволяет оперативно реагировать на ввод пользователя.

  2. Событие ОбработкаВыбора:
    Это событие возникает, когда пользователь выбирает значение из списка выбора, открытого для поля (например, из списка справочника или перечисления). Аналогично ОкончаниеВводаТекста, в этот момент поле еще содержит старое значение, а новое, выбранное пользователем, передается в параметре ВыбранноеЗначение.
    
    &НаКлиенте
    Процедура ПолеДляОтслеживанияОбработкаВыбора(Элемент, ВыбранноеЗначение, СтандартнаяОбработка)
        // Старое значение находится в самом элементе
        СтароеЗначение = Элемент.Значение;
        // Новое значение передается в параметре "ВыбранноеЗначение"
        НовоеЗначение = ВыбранноеЗначение;
    
        Если СтароеЗначение <> НовоеЗначение Тогда
            Сообщение = Новый СообщениеПользователю;
            Сообщение.Текст = "Вы выбрали другое значение: было " + СтароеЗначение + ", стало " + НовоеЗначение;
            Сообщение.Сообщить();
            // Также можно отменить выбор:
            // СтандартнаяОбработка = Ложь;
        КонецЕсли;
    КонецПроцедуры
    

    Это событие особенно полезно для полей ссылочного типа (ссылки на справочники, документы) или перечисления, где выбор осуществляется из предопределенного списка.

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

Получение старого значения из базы данных (для уже записанных объектов)

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

Рассмотрим, как это можно реализовать:

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

    В этом примере мы создаем серверную функцию ПолучитьСтароеЗначениеПоляИзБазыДанныхНаСервере, которая по ссылке объекта и имени поля возвращает его значение из базы данных. Затем мы вызываем эту функцию из клиентского обработчика ПриИзменении, чтобы сравнить текущее значение с тем, что хранится в БД.

  3. Особенности и рекомендации:
    • Производительность: Каждое обращение к базе данных (ПолучитьОбъект()) может быть ресурсоемким, особенно если это происходит часто или для большого количества полей. Используйте этот метод осознанно.
    • Актуальность: Этот метод дает вам значение, которое было сохранено в базе. Если пользователь несколько раз меняет поле до записи, вы будете видеть сравнение с сохраненным, а не с предыдущим значением в текущей сессии.
    • Контекст: Чтение объекта по ссылке всегда выполняется на сервере, так как только там есть прямой доступ к базе данных.

Рассмотрим дополнительные аспекты и рекомендации

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

  1. Отслеживание изменений в табличных частях:
    Если вам необходимо отслеживать изменения полей в табличных частях документа или справочника, подход остается аналогичным, но требует более тщательной организации. Для каждой строки табличной части может потребоваться своя "история" старых значений. Мы можем:
    • Добавить вспомогательные колонки в табличную часть формы: Создаем невидимые колонки, которые будут хранить старые значения соответствующих полей для каждой строки.
    • Использовать коллекцию структур/соответствий: В модуле формы или в реквизите формы типа "Произвольный" (Структура, Соответствие) хранить коллекцию старых значений, где ключом может быть уникальный идентификатор строки (например, УникальныйИдентификатор), а значением — структура с парами "ИмяПоля:СтароеЗначение".

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

  2. Контекст выполнения (Клиент/Сервер):
    Мы постоянно упоминаем клиентские и серверные процедуры. Очень важно понимать, где должна выполняться ваша логика:
    • Клиент: Подходит для вывода сообщений пользователю (СообщениеПользователю), интерактивной реакции на ввод (ПриИзменении, ОкончаниеВводаТекста). Хранение старых значений на клиенте (реквизиты формы, клиентские переменные) обеспечивает быстрый доступ и минимальную нагрузку на сервер.
    • Сервер: Необходим для доступа к данным базы (ПолучитьОбъект()), выполнения сложных вычислений, которые требуют серверных ресурсов или доступа к серверным модулям. Инициализация старых значений при создании/чтении формы часто происходит на сервере, чтобы получить данные из БД.

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

  3. Производительность и удобство поддержки:
    При выборе метода всегда следует учитывать эти два фактора:
    • Производительность: Частые запросы к серверу (например, многократные ПолучитьОбъект() при каждом изменении поля) могут замедлить работу формы. Предпочтительнее хранить старые значения на клиенте, если это возможно.
    • Удобство поддержки: Слишком большое количество вспомогательных реквизитов формы или запутанная логика с клиентскими переменными может усложнить код. Стремитесь к чистому и читаемому коду, возможно, с выносом общей логики в отдельные функции или процедуры.
  4. Работа со сложными типами данных:
    Если отслеживаемое поле имеет сложный тип (например, структуру, соответствие, коллекцию объектов), простое сравнение Значение1 <> Значение2 может не сработать корректно. Для таких случаев потребуется более глубокое сравнение, возможно, через сериализацию в строку или поэлементное сравнение. В большинстве простых сценариев с примитивными типами или ссылками стандартное сравнение работает без проблем.

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

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

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

← На главную