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