Как правильно передать данные из внешнего COM-источника с клиента на сервер в управляемых формах 1С и создать документ

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

Часто перед разработчиком 1С встает задача интеграции с внешними системами. Например, необходимо подключиться к внешней базе данных или ERP-системе (такой как SAP) через COM-соединение, получить таблицу данных и на ее основе создать документы в 1С. В условиях управляемого приложения (УФ) эта тривиальная на первый взгляд задача превращается в архитектурный вызов.

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

--- ### Почему возникает ошибка мутабельности и зачем подключаться на клиенте?

В управляемом приложении клиент и сервер работают в разных адресных пространствах (и часто на разных физических машинах). Объект типа ТаблицаЗначений физически не существует на тонком клиенте — он доступен только на сервере. В свою очередь, COM-объект, созданный на клиенте для связи с внешней системой, не может быть передан на сервер, так как он привязан к памяти и библиотекам конкретного клиентского компьютера.

Перенос подключения к COM-объекту полностью на сторону сервера часто невозможен по следующим причинам:

  1. Разрядность систем: Сервер 1С чаще всего является 64-разрядным (x64), в то время как драйверы внешних систем или COM-компоненты (например, SAP GUI или библиотеки работы с Excel) установлены в 32-разрядном варианте (x86) на рабочих станциях пользователей.
  2. Операционная система: Сервер 1С может функционировать под управлением Linux, где технология COM вообще не поддерживается.
  3. Сетевые ограничения: Клиентская машина может иметь прямой VPN-канал к внешней базе данных, тогда как сервер 1С находится в закрытом и изолированном контуре безопасности.

Поэтому получение данных через COM на клиенте — это частая и оправданная практика. Однако напрямую передавать полученный COM-объект или таблицу значений на сервер нельзя. Ниже мы подробно разберем четыре лучших архитектурных решения этой задачи.

--- ### Вариант 1. Использование массива структур (Универсальный стандарт)

Этот метод считается классическим для типовых конфигураций 1С. Суть его заключается в том, что на клиенте мы обходим строки внешнего COM-объекта, упаковываем данные каждой строки в простую структуру (Структура), добавляем эти структуры в массив (Массив) и передаем этот массив в качестве параметра серверной процедуре. Массивы и структуры без проблем сериализуются платформой и передаются между клиентом и сервером.

Давайте рассмотрим пример реализации этого метода по шагам:


&НаКлиенте
Процедура ЗагрузитьДанныеИзSAP(Команда)
    
    // Инициализируем COM-соединение с SAP
    Попытка
        ОбъектСАП = Новый COMОбъект("SAP.Functions");
    Исключение
        ПоказатьПредупреждение(, "Не удалось загрузить COM-объект SAP!");
        Возврат;
    КонецПопытки;
    
    // Выполняем авторизацию и вызов функции (для примера)
    РезультатВхода = ОбъектСАП.Connection.Logon(0, -1);
    Если РезультатВхода = 0 Тогда
        ПоказатьПредупреждение(, "Неудачный вход в SAP!");
        Возврат;
    КонецЕсли;
    
    ФункцияСАП = ОбъектСАП.Add("ZMM055_AUTOPRIHOD");
    ФункцияСАП.Exports.Item("IM_MBLNR").Value = Объект.НомерПоступления;
    ФункцияСАП.Exports.Item("IM_MJAHR").Value = Объект.ГодПоступления;
    
    Если Не ФункцияСАП.Call() Тогда
        ПоказатьПредупреждение(, "Ошибка вызова функции SAP!");
        ОбъектСАП.Connection.Logoff();
        Возврат;
    КонецЕсли;
    
    РезультатТаблица = ФункцияСАП.Tables("EX_TABLE");
    КолСтрок = РезультатТаблица.RowCount;
    
    // Создаем массив для передачи данных на сервер
    МассивДанных = Новый Массив;
    
    // Обходим строки таблицы COM-объекта и формируем структуры
    Для Стр = 1 По КолСтрок Цикл
        
        ДанныеСтроки = Новый Структура;
        ДанныеСтроки.Вставить("НомерДокумента", РезультатТаблица.Value(Стр, "MBLNR"));
        ДанныеСтроки.Вставить("ГодДокумента",   РезультатТаблица.Value(Стр, "MJAHR"));
        ДанныеСтроки.Вставить("ИННПоставщика",  РезультатТаблица.Value(Стр, "STCD1"));
        ДанныеСтроки.Вставить("НомерМатериала", РезультатТаблица.Value(Стр, "MATNR"));
        ДанныеСтроки.Вставить("Количество",    РезультатТаблица.Value(Стр, "MENGE"));
        ДанныеСтроки.Вставить("Цена",          РезультатТаблица.Value(Стр, "NETPR"));
        
        МассивДанных.Добавить(ДанныеСтроки);
        
    КонецЦикла;
    
    // Отключаемся от COM-объекта
    ОбъектСАП.Connection.Logoff();
    
    // Передаем массив на сервер для создания документов
    СоздатьДокументыНаСервере(МассивДанных);
    
КонецПроцедуры

