Формирование внешнего отчета в другой базе через COM-соединение: разбор ошибок и пошаговое руководство

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

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

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

---

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

Главная причина всех ошибок при программном вызове функций отчета из другой базы — отсутствие интерактивного контекста (формы). Когда пользователь формирует отчет в режиме предприятия, платформа автоматически выполняет целый ряд действий:

  1. Инициализирует форму отчета.
  2. Заполняет настройки по умолчанию в КомпоновщикНастроек.
  3. Передает элемент формы Результат (который является готовым объектом ТабличныйДокумент) в процедуру генерации.
  4. Выполняет клиентские обработчики событий (например, ПриОткрытии), в которых могут заполняться параметры панели настроек.

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

---

Ошибка 1: Значение не является значением объектного типа (АвтоМасштаб)

Рассмотрим строку кода, которая вызывает падение в модуле объекта отчета:

Результат.АвтоМасштаб = Истина;

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

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


Если Результат = НеОпределено Тогда
    Результат = Новый ТабличныйДокумент;
КонецЕсли;

---

Ошибка 2: Индекс находится за границами массива при установке отборов

Эта ошибка обычно возникает на следующей строке кода:

КомпоновщикНастроек.Настройки.Отбор.Элементы[0].ПравоеЗначение = ВнОрганизация;

Выясним причину: Попытка обратиться к первому элементу отбора по индексу [0] крайне небезопасна. При открытии формы отбора структура настроек компоновщика может заполняться программно или интерактивно пользователем. В режиме внешнего соединения коллекция Элементы отбора оказывается пустой, что вызывает ошибку выхода индекса за границы массива.

Решение проблемы: Нам необходимо отказаться от обращений по жесткому индексу. Вместо этого мы должны обходить коллекцию элементов отбора в цикле, искать нужный параметр по полю компоновки, а если он отсутствует — программно добавлять его. Рассмотрим пример безопасной функции для установки отбора СКД:


ПолеОрганизация = Новый ПолеКомпоновкиДанных("Организация");
ЭлементОтбора = НеОпределено;

// Ищем уже существующий отбор по Организации
Для Каждого Элемент Из КомпоновщикНастроек.Настройки.Отбор.Элементы Цикл
    Если Элемент.ЛевоеЗначение = ПолеОрганизация Тогда
        ЭлементОтбора = Элемент;
        Прервать;
    КонецЕсли;
КонецЦикла;

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

// Устанавливаем параметры и активируем использование отбора
ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
ЭлементОтбора.ПравоеЗначение = ВнОрганизация;
ЭлементОтбора.Использование = Истина;

---

Ошибка 3: Ошибка компоновки макета — ДАТАВРЕМЯ(<<?>>, 1, 1)

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

Выясним причину: Метод динамической подмены запроса календаря парсит исходный текст СКД и подставляет в него даты начала и окончания периода, конструируя строку вида ДАТАВРЕМЯ(Год, Месяц, День). Если даты, переданные через COM-соединение, не определены, имеют пустые значения или некорректно преобразовались (COM-объекты иногда теряют точность типов при маршалинге), строковый парсер возвращает пустоту, что ломает синтаксис SQL-запроса на выходе.

Решение проблемы: Нам необходимо жестко типизировать и гарантировать заполнение дат еще на входе в экспортируемую процедуру. Если передан пустой период, программа должна подставить дефолтные значения (например, начало и конец текущего месяца):


НачалоПериода = ?(ЗначениеЗаполнено(ВнНачПер), Дата(ВнНачПер), НачалоМесяца(ТекущаяДата()));
КонецПериода  = ?(ЗначениеЗаполнено(ВнКонПер), Дата(ВнКонПер), КонецМесяца(ТекущаяДата()));

Также важно убедиться, что из базы-источника даты передаются в формате Дата, а не как COM-объекты сторонних типов.

---

Обход интерактивных вызовов библиотеки «ТиповыеОтчеты»

Типовые отчеты старых конфигураций часто перегружены вызовами методов общего модуля ТиповыеОтчеты. Эти методы обращаются к кэшу пользовательских настроек формы. При работе через COM они вызывают исключения.

Для предотвращения выполнения интерактивного кода используем проверку системного свойства ВнешнееСоединение. Мы можем обернуть опасные блоки в директивы или условия:


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

Если конфигурация не поддерживает директивы компиляции в нужном контексте, используйте проверку имени программы:


Если ИмяПрограммы() <> "ExternalConnection" Тогда
    // Логика только для обычного сеанса предприятия
КонецЕсли;

---

Полный пример реализации обмена

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

Код в базе-источнике (вызов внешнего отчета через COM):


// Устанавливаем подключение к базе-приемнику
ПараметрыСоединения = "Srvr=""Server1C"";Ref=""TargetBase"";Usr=""Администратор"";Pwd=""Password"";";
Connector = Новый COMОбъект("V83.COMConnector");
Соединение = Connector.Connect(ПараметрыСоединения);

