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