В практике разработки на платформе 1С:Предприятие часто возникает задача: необходимо проверить определенные условия перед проведением документа и, если они не выполнены, запретить проведение, но при этом сохранить те данные (реквизиты, комментарии, изменения в табличных частях), которые пользователь внес в форму. Рассмотрим подробнее, почему стандартный подход "в лоб" не работает и какие архитектурные решения существуют для реализации этой задачи.
Разберем по шагам, что происходит, когда пользователь нажимает кнопку "Провести". Платформа открывает одну общую транзакцию. Внутри этой транзакции последовательно вызываются события: ПередЗаписью (на сервере), собственно запись объекта в базу данных, и затем — ОбработкаПроведения. Если в процедуре ОбработкаПроведения мы устанавливаем флаг Отказ = Истина, система воспринимает это как сигнал к полной отмене транзакции. Согласно принципу атомарности (ACID), либо все действия внутри транзакции выполняются успешно, либо все они отменяются. В итоге запись документа в базу данных аннулируется, и все изменения, внесенные пользователем перед нажатием кнопки, пропадают.
Проанализируем ситуацию, в которой оказался автор темы. Он пытался вызвать метод Записать(РежимЗаписиДокумента.Запись) прямо внутри процедуры проведения. Это классическая ошибка, так как объект уже находится в состоянии записи, и повторный вызов записи того же экземпляра объекта внутри транзакции либо проигнорируется, либо приведет к ошибке блокировки, но главное — последующий Отказ = Истина все равно откатит всё назад.
Выясним причину, по которой это решение является наиболее стабильным. Если мы перенесем проверку критических условий из проведения в обработчик ПередЗаписью, у нас появится возможность управлять самим процессом входа в транзакцию проведения.
Посмотрим на пример реализации в модуле объекта:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Проверяем, что пользователь нажал именно кнопку "Провести"
Если РежимЗаписи = РежимЗаписиДокумента.Проведение Тогда
// Выполняем нашу проверку (например, соответствие ТЧ перемещению)
ТчСоответствует = ПроверитьСоответствиеТабличнойЧасти();
Если НЕ ТчСоответствует Тогда
// Если условие не выполнено, мы НЕ ставим Отказ = Истина,
// так как это отменит запись. Вместо этого мы меняем РЕЖИМ записи.
РежимЗаписи = РежимЗаписиДокумента.Запись;
#Если Клиент Тогда
Сообщить("Документ записан, но не проведен: данные ТЧ некорректны!");
#КонецЕсли
КонецЕсли;
КонецЕсли;
КонецПроцедуры
В данном случае мы "подменяем" намерение пользователя на лету. Система видит, что РежимЗаписи изменился с Проведение на Запись, и после завершения процедуры ПередЗаписью она просто сохранит документ, не запуская механизм формирования движений и не устанавливая флаг Проведен = Истина. Это именно то, чего добивался автор.
Иногда логика проверки слишком сложна и должна быть разделена между записью и проведением. В таких случаях мы можем использовать коллекцию ДополнительныеСвойства. Проанализируем, как это работает вместе с обработчиком ОбработкаПроведения.
В ПередЗаписью мы помечаем объект специальным флагом, а в ОбработкаПроведения принимаем решение. Однако, помните: если мы хотим именно записать изменения и не провести, нам всё равно придется менять РежимЗаписи в ПередЗаписью, как показано выше. Если же мы всё-таки попали в ОбработкаПроведения, то единственный способ не делать движений, но оставить документ "проведенным" (с синей галочкой) — это просто выйти из процедуры.
Рассмотрим пример кода в ОбработкаПроведения:
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
Если НеобходимостьПроведенияОтсутствует Тогда
// Документ станет "Проведенным", но движений не будет.
// Это может привести к рассинхрону данных!
Возврат;
КонецЕсли;
// Стандартная логика проведения...
Движения.Хозрасчетный.Записывать = Истина;
// ...
КонецПроцедуры
Важное предупреждение: Использование Возврат без установки Отказ = Истина в проведении — это опасный путь. Пользователь будет видеть статус "Проведен", но фактически документ не сделает никаких записей в регистры. Это допустимо только для специфических технических документов.
Рассмотрим ситуацию с точки зрения интерфейса. В управляемых (и обычных) формах мы можем перехватить нажатие кнопки проведения. Это самый "чистый" способ с точки зрения пользователя. Разберем по шагам алгоритм для формы:
ПередЗаписью на форме проверяем ПараметрыЗаписи.РежимЗаписи.ПараметрыЗаписи.РежимЗаписи на Запись.Такой подход позволяет избежать "неожиданного" поведения системы, когда пользователь нажал "Провести", а документ просто записался. Прозрачность действий — залог отсутствия ошибок учета.
Разберем несколько важных моментов, которые упоминались в обсуждении на форуме:
1. Синхронизация данных. Если вы позволяете записывать документ, который не прошел проверку проведения, вы создаете ситуацию "отложенной ошибки". Убедитесь, что в системе настроены отчеты или регламентные задания, которые помогут пользователю найти такие недопроведенные документы позже.
2. Блокировки. При попытке программной записи объекта из самого себя (например, вызов ЭтотОбъект.Записать() внутри события записи) платформа 8.3 выдаст ошибку. Никогда не вызывайте метод Записать() внутри обработчиков событий записи или проведения текущего объекта.
3. Фоновые задания. В сообщении 26 предлагали использовать фоновое задание для записи. Проанализируем это: фоновое задание работает в другой транзакции. Если основная транзакция еще не завершена (а она не завершена, пока работает ОбработкаПроведения), фоновое задание наткнется на объектную блокировку и не сможет ничего записать. Этот метод избыточен и сложен в реализации для данной задачи.
Для реализации прерывания проведения с сохранением данных мы рекомендуем использовать подмену РежимЗаписи в процедуре ПередЗаписью модуля объекта. Это позволяет остаться в рамках одной транзакции, корректно сохранить все реквизиты и при этом не вызвать механизм проведения, который мог бы завершиться откатом всех внесенных изменений.