Как задать вопрос пользователю при создании документа в 1С: обход ограничений асинхронности

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

При переходе на управляемые формы и отказе от модальности многие разработчики сталкиваются с трудностями при реализации привычных сценариев. Один из таких сценариев — проверка на существование дубликата документа при его создании на основании и вывод предупреждающего вопроса: «Документ уже существует. Создать новый или нет?».

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

В чем проблема «наивного» решения в лоб?

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

Рассмотрим пример такого неоптимального кода:


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

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

Данный подход содержит критические архитектурные недостатки:

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

Выясним, как сделать эту операцию правильно и элегантно.

---

Решение 1. Проверка в команде создания (Рекомендуемый стандарт 1С)

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

Разберем этот метод по шагам.

Шаг 1. На форме исходного документа (например, ВозвратОтПокупателя) переопределяем команду создания подчиненного документа ОплатаПлатежнойКартой.

Шаг 2. В модуле формы источника пишем следующий код:


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

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

Шаг 3. Пишем обработчик ответа пользователя и процедуру открытия формы:


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

&НаКлиенте
Процедура ОткрытьФормуСозданияНового(Основание)
    
    ПараметрыЗаполнения = Новый Структура("Основание", Основание);
    ПараметрыФормы = Новый Структура("ЗначенияЗаполнения", ПараметрыЗаполнения);
    
    ОткрытьФорму("Документ.ОплатаПлатежнойКартой.ФормаОбъекта", ПараметрыФормы);
    
КонецПроцедуры

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

---

Решение 2. Паттерн «двойного открытия» формы

Что делать, если новый документ может создаваться из самых разных мест системы (из списков, обработок, других документов), и переопределить все команды физически невозможно? В таком случае логику проверки приходится встраивать непосредственно внутрь формы создаваемого документа.

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

Проанализируем этот алгоритм:

Шаг 1. В модуле создаваемой формы (в нашем примере это форма документа ОплатаПлатежнойКартой) определим служебную переменную на клиенте или реквизит формы ДубликатНайден (тип: Булево).

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


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

Шаг 3. В обработчике ПриОткрытии мгновенно гасим открытие формы, если дубликат найден, и выводим асинхронный вопрос:


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

Шаг 4. В обработчике оповещения повторно вызываем форму, но передаем параметр ПроверкаВыполнена = Истина, чтобы обойти проверку при повторном открытии:


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

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

---

Важное замечание об использовании конструкций Асинх / Ждать (платформа 8.3.18+)

Начиная с версии платформы 1С 8.3.18, разработчикам стали доступны современные конструкции для асинхронного программирования — Асинх и Ждать (работа с Обещаниями). На первый взгляд может показаться, что они позволяют решить проблему лаконичнее, без создания процедур обратного вызова.

Однако использовать Ждать внутри стандартного предопределенного события ПриОткрытии для приостановки открытия формы нельзя. Если вы объявите процедуру как асинхронную:


&НаКлиенте
Асинх Процедура ПриОткрытии(Отказ)
    // Код не заблокирует открытие формы!
    Результат = Ждать ВопросАсинх("Создать новый?", РежимДиалогаВопрос.ДаНет);
КонецПроцедуры

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

Резюме

Мы рассмотрели две профессиональные методики обработки асинхронных вопросов при создании документов. Рекомендуется использовать Вариант 1 (проверка в вызывающей команде) как наиболее производительный и соответствующий стандартам разработки фирмы «1С». Вариант 2 (двойное открытие) отлично подойдет в качестве универсального решения, когда создание документа происходит из разных неконтролируемых точек конфигурации.

← На главную