Как реализовать проверку при интерактивной отмене проведения и пометке на удаление документа в 1С 8.3?

Программист 1С v8.3 (Управляемые формы)
← На главную

При разработке и адаптации информационных систем на платформе 1С 8.3 мы часто сталкиваемся с необходимостью внедрения специфической бизнес-логики, которая должна срабатывать при определенных действиях пользователя. Одна из таких распространенных задач — это перехват событий интерактивной отмены проведения документа и пометки его на удаление. Например, нам может потребоваться предотвратить отмену проведения документа, если по нему существуют некие "резервы" или незакрытые обязательства, или запретить пометку на удаление, если документ влияет на критически важные данные.

Сложность заключается в том, что такие действия, как отмена проведения или пометка на удаление, могут быть инициированы пользователем из множества мест в программе: из формы самого документа, из списков, из различных отчетов или обработок (например, из структуры подчиненности). Поэтому решение "в лоб" с добавлением собственной кнопки или пункта в контекстное меню в каждом таком месте становится непрактичным и трудоемким.

Давайте подробно рассмотрим, как эффективно и централизованно реализовать проверки при интерактивной отмене проведения и пометке на удаление документа, используя стандартные механизмы платформы 1С.

Где ловить события: обработчики модуля объекта

Наиболее надежный и рекомендуемый способ для реализации проверок, которые должны срабатывать независимо от того, как было инициировано действие (интерактивно или программно), — это размещение логики в обработчиках событий модуля объекта документа. Модуль объекта — это "сердце" документа, его логика срабатывает всегда, когда происходят изменения самого объекта базы данных.

Платформа 1С предоставляет специальные обработчики событий, которые вызываются при определенных операциях с документом. Эти обработчики обеспечивают централизованный контроль над поведением документа, позволяя нам единообразно применять бизнес-правила ко всем операциям с ним.

Перехват отмены проведения документа

Для того чтобы перехватить попытку отмены проведения документа, мы используем событие ОбработкаОтменыПроведения(), которое располагается в модуле объекта документа. Этот обработчик вызывается платформой непосредственно перед тем, как документ будет отменен. Он предоставляет нам возможность выполнить необходимые проверки и, при необходимости, запретить отмену проведения.

Рассмотрим подробнее, как работает этот обработчик. Когда пользователь (или программный код) пытается отменить проведение документа, платформа сначала вызывает процедуру ОбработкаОтменыПроведения(). В эту процедуру передается параметр Отказ, который по умолчанию имеет значение Ложь. Если в процессе наших проверок мы обнаруживаем условие, которое запрещает отмену проведения, мы устанавливаем Отказ = Истина. В этом случае платформа не выполняет отмену проведения документа и, как правило, выводит стандартное сообщение об ошибке, если мы не предоставим свое собственное.

Предположим, наша задача — не дать отменить проведение документа, если по нему существуют какие-либо "резервы". Резервы могут храниться в одном из регистров накопления или других справочных данных, связанных с этим документом. Мы должны выполнить запрос к этим данным и, если резервы обнаружены, запретить отмену.

Давайте посмотрим на пример кода, который можно разместить в модуле объекта документа:


