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