Как исправить ошибку «РежимДиалогаВопрос» и сбои подписок на события при создании документа через COM-соединение в 1С?

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

Интеграция конфигураций «1С:Предприятие 8» через COM-соединение — это классический инструмент для обмена данными. Однако разработчики часто сталкиваются с неочевидным поведением платформы, когда при вызове метода СоздатьДокумент() через внешнее подключение внезапно возникают ошибки компиляции общих модулей или обработчиков подписок на события.

В этой статье мы подробно разберем, почему возникают ошибки инициализации модулей (например, связанные с переменной РежимДиалогаВопрос), почему «теряются» обработчики подписок на события при записи объектов через COM и как спроектировать код обмена по лучшим практикам разработки 1С.

Почему возникает ошибка компиляции общего модуля во внешнем соединении?

Рассмотрим типичную ситуацию. Вы подключаетесь к базе-приемнику с помощью объекта V83.COMConnector и пытаетесь программно создать документ:

ДокКомандаОЛЕ = МенеджерДокумента.СоздатьДокумент();

В ответ платформа возвращает исключение вида:

«Произошла исключительная ситуация (1C:Enterprise 8.3): Ошибка инициализации модуля: ОбщийМодуль.ОбщегоНазначения.Модуль. Переменная не определена (РежимДиалогаВопрос)»

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

Когда внешнее приложение подключается к информационной базе через COM-соединение, платформа инициализирует сеанс. При этом компилируются все общие модули, у которых в свойствах установлен флаг «Внешнее соединение». Компиляция модуля происходит целиком. Если в коде модуля присутствует хотя бы одна строка с упоминанием элементов клиентского интерфейса (например, Вопрос(), Предупреждение(), системное перечисление РежимДиалогаВопрос, ЭлементыФормы), компилятор выдает ошибку.

Контекст внешнего соединения (в отличие от обычного или тонкого клиента) полностью лишен интерфейсной части. Для него таких объектов и типов данных просто не существует. В результате сбоя компиляции общего модуля рушится весь процесс инициализации или выполнения метода менеджера объекта.

Решение №1: Использование инструкций препроцессора

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

Проанализируем пример функции, вызывающей ошибку:


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

Чтобы этот общий модуль успешно компилировался под внешним соединением, мы должны обернуть весь интерактивный блок в инструкции препроцессора #Если Клиент Тогда:


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

При такой структуре кода препроцессор во время компиляции под внешним соединением просто проигнорирует ветку #Если Клиент, заменив ее безопасным возвратом значения, и компиляция модуля завершится успешно.

Решение №2: Разделение модулей по назначению

Согласно стандартам разработки фирмы «1С» (включая стандарты Библиотеки Стандартных Подсистем — БСП), смешивание клиентского (интерактивного) и серверного кода в одном общем модуле крайне не рекомендуется.

Правильным архитектурным решением будет разделение логики:

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

Ошибка «Метод обработчика события ... не найден» при записи объекта

После решения проблем компиляции общего модуля разработчики часто сталкиваются со следующей проблемой при вызове метода Записать() или Записать(РежимЗаписиДокумента.Проведение):

«При подписке [ИмяПодписки] на событие ПередЗаписью произошла ошибка. Метод обработчика события [ИмяПроцедуры] не найден».

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

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


// ПРАВИЛЬНО: заголовок процедуры доступен везде
Процедура ПроведениеБудущейДатойПередЗаписью(Источник, Отказ, РежимЗаписи, РежимПроведения) Экспорт
    
    // Внутри процедуры можно ограничить выполнение конкретных блоков
    #Если Клиент Тогда
        // Интерактивные предупреждения (хотя в подписках их быть не должно)
    #КонецЕсли
    
    // Основная логика проверки, которая должна работать и при COM-соединении
    Если Источник.Дата > ТекущаяДатаСеанса() Тогда
        // Код проверки...
    КонецЕсли;
    
КонецПроцедуры

Использование механизма «ОбменДанными.Загрузка» — лучшая практика

Когда вы переносите документы или справочники из одной базы в другую через COM, вам, как правило, не нужно, чтобы при записи выполнялись интерактивные проверки, заполнение реквизитов по умолчанию, отправка уведомлений или проверка прав пользователя. Все эти проверки должны выполняться в интерактивном режиме при вводе данных человеком.

Для отключения прикладных проверок при программном импорте данных в платформе 1С предусмотрено специальное свойство объекта — ОбменДанными.Загрузка. Установите его в значение Истина сразу после создания или получения объекта в COM-соединении:


ComПоступление = СоединениеБух.Документы.ПоступлениеТоваровУслуг.СоздатьДокумент();
// Заполнение реквизитов документа...

// Включаем режим загрузки данных
ComПоступление.ОбменДанными.Загрузка = Истина;

// Записываем документ
ComПоступление.Записать(СоединениеБух.РежимЗаписиДокумента.Записать);

Посмотрим, почему этот подход решает большинство проблем:

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

Различие между COMConnector и Application

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

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

Полный пример создания и проведения документа через COM-соединение

Рассмотрим готовый шаблон кода, демонстрирующий правильное создание, заполнение табличной части и проведение документа во внешней базе с обработкой возможных исключений:


// Инициализируем COM-соединение
V83 = Новый COMОбъект("V83.COMConnector");
ПараметрыПодключения = "File=""C:\Base\Бухгалтерия"";Usr=""Администратор"";Pwd=""123"";";

Попытка
    СоединениеБух = V83.Connect(ПараметрыПодключения);
Исключение
    ВызватьИсключение "Не удалось установить связь с базой данных: " + ОписаниеОшибки();
КонецПопытки;

// Создаем новый документ "Поступление товаров и услуг"
ComПоступление = СоединениеБух.Документы.ПоступлениеТоваровУслуг.СоздатьДокумент();

// Заполняем реквизиты шапки через COM-соответствие типов
ComПоступление.Дата = ТекущаяДата();
ComПоступление.Организация = СоединениеБух.Справочники.Организации.НайтиПоКоду("00001");
ComПоступление.Контрагент = СоединениеБух.Справочники.Контрагенты.НайтиПоКоду("КН-00125");
ComПоступление.ДоговорКонтрагента = СоединениеБух.Справочники.ДоговорыКонтрагентов.НайтиПоКоду("ДГ-0041");
ComПоступление.Склад = СоединениеБух.Справочники.Склады.НайтиПоКоду("00000001");

// Добавляем строку в табличную часть "Товары"
НоваяСтрока = ComПоступление.Товары.Добавить();
НоваяСтрока.Номенклатура = СоединениеБух.Справочники.Номенклатура.НайтиПоКоду("НМ-00085");
НоваяСтрока.Количество = 10;
НоваяСтрока.Цена = 1500;
НоваяСтрока.Сумма = 15000;
НоваяСтрока.СтавкаНДС = СоединениеБух.Перечисления.СтавкиНДС.НДС20;

// Применяем свойство загрузки данных, чтобы обойти лишние проверки и UI-ошибки
ComПоступление.ОбменДанными.Загрузка = Истина;

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

Используя приведенные рекомендации, вы сможете проектировать надежные механизмы интеграции программных продуктов на платформе «1С:Предприятие 8», полностью защищенные от непредвиденных ошибок компиляции интерфейсных модулей и сбоев подписок на события.

← На главную