Теперь рассмотрим, как принять эти данные на сервере, сгруппировать их и создать соответствующие документы:


&НаСервереБезКонтекста
Процедура СоздатьДокументыНаСервере(МассивДанных)
    
    // Для удобства группировки перенесем массив структур в Таблицу Значений
    ТЗДанных = Новый ТаблицаЗначений;
    ТЗДанных.Колонки.Добавить("НомерДокумента", Новый ОписаниеТипов("Строка"));
    ТЗДанных.Колонки.Добавить("ГодДокумента",   Новый ОписаниеТипов("Число"));
    ТЗДанных.Колонки.Добавить("ИННПоставщика",  Новый ОписаниеТипов("Строка"));
    ТЗДанных.Колонки.Добавить("НомерМатериала", Новый ОписаниеТипов("Строка"));
    ТЗДанных.Колонки.Добавить("Количество",    Новый ОписаниеТипов("Число"));
    ТЗДанных.Колонки.Добавить("Цена",          Новый ОписаниеТипов("Число"));
    
    Для Каждого СтруктураСтроки Из МассивДанных Цикл
        ЗаполнитьЗначенияСвойств(ТЗДанных.Добавить(), СтруктураСтроки);
    КонецЦикла;
    
    // Создаем копию таблицы для сворачивания (группировки по шапке документа)
    ТаблицаШапок = ТЗДанных.Скопировать();
    ТаблицаШапок.Свернуть("НомерДокумента, ГодДокумента, ИННПоставщика");
    
    // Обходим уникальные шапки документов
    Для Каждого СтрокаШапки Из ТаблицаШапок Цикл
        
        // Создаем новый документ 1С
        НовыйДокумент = Документы.ПоступлениеТоваровУслуг.СоздатьДокумент();
        НовыйДокумент.Дата = ТекущаяДата();
        НовыйДокумент.НомерВходящегоДокумента = СтрокаШапки.НомерДокумента;
        
        // Ищем контрагента по ИНН
        Поставщик = Справочники.Контрагенты.НайтиПоРеквизиту("ИНН", СтрокаШапки.ИННПоставщика);
        Если Не Поставщик.Пустая() Тогда
            НовыйДокумент.Контрагент = Поставщик;
        КонецЕсли;
        
        // Отбираем строки спецификации для текущего документа
        Отбор = Новый Структура;
        Отбор.Вставить("НомерДокумента", СтрокаШапки.НомерДокумента);
        Отбор.Вставить("ГодДокумента",   СтрокаШапки.ГодДокумента);
        
        НайденныеСтроки = ТЗДанных.НайтиСтроки(Отбор);
        
        Для Каждого СтрокаТЧ Из НайденныеСтроки Цикл
            
            НоваяСтрока = НовыйДокумент.Товары.Добавить();
            НоваяСтрока.Количество = СтрокаТЧ.Количество;
            НоваяСтрока.Цена = СтрокаТЧ.Цена;
            
            // Находим номенклатуру по коду из внешней системы
            НоменклатураСсылка = Справочники.Номенклатура.НайтиПоКоду(СтрокаТЧ.НомерМатериала);
            Если Не НоменклатураСсылка.Пустая() Тогда
                НоваяСтрока.Номенклатура = НоменклатураСсылка;
            КонецЕсли;
            
        КонецЦикла;
        
        // Записываем документ
        Попытка
            НовыйДокумент.Записать(РежимЗаписиДокумента.Запись);
        Исключение
            ЗаписьЖурналаРегистрации("Загрузка SAP", УровеньЖурналаРегистрации.Ошибка,,, 
                ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
        КонецПопытки;
        
    КонецЦикла;
    
КонецПроцедуры

--- ### Вариант 2. Использование реквизита формы (ДанныеФормыКоллекция)

Если вы разрабатываете внешнюю обработку и вам визуально требуется отображать загружаемые строки пользователю на форме, то идеальным решением будет использование реквизита формы с типом ТаблицаЗначений. На клиенте этот реквизит имеет тип ДанныеФормыКоллекция. Мы можем заполнять её прямо на клиенте, и при вызове сервера эти данные автоматически станут доступны без необходимости ручной сборки массивов.

Рассмотрим по шагам, как это реализовать:

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

Теперь код заполнения на клиенте и обработки на сервере будет выглядеть следующим образом:


&НаКлиенте
Процедура ЗагрузитьВТаблицуФормы(Команда)
    
    // Очищаем таблицу перед новой загрузкой
    Объект.ТаблицаЗагрузки.Очистить();
    
    // Код подключения к COM-объекту опустим для краткости
    // Представим, что мы получили результат в переменную "РезультатТаблица"
    
    Для Стр = 1 По КолСтрок Цикл
        
        // На клиенте добавляем строки в реквизит формы напрямую
        НоваяСтрока = Объект.ТаблицаЗагрузки.Добавить();
        НоваяСтрока.НомерДокумента = РезультатТаблица.Value(Стр, "MBLNR");
        НоваяСтрока.НоменклатураКод = РезультатТаблица.Value(Стр, "MATNR");
        НоваяСтрока.Количество = РезультатТаблица.Value(Стр, "MENGE");
        
    КонецЦикла;
    
    // Теперь вызываем серверный метод. 
    // Поскольку вызов контекстный (на сервере), таблица автоматически уедет на сервер.
    СоздатьДокументыИзТаблицыФормыНаСервере();
    
КонецПроцедуры

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

Важное предупреждение! Используйте этот метод с осторожностью. Если объем загружаемых данных превышает несколько тысяч строк, передача данных через контекст формы (между клиентом и сервером) создаст сильную нагрузку на сеть ("сетевой оверхед") и замедлит работу интерфейса. Для работы с большими объемами данных используйте неконтекстные методы.

--- ### Вариант 3. Использование Временного хранилища

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

Проанализируем алгоритм работы с временным хранилищем:


&НаКлиенте
Процедура ЗагрузитьЧерезХранилище(Команда)
    
    МассивДанных = Новый Массив;
    
    // Заполняем массив структур из COM-объекта на клиенте
    // (Логику обхода COM-объекта см. в Варианте 1)
    
    // Помещаем массив во временное хранилище прямо с клиента.
    // Связываем адрес с УникальнымИдентификатором формы, чтобы данные жили до закрытия формы.
    АдресХранилища = ПоместитьВоВременноеХранилище(МассивДанных, ЭтаФорма.УникальныйИдентификатор);
    
    // Передаем только адрес (короткую строку-ссылку UUID) на сервер
    ОбработатьДанныеИзХранилищаНаСервере(АдресХранилища);
    
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ОбработатьДанныеИзХранилищаНаСервере(АдресХранилища)
    
    // Извлекаем массив структур из временного хранилища на сервере
    МассивДанных = ПолучитьИзВременногоХранилища(АдресХранилища);
    
    Если МассивДанных = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    // Далее обрабатываем полученные данные
    // ...
    
КонецПроцедуры

--- ### Вариант 4. Сериализация в JSON (Максимальная производительность)

Для продвинутых разработчиков, работающих со сверхбольшими массивами строк (от 10 000 до 100 000 записей), наиболее производительным решением будет предварительная сериализация данных в формат JSON на стороне клиента с последующей быстрой десериализацией на сервере.

Дело в том, что при передаче обычного массива структур платформа выполняет ресурсоемкий XML/XDTO-маршалинг каждого элемента. Преобразование же в JSON выполняется платформой на уровне С++ чрезвычайно быстро. Давайте посмотрим, как это реализовать:


&НаКлиенте
Процедура ЗагрузитьСИспользованиемJSON(Команда)
    
    МассивДанных = Новый Массив;
    
    // Шаг 1. Наполняем массив структурами на клиенте
    // ...
    
    // Шаг 2. Сериализуем массив в JSON-строку
    ЗаписьJSON = Новый ЗаписьJSON;
    ЗаписьJSON.УстановитьСтроку();
    ЗаписатьJSON(ЗаписьJSON, МассивДанных);
    СтрокаДанныхJSON = ЗаписьJSON.Закрыть();
    
    // Шаг 3. Передаем строку на сервер
    СоздатьДокументыИзJSONНаСервере(СтрокаДанныхJSON);
    
КонецПроцедуры

&НаСервереБезКонтекста
Процедура СоздатьДокументыИзJSONНаСервере(СтрокаДанныхJSON)
    
    // Шаг 4. Десериализуем строку обратно в массив структур
    ЧтениеJSON = Новый ЧтениеJSON;
    ЧтениеJSON.УстановитьСтроку(СтрокаДанныхJSON);
    МассивДанных = ПрочитатьJSON(ЧтениеJSON);
    ЧтениеJSON.Закрыть();
    
    // Шаг 5. Работаем с массивом на сервере
    // ...
    
КонецПроцедуры

--- ### Сравнительный анализ решений

Чтобы вам было проще сделать выбор архитектуры для вашей задачи, мы структурировали все подходы в виде сводной таблицы преимуществ и недостатков:

Метод передачи Сложность реализации Рекомендуемый объем данных Плюсы Минусы
Массив структур Низкая До 5 000 строк Просто писать и читать код, стандартный механизм 1С. При больших объемах возрастает нагрузка на сетевой маршалинг.
Реквизит формы (ДанныеФормыКоллекция) Очень низкая До 1 000 строк Данные видны пользователю на форме в интерактивной таблице. Тяжелый контекст формы передается туда-обратно при каждом серверном вызове.
Временное хранилище Средняя До 20 000 строк Контекст формы не засоряется, высокая скорость передачи ссылки. Требуется контроль времени жизни и очистки временного хранилища.
Сериализация в JSON Выше средней Более 20 000 строк Максимальная скорость обмена, минимальный объем сетевого трафика. Требуется написание дополнительного кода сериализации/десериализации.

Используйте полученные знания и примеры кода для оптимизации интеграционных механизмов в ваших конфигурациях 1С. Помните, что залог стабильной и быстрой работы управляемого интерфейса — это минимизация объема данных, передаваемых в контексте формы между клиентом и сервером!

← На главную