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