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