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