// В модуле объекта документа "РеализацияТоваровУслуг" (или любом другом)
Процедура ОбработкаОтменыПроведения(Отказ)

    // Шаг 1: Инициализируем флаг наличия резервов
    ЕстьРезервы = Ложь;

    // Шаг 2: Выполняем проверку наличия резервов.
    // Это может быть запрос к регистру накопления или другой таблице.
    // Для примера, представим, что у нас есть функция, которая проверяет это.
    // Обычно здесь выполняется запрос к базе данных.
    //
    // Пример запроса к виртуальной таблице регистра накопления "РезервыТоваров"
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   РезервыТоваровОстатки.КоличествоОстаток
        |ИЗ
        |   РегистрНакопления.РезервыТоваров.Остатки(
        |       ,
        |       ДокументРезервирования = &ДокументСсылка
        |   ) КАК РезервыТоваровОстатки
        |ГДЕ
        |   РезервыТоваровОстатки.КоличествоОстаток > 0";
    
    Запрос.УстановитьПараметр("ДокументСсылка", Ссылка); // Ссылка на текущий документ

    РезультатЗапроса = Запрос.Выполнить();

    Если Не РезультатЗапроса.Пустой() Тогда
        Выборка = РезультатЗапроса.Выбрать();
        Если Выборка.Следующий() Тогда
            Если Выборка.КоличествоОстаток > 0 Тогда
                ЕстьРезервы = Истина;
            КонецЕсли;
        КонецЕсли;
    КонецЕсли;

    // Шаг 3: Если резервы найдены, запрещаем отмену проведения
    Если ЕстьРезервы Тогда
        Отказ = Истина; // Запрещаем отмену проведения
        Сообщение = Новый СообщениеПользователю;
        Сообщение.Текст = "Отмена проведения документа невозможна: по документу существуют активные резервы.";
        Сообщение.Поле = "Ссылка"; // Можно указать поле, к которому относится сообщение
        Сообщение.ВидСообщения = ВидСообщенияПользователю.Ошибка;
        Сообщение.Сообщить();
    КонецЕсли;

КонецПроцедуры

Таким образом, мы гарантируем, что наша бизнес-логика будет соблюдена, независимо от того, откуда пользователь пытается отменить проведение документа. Это централизованное и надежное решение.

Перехват пометки на удаление документа

Для перехвата события пометки документа на удаление мы будем использовать обработчик ПередЗаписью(), который также находится в модуле объекта документа. Этот обработчик вызывается каждый раз, когда документ записывается в базу данных, будь то создание нового документа, изменение уже существующего или, в нашем случае, установка флага пометки на удаление.

Внутри процедуры ПередЗаписью() мы можем проверить значение свойства ПометкаУдаления текущего объекта документа. Если это свойство установлено в Истина, это означает, что пользователь (или программный код) пытается пометить документ на удаление. Как и в случае с отменой проведения, у нас есть параметр Отказ, с помощью которого мы можем предотвратить запись документа с установленным флагом удаления.

Важно понимать различие между ПометкаУдаления и фактическим удалением. ПометкаУдаления лишь обозначает объект как подлежащий удалению, но сам объект остается в базе данных. Физическое удаление объектов происходит позже, обычно при выполнении регламентной операции "Удаление помеченных объектов" или интерактивно через соответствующую обработку. Событие ОбработкаУдаления() вызывается уже непосредственно перед физическим удалением объекта из базы данных, а не при его пометке на удаление. Поэтому для перехвата именно *пометки* на удаление мы используем ПередЗаписью().

Представим, что мы хотим запретить помечать на удаление проведенные документы, если они не были предварительно отменены, или если по ним имеются неоплаченные счета.

Пример кода для модуля объекта документа:


// В модуле объекта документа
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)

    // Шаг 1: Проверяем, если документ помечен на удаление
    Если ЭтотОбъект.ПометкаУдаления Тогда

        // Шаг 2: Выполняем наши проверки.
        // Например, проверим, является ли документ проведенным.
        Если ЭтотОбъект.Проведен Тогда
            Отказ = Истина; // Запрещаем пометку на удаление
            Сообщение = Новый СообщениеПользователю;
            Сообщение.Текст = "Невозможно пометить на удаление проведенный документ. Сначала отмените его проведение.";
            Сообщение.Поле = "ПометкаУдаления";
            Сообщение.ВидСообщения = ВидСообщенияПользователю.Ошибка;
            Сообщение.Сообщить();
            Возврат;
        КонецЕсли;
        
        // Дополнительная проверка, например, на наличие связанных данных
        // Представим, что есть функция, которая проверяет наличие неоплаченных счетов
        Если ЕстьНеоплаченныеСчетаПоДокументу(ЭтотОбъект.Ссылка) Тогда
            Отказ = Истина;
            Сообщение = Новый СообщениеПользователю;
            Сообщение.Текст = "Документ имеет неоплаченные счета и не может быть помечен на удаление.";
            Сообщение.Поле = "ПометкаУдаления";
            Сообщение.ВидСообщения = ВидСообщенияПользователю.Ошибка;
            Сообщение.Сообщить();
            Возврат;
        КонецЕсли;

    КонецЕсли;