// Получаем внешнюю обработку/отчет из справочника приемника
ВнешняяФорма = Соединение.Справочники.ВнешниеОбработки.НайтиПоНаименованию("Выгрузка авансов в Зарплату");

// Выгружаем двоичные данные во временный файл в системе приемника
ИмяФайла = Соединение.ПолучитьИмяВременногоФайла();
ДвоичныеДанные = ВнешняяФорма.ХранилищеВнешнейОбработки.Получить();
ДвоичныеДанные.Записать(ИмяФайла);

// Создаем объект внешнего отчета в базе-приемнике
ОтчетОбъект = Соединение.ВнешниеОтчеты.Создать(ИмяФайла, Ложь);

// Подготавливаем параметры для передачи (только примитивные типы!)
ОрганизацияНаименование = Отчет.Организация.Наименование;
ДатаНачала = Отчет.НачалоПериода;
ДатаОкончания = Отчет.КонецПериода;

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

Код в базе-приемнике (модуль объекта внешнего отчета):


Функция ВнешнееПодключение(ВнНачПер = НеОпределено, ВнКонПер = НеОпределено, ВнОрганизацияИмя = НеОпределено) Экспорт
    
    // 1. Поиск организации по наименованию
    СсылкаОрганизация = Справочники.Организации.НайтиПоНаименованию(ВнОрганизацияИмя);
    Если СсылкаОрганизация.Пустая() Тогда
        ВызватьИсключение "Организация с наименованием '" + ВнОрганизацияИмя + "' не найдена в базе-приемнике.";
    КонецЕсли;
    
    // 2. Инициализация и нормализация дат
    НачалоПериода = ?(ЗначениеЗаполнено(ВнНачПер), Дата(ВнНачПер), НачалоМесяца(ТекущаяДата()));
    КонецПериода  = ?(ЗначениеЗаполнено(ВнКонПер), Дата(ВнКонПер), КонецМесяца(ТекущаяДата()));
    
    // 3. Безопасная инициализация табличного документа
    ТабличныйДок = Новый ТабличныйДокумент;
    
    // 4. Программная настройка компоновщика СКД
    ПолеОрганизация = Новый ПолеКомпоновкиДанных("Организация");
    ЭлементОтбора = НеОпределено;
    Для Каждого Элемент Из КомпоновщикНастроек.Настройки.Отбор.Элементы Цикл
        Если Элемент.ЛевоеЗначение = ПолеОрганизация Тогда
            ЭлементОтбора = Элемент;
            Прервать;
        КонецЕсли;
    КонецЦикла;
    
    Если ЭлементОтбора = НеОпределено Тогда
        ЭлементОтбора = КомпоновщикНастроек.Настройки.Отбор.Элементы.Добавить(Type("ЭлементОтбораКомпоновкиДанных"));
        ЭлементОтбора.ЛевоеЗначение = ПолеОрганизация;
    КонецЕсли;
    
    ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
    ЭлементОтбора.ПравоеЗначение = СсылкаОрганизация;
    ЭлементОтбора.Использование = Истина;
    
    // Установка параметров СКД
    ПараметрНачало = КомпоновщикНастроек.Настройки.ПараметрыДанных.Элементы.Найти("НачалоПериода");
    Если ПараметрНачало <> НеОпределено Тогда
        ПараметрНачало.Значение = НачалоПериода;
        ПараметрНачало.Использование = Истина;
    КонецЕсли;
    
    ПараметрКонец = КомпоновщикНастроек.Настройки.ПараметрыДанных.Элементы.Найти("КонецПериода");
    Если ПараметрКонец <> НеОпределено Тогда
        ПараметрКонец.Значение = КонецПериода;
        ПараметрКонец.Использование = Истина;
    КонецЕсли;

    // 5. Вызов функции формирования отчета (без интерактивных вызовов)
    ТабличныйДок = СформироватьОтчетДляCOM(ТабличныйДок, НачалоПериода, КонецПериода, СсылкаОрганизация);
    
    // 6. Преобразование табличного документа в ТаблицуЗначений для возврата через COM
    ТаблицаРезультат = ТабличныйДокументВТаблицуЗначений(ТабличныйДок);
    
    Возврат ТаблицаРезультат;
КонецФункции

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

---

Альтернативные архитектурные решения

Хотя технология COM-соединения является классической, в современных реалиях она имеет ряд существенных недостатков:

Если вы хотите построить по-настоящему отказоустойчивую архитектуру, рассмотрите возможность перехода на HTTP-сервисы. В базе-приемнике создается простой HTTP-шаблон, который принимает GET-запрос с параметрами периода и организации. Внутри сервиса выполняется та же логика получения данных СКД, после чего таблица сериализуется в JSON или XML и отправляется обратно по протоколу HTTP. Это полностью решает проблемы несовместимости интерфейсного кода и позволяет производить обмен мгновенно.

← На главную