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