КонецПроцедуры

// Пример вспомогательной функции (может быть в общем модуле или модуле менеджера)
Функция ЕстьНеоплаченныеСчетаПоДокументу(СсылкаДокумента)
    // Здесь должна быть логика запроса к данным
    // Например, к регистру "Взаиморасчеты" или "ОплатыСчетов"
    // Заглушка для примера:
    Возврат Ложь; // Предположим, что счетов нет для демонстрации
КонецФункции

Размещая такую логику в обработчике ПередЗаписью(), мы обеспечиваем надежный контроль над процессом пометки документов на удаление, предотвращая ошибочные или несанкционированные действия.

Отличие интерактивного действия от программного

Один из вопросов, который часто возникает в таких сценариях, — это как различить, было ли действие (отмена проведения, пометка на удаление) инициировано интерактивно пользователем или программно. Платформа 1С не предоставляет прямого встроенного механизма для определения источника вызова (интерактивный или программный) непосредственно в обработчиках модуля объекта.

Однако существуют косвенные методы, которые мы можем использовать для такого различения. Важно отметить, что эти методы не являются абсолютно надежными в 100% случаев, но в большинстве практических сценариев они работают.

  1. Передача дополнительного параметра при программном вызове: Этот метод считается наиболее надежным. Если вы программно отменяете проведение или записываете документ с пометкой на удаление, вы можете передать дополнительный параметр в процедуру записи/отмены проведения. В обработчике модуля объекта вы будете проверять наличие этого параметра. Если параметр отсутствует, мы можем предположить, что действие было интерактивным.

    Для этого в обработчиках ПередЗаписью() и ОбработкаОтменыПроведения() нам доступны параметры ДополнительныеСвойства (для ПередЗаписью, начиная с версии платформы 8.3.10) или можно использовать свойства ГлобальногоКонтекста (для универсальных модулей) или напрямую передавать параметры через общие методы.

    Например, при программном вызове мы можем сделать так:

    
    // Программный вызов отмены проведения
    ДокументОбъект = СсылкаНаДокумент.ПолучитьОбъект();
    Если ДокументОбъект.Проведен Тогда
        ДокументОбъект.ДополнительныеСвойства.Вставить("ПрограммныйВызов", Истина); // Для ПередЗаписью
        // Или использовать временное хранилище или глобальный параметр для ОбработкаОтменыПроведения
        ДокументОбъект.ОтменитьПроведение();
        ДокументОбъект.Записать();
    КонецЕсли;
    

    А в обработчике модуля объекта:

    
    Процедура ОбработкаОтменыПроведения(Отказ)
        Если МодульОбщиеКлиентСервер.ПараметрПрограммногоВызова("ОтменаПроведения") Тогда
            // Это программный вызов, возможно, не нужны проверки
            Возврат;
        КонецЕсли;
        // Далее обычные проверки для интерактивного вызова
        // ...
    КонецПроцедуры
    
    Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
        // Для ПередЗаписью можно использовать ДополнительныеСвойства
        Если ЭтотОбъект.ДополнительныеСвойства.Свойство("ПрограммныйВызов") И ЭтотОбъект.ДополнительныеСвойства.ПрограммныйВызов Тогда
            // Это программный вызов, возможно, не нужны проверки
            Возврат;
        КонецЕсли;
        // Далее обычные проверки для интерактивного вызова
        // ...
    КонецПроцедуры
    

    Такой подход требует дисциплины при разработке: каждый программный вызов должен явно передавать соответствующий флаг.

  2. Анализ наличия формы: Этот метод менее надежен. В некоторых случаях можно попытаться определить наличие открытых форм, используя объект ЭтаФорма. Если действие инициировано из формы, ЭтаФорма будет доступна. Однако серверный контекст не всегда имеет доступ к ЭтаФорма, и действие может быть инициировано из серверного кода, который сам был вызван с клиентской формы. Это создает сложности и делает метод ненадежным. Крайне не рекомендуем использовать этот подход для критической бизнес-логики.

В большинстве случаев для проверок бизнес-логики не так важно, интерактивно ли было действие. Важно, чтобы проверка срабатывала всегда, предотвращая нарушение целостности данных. Если же есть специфические требования к различению, то передача параметра является предпочтительным вариантом.

Централизованная проверка и управление доступом

Использование обработчиков событий модуля объекта документа для всех проверок — это мощный инструмент для обеспечения целостности данных и единообразного поведения системы. Мы централизуем всю логику проверок в одном месте, что значительно упрощает поддержку и развитие кода. Нам не приходится беспокоиться о том, что где-то в программе кто-то забудет добавить нужную проверку.

В дополнение к программным проверкам, реализованным в модулях объектов, мы можем использовать систему ролей и прав доступа 1С для более широкого контроля. Например, можно настроить роли таким образом, чтобы только пользователи с определенными правами могли интерактивно отменять проведение или помечать на удаление документы. Это не заменяет проверки бизнес-логики, но добавляет еще один уровень защиты от несанкционированных действий. Мы можем, например, запретить всем, кроме бухгалтера, отменять проведение документов, а уже на уровне бизнес-логики проверять, можно ли отменить проведение конкретного документа.

Предположим, мы хотим ограничить отмену проведения только для пользователей с ролью "ОтменаПроведенияДокументов". Мы можем добавить проверку в начале процедуры:


Процедура ОбработкаОтменыПроведения(Отказ)
    // Проверка прав пользователя
    Если Не РольДоступна("ОтменаПроведенияДокументов") Тогда
        Отказ = Истина;
        Сообщение = Новый СообщениеПользователю;
        Сообщение.Текст = "У вас нет прав на отмену проведения документов.";
        Сообщение.ВидСообщения = ВидСообщенияПользователю.Ошибка;
        Сообщение.Сообщить();
        Возврат;
    КонецЕсли;
    
    // Далее идут основные проверки бизнес-логики
    // ...
КонецПроцедуры

Такой комбинированный подход — программные проверки в модуле объекта и ограничения через права доступа — обеспечивает наиболее гибкую и надежную систему контроля.

Почему Журнал регистрации не подходит для интерактивных проверок

В начале обсуждения на форуме прозвучал вопрос о возможности использования Журнала регистрации. Журнал регистрации действительно фиксирует множество событий в системе 1С, включая отмену проведения и пометку на удаление документов. Он является незаменимым инструментом для анализа действий пользователей постфактум, то есть после того, как событие уже произошло.

Однако Журнал регистрации совершенно не подходит для интерактивных проверок в реальном времени. Во-первых, он содержит огромное количество записей, и постоянное его "шерстить" фоновым заданием для поиска свежих событий — крайне неэффективно и ресурсоемко. Во-вторых, даже если бы мы могли быстро найти запись о событии, к тому моменту действие уже было бы совершено. Журнал регистрации предназначен для аудита и мониторинга, а не для предотвращения действий в момент их совершения. Для предотвращения действий нам нужны именно обработчики событий, которые вызываются *до* выполнения действия.

Заключение

Мы выяснили, что для эффективного перехвата интерактивных (и программных) действий по отмене проведения и пометке на удаление документов в 1С 8.3 наиболее правильным и гибким решением является использование обработчиков событий модуля объекта документа. Методы ОбработкаОтменыПроведения() и ПередЗаписью() с проверкой свойства ПометкаУдаления позволяют нам централизованно внедрять необходимую бизнес-логику и предотвращать нежелательные действия. В случае необходимости различения интерактивного и программного вызова, наиболее надежным подходом является передача дополнительного параметра. Это обеспечивает надежность, целостность данных и удобство сопровождения системы.

← На главную