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

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

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

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

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

Почему новые асинхронные методы (Асинх / Ждать) не решают проблему напрямую?

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


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

Выясним причину, почему этот код не будет работать корректно:

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

Таким образом, нам требуются обходные архитектурные решения. Рассмотрим их подробно.

Способ 1. Использование параметров записи (Рекомендуемый стандартный вариант)

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

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

Разберем реализацию этого способа по шагам:

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

Посмотрим на пример программного кода для реализации этой схемы:


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

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

Способ 2. Использование переменной модуля формы

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

Рассмотрим пример реализации этого алгоритма:


&НаКлиенте
Перем ВопросОЗаписиЗадан; // Переменная будет хранить состояние проверки

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

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

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

Способ 3. Официальная рекомендация фирмы «1С» (Замена стандартных команд)

Методологи фирмы «1С» указывают на то, что использование рекурсивных вызовов с отменой записи через Отказ = Истина усложняет чтение программного кода и усложняет отладку программных модулей. Рекомендуемый архитектурный паттерн — полностью отказаться от интерактивных вопросов внутри события ПередЗаписью.

Вместо этого предлагается следующий алгоритм работы:

  1. В свойствах командной панели управляемой формы отключается свойство Автозаполнение.
  2. Разработчик вручную создает собственные команды: «Записать» и «Записать и закрыть» и размещает их на форме.
  3. Для этих команд пишется линейный, простой для понимания асинхронный код с использованием конструкции Асинх / Ждать.

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


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

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

Критически важный нюанс: Ограничения интерактивных проверок при групповой обработке

Проектируя логику работы интерактивных вопросов, любой разработчик 1С должен четко разграничивать клиентский и серверный контексты исполнения кода:

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

← На главную