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