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