При интеграции различных систем на платформе 1С:Предприятие часто возникает задача сформировать готовый отчет в базе-приемнике и передать его данные обратно в базу-источник. Технология COM-соединения (Automation) остается одним из самых популярных способов решения этой задачи. Однако при попытке сформировать сложный отчет (особенно построенный на системе компоновки данных — СКД) «вслепую», без открытия формы, разработчики неизбежно сталкиваются со специфическими ошибками инициализации.
В этой статье мы подробно разберем, почему возникают ошибки при работе с СКД и интерактивными механизмами конфигураций в режиме внешнего подключения, а также детально рассмотрим пошаговые решения для каждой из них.
---
Главная причина всех ошибок при программном вызове функций отчета из другой базы — отсутствие интерактивного контекста (формы). Когда пользователь формирует отчет в режиме предприятия, платформа автоматически выполняет целый ряд действий:
КомпоновщикНастроек.Результат (который является готовым объектом ТабличныйДокумент) в процедуру генерации.ПриОткрытии), в которых могут заполняться параметры панели настроек.При вызове метода объекта через COM-соединение формы не существует. Все механизмы, которые неявно рассчитывали на наличие элементов интерфейса, начинают вызывать исключения. Проанализируем самые распространенные ошибки.
---
Рассмотрим строку кода, которая вызывает падение в модуле объекта отчета:
Результат.АвтоМасштаб = Истина;
Выясним причину: В экспортную функцию ВнешнееПодключение первым параметром передается переменная Результат со значением по умолчанию НеОпределено. Если вызов происходит неинтерактивно, этот параметр так и остается незаполненным. Попытка установить свойство у объекта, который равен НеОпределено, закономерно приводит к аварийному завершению работы кода.
Решение проблемы: Перед тем как вызывать внутренние процедуры формирования макета, необходимо принудительно инициализировать табличный документ, если он не был передан извне. Реализуем эту проверку в коде функции:
Если Результат = НеОпределено Тогда
Результат = Новый ТабличныйДокумент;
КонецЕсли;
---
Эта ошибка обычно возникает на следующей строке кода:
КомпоновщикНастроек.Настройки.Отбор.Элементы[0].ПравоеЗначение = ВнОрганизация;
Выясним причину: Попытка обратиться к первому элементу отбора по индексу [0] крайне небезопасна. При открытии формы отбора структура настроек компоновщика может заполняться программно или интерактивно пользователем. В режиме внешнего соединения коллекция Элементы отбора оказывается пустой, что вызывает ошибку выхода индекса за границы массива.
Решение проблемы: Нам необходимо отказаться от обращений по жесткому индексу. Вместо этого мы должны обходить коллекцию элементов отбора в цикле, искать нужный параметр по полю компоновки, а если он отсутствует — программно добавлять его. Рассмотрим пример безопасной функции для установки отбора СКД:
ПолеОрганизация = Новый ПолеКомпоновкиДанных("Организация");
ЭлементОтбора = НеОпределено;
// Ищем уже существующий отбор по Организации
Для Каждого Элемент Из КомпоновщикНастроек.Настройки.Отбор.Элементы Цикл
Если Элемент.ЛевоеЗначение = ПолеОрганизация Тогда
ЭлементОтбора = Элемент;
Прервать;
КонецЕсли;
КонецЦикла;
// Если отбор не найден в структуре, создадим его программно
Если ЭлементОтбора = НеОпределено Тогда
ЭлементОтбора = КомпоновщикНастроек.Настройки.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементОтбора.ЛевоеЗначение = ПолеОрганизация;
КонецЕсли;
// Устанавливаем параметры и активируем использование отбора
ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
ЭлементОтбора.ПравоеЗначение = ВнОрганизация;
ЭлементОтбора.Использование = Истина;
---
Данная ошибка характерна для конфигураций, использующих сложные механизмы динамического изменения запросов (например, в УПП или ЗУП 2.5 при вызове общего модуля УправлениеОтчетамиЗК.ЗаменитьВСКДТекстЗапросКалендаря).
Выясним причину: Метод динамической подмены запроса календаря парсит исходный текст СКД и подставляет в него даты начала и окончания периода, конструируя строку вида ДАТАВРЕМЯ(Год, Месяц, День). Если даты, переданные через COM-соединение, не определены, имеют пустые значения или некорректно преобразовались (COM-объекты иногда теряют точность типов при маршалинге), строковый парсер возвращает пустоту, что ломает синтаксис SQL-запроса на выходе.
Решение проблемы: Нам необходимо жестко типизировать и гарантировать заполнение дат еще на входе в экспортируемую процедуру. Если передан пустой период, программа должна подставить дефолтные значения (например, начало и конец текущего месяца):
НачалоПериода = ?(ЗначениеЗаполнено(ВнНачПер), Дата(ВнНачПер), НачалоМесяца(ТекущаяДата()));
КонецПериода = ?(ЗначениеЗаполнено(ВнКонПер), Дата(ВнКонПер), КонецМесяца(ТекущаяДата()));
Также важно убедиться, что из базы-источника даты передаются в формате Дата, а не как COM-объекты сторонних типов.
---
Типовые отчеты старых конфигураций часто перегружены вызовами методов общего модуля ТиповыеОтчеты. Эти методы обращаются к кэшу пользовательских настроек формы. При работе через COM они вызывают исключения.
Для предотвращения выполнения интерактивного кода используем проверку системного свойства ВнешнееСоединение. Мы можем обернуть опасные блоки в директивы или условия:
#Если НЕ ВнешнееСоединение Тогда
// Код, который должен работать только в интерактивном режиме
ТиповыеОтчеты.ПолучитьПримененуюНастройку(ЭтотОбъект);
ЗначениеПанелипользователя = ТиповыеОтчеты.ПолучитьЗначенияНастроекПанелиПользователяОбъекта(ЭтотОбъект);
#КонецЕсли
Если конфигурация не поддерживает директивы компиляции в нужном контексте, используйте проверку имени программы:
Если ИмяПрограммы() <> "ExternalConnection" Тогда
// Логика только для обычного сеанса предприятия
КонецЕсли;
---
Посмотрим на практический пример того, как должен выглядеть готовый шаблон интеграции.
// Устанавливаем подключение к базе-приемнику
ПараметрыСоединения = "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. Это полностью решает проблемы несовместимости интерфейсного кода и позволяет производить обмен мгновенно.