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

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

В процессе разработки бизнес-логики в системе «1С:Предприятие 8» часто возникает задача предупреждения пользователя перед выполнением необратимых или критически важных действий. Одно из таких действий — отмена проведения (распроведение) документа. Ошибочное распроведение может нарушить партионный учет, сбить последовательность документов или привести к расхождениям в отчетах.

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

---

Почему нельзя задавать вопросы в модуле объекта?

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

Выделим основные причины этого ограничения:

  1. Выполнение на сервере: Код модуля объекта чаще всего выполняется на сервере. На сервере физически отсутствует контекст отображения интерфейса. Вызов любого окна (даже через директиву #Если Клиент) в серверном вызове приведет к аварийному завершению работы системы.
  2. Фоновые задания и обмены данными: Запись и проведение документов могут инициироваться регламентными заданиями, веб-сервисами или механизмами синхронизации данных. В этих сценариях нет активного пользователя, который мог бы ответить на вопрос, что приведет к зависанию или ошибке транзакции.
  3. Нарушение стандартов разработки: Фирма «1С» строго контролирует разделение логики интерфейса и бизнес-логики базы данных. Любое интерактивное взаимодействие должно происходить исключительно на уровне форм.

Выясним, как решать эту задачу правильно на уровне форм документов.

---

Решение для управляемых форм (УФ): Паттерн «Повторная запись с флагом»

Основная сложность разработки в управляемом приложении заключается в том, что платформа функционирует в немодальном режиме. Стандартное событие формы ПередЗаписью выполняется синхронно. Если мы попытаемся использовать асинхронную функцию (например, ПоказатьВопрос или ВопросАсинх), платформа не будет ждать ответа пользователя — она мгновенно продолжит выполнение кода, и документ будет распроведен до того, как пользователь успеет нажать кнопку в диалоговом окне.

Чтобы обойти это ограничение, применим классический паттерн «Повторная запись с флагом». Разберем его по шагам:

  1. В модуле формы объявляем клиентскую переменную-флаг, которая по умолчанию равна Ложь.
  2. В обработчике ПередЗаписью на клиенте проверяем режим записи. Если выполняется отмена проведения и наш флаг равен Ложь, мы принудительно устанавливаем Отказ = Истина, прерывая стандартный процесс записи.
  3. Параллельно запускаем асинхронный диалог с вопросом.
  4. Если пользователь отвечает «Да», мы меняем значение флага на Истина и программно вызываем метод Записать(), передавая те же параметры. В этот раз процедура ПередЗаписью увидит, что флаг установлен в значение Истина, пропустит проверку и позволит документу распровестись.
  5. После успешной записи сбрасываем флаг обратно в Ложь для последующих действий.

Посмотрим на пример реализации этого паттерна на современной платформе (версии 8.3.18 и выше) с использованием ключевого слова Ждать:


&НаКлиенте
Перем ВопросЗадан;

&НаКлиенте
Асинх Процедура ПередЗаписью(Отказ, ПараметрыЗаписи)
    
    // Проверяем, что инициирована именно отмена проведения и вопрос еще не задавался
    Если ПараметрыЗаписи.РежимЗаписи = РежимЗаписиДокумента.Form_UndoPosting // Для УФ: РежимЗаписиДокумента.ОтменаПроведения
       И Не ВопросЗадан Тогда
        
        // Прерываем текущую синхронную транзакцию записи формы
        Отказ = Истина; 
        
        // Задаем вопрос асинхронно
        Ответ = Ждать ВопросАсинх("Вы точно уверены, что хотите отменить проведение этого документа?", РежимДиалогаВопрос.ДаНет);
        
        Если Ответ = КодВозвратаДиалога.Да Тогда
            // Устанавливаем флаг в Истина, чтобы при повторном входе не уйти в бесконечный цикл
            ВопросЗадан = Истина; 
            
            // Повторно вызываем запись с исходными параметрами
            Записать(ПараметрыЗаписи); 
            
            // Сбрасываем флаг после завершения процесса
            ВопросЗадан = Ложь; 
        КонецЕсли;
        
    КонецЕсли;
    
КонецПроцедуры

Если ваша конфигурация работает на более старых версиях платформы 1С (до 8.3.18), где использование ключевого слова Ждать недоступно, логику необходимо реализовать через механизм описания оповещения:


&НаКлиенте
Перем ВопросЗадан;

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

&НаКлиенте
Процедура ПослеВопросаОбОтменеПроведения(Результат, ДополнительныеПараметры) Экспорт
    
    Если Результат = КодВозвратаДиалога.Да Тогда
        ВопросЗадан = Истина;
        Записать(ДополнительныеПараметры);
        ВопросЗадан = Ложь;
    КонецЕсли;
    
КонецПроцедуры

---

Решение для обычных форм (ОФ)

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

Проанализируем пример кода для модуля обычной формы документа:


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

---

Как перехватить отмену проведения из динамического списка

Описанные выше методы отлично работают, если пользователь открыл форму самого документа и нажал кнопку «Отмена проведения». Но на практике пользователи часто выполняют это действие напрямую из формы списка документов (через контекстное меню, панель инструментов или горячую клавишу Del).

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

Рассмотрим два основных способа решения этой проблемы.

Способ 1. Переопределение стандартных команд списка

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

  1. В свойствах командной панели формы списка отключим свойство Автозаполнение.
  2. Вручную добавим все необходимые кнопки, кроме стандартной кнопки «Отменить проведение».
  3. Добавим собственную команду, например, КомандаОтменитьПроведение, и выведем её на панель и в контекстное меню.
  4. В обработчике этой команды программно определим состав выделенных строк и зададим один общий вопрос. Это убережет пользователя от необходимости отвечать на вопросы по каждому документу отдельно при массовом выделении.

Посмотрим на пример реализации такой команды в модуле формы списка:


&НаКлиенте
Асинх Процедура КомандаОтменитьПроведение(Команда)
    
    ВыделенныеСтроки = Элементы.Список.ВыделенныеСтроки;
    КоличествоДокументов = ВыделенныеСтроки.Количество();
    
    Если КоличествоДокументов = 0 Тогда
        ПоказатьПредупреждение(, "Не выбрано ни одного документа!");
        Возврат;
    КонецЕсли;
    
    // Формируем понятный текст вопроса в зависимости от количества документов
    ШаблонТекста = НСтр("ru = 'Вы действительно хотите отменить проведение для %1 выделенных документов?'");
    ТекстВопроса = СтроковыеШаблоны.Шаблон(ШаблонТекста, КоличествоДокументов);
    
    Ответ = Ждать ВопросАсинх(ТекстВопроса, РежимДиалогаВопрос.ДаНет);
    
    Если Ответ = КодВозвратаДиалога.Да Тогда
        // Вызываем серверный метод для безопасной групповой отмены проведения
        ОтменитьПроведениеДокументовНаСервере(ВыделенныеСтроки);
        Элементы.Список.Обновить();
    КонецЕсли;
    
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ОтменитьПроведениеДокументовНаСервере(Знач МассивДокументов)
    
    Для Каждого Ссылка Из МассивДокументов Цикл
        // Проверяем права текущего пользователя на отмену проведения данного документа
        Если ПравоДоступа("ИнтерактивнаяОтменаПроведения", Ссылка.Метаданные()) Тогда
            ОбъектДокумента = Ссылка.ПолучитьОбъект();
            Если ОбъектДокумента.Проведен Тогда
                Попытка
                    ОбъектДокумента.Записать(РежимЗаписиДокумента.ОтменаПроведения);
                Исключение
                    // Записываем ошибку в Журнал регистрации или выводим сообщение пользователю
                    ЗаписьЖурналаРегистрации("Отмена проведения", УровеньЖурналаРегистрации.Ошибка, 
                        Ссылка.Метаданные(), Ссылка, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
                КонецПопытки;
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
    
КонецПроцедуры

Способ 2. Административный запрет через роли (права доступа)

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

Для этого в ролях основных пользователей можно снять галочку «Интерактивная отмена проведения» (Interactive undo posting) для требуемых типов документов.

В результате:

---

Жесткий контроль бизнес-логики в модуле объекта

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

В этом случае вместо интерактивного вопроса система должна вызывать жесткое исключение с понятным описанием причины отказа:


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

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

← На главную