Как на клиенте открыть и заполнить реквизиты справочника в 1С при импорте данных?

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

При разработке обработок загрузки данных из внешних источников (например, файлов Microsoft Excel) начинающие разработчики часто сталкиваются со следующей ошибкой компиляции в тонком клиенте:

{Обработка.ЧтениеИзExcel.Форма.Форма.Форма(75,9)}: Переменная не определена (справочники)

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

Выясняем причину: почему на клиенте недоступны «Справочники»?

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

Объект метаданных Справочники (глобальный контекст менеджера справочников) существует исключительно на сервере. На клиенте вы не можете:

  1. Использовать метод Справочники.ОсновныеСредства.Выбрать() для обхода элементов.
  2. Вызывать методы поиска вроде НайтиПоНаименованию() или НайтиПоРеквизиту().
  3. Создавать новые элементы справочников через метод СоздатьЭлемент().

Попытка обхода элементов справочника в цикле на клиенте (даже если бы это было технически возможно) привела бы к катастрофическому падению производительности из-за сотен микрозапросов между клиентским приложением и сервером СУБД. Наша задача — спроектировать процесс так, чтобы вся тяжелая работа выполнялась на сервере за один вызов.

Шаг 1. Перенос файла Excel с клиента на сервер

Вместо того чтобы читать Excel-файл построчно на клиенте с помощью медленных COM-объектов (таких как Excel.Application), которые к тому же не работают на операционных системах Linux, мы применим современный кроссплатформенный подход 1С.

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


&НаКлиенте
Процедура ЗагрузитьФайлExcel(Команда)
    
    // Описываем параметры диалога выбора файла
    ПараметрыДиалога = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
    ПараметрыДиалога.Фильтр = "Файлы Excel (*.xls;*.xlsx)|*.xls;*.xlsx";
    ПараметрыДиалога.МножественныйВыбор = Ложь;
    ПараметрыДиалога.Заголовок = "Выберите файл для импорта";
    
    // Ожидаем выбор файла пользователем
    ОписаниеОповещения = Новый ОписаниеОповещения("ПослеВыбораФайла", ЭтотОбъект);
    НачатьВыборФайла(ОписаниеОповещения, ПараметрыДиалога);
    
КонецПроцедуры

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

&НаКлиенте
Процедура ПослеПомещенияФайла(Результат, АдресХранилища, ВыбранноеИмяФайла, ДополнительныеПараметры) Экспорт
    
    Если Не Результат Тогда
        ПоказатьПредупреждение(, "Не удалось загрузить файл во временное хранилище!");
        Возврат;
    КонецЕсли;
    
    // Передаем управление серверному методу, отправляя адрес во временном хранилище
    ОбработатьФайлНаСервере(АдресХранилища);
    
КонецПроцедуры

Шаг 2. Чтение данных Excel и сопоставление на сервере

Теперь на сервере мы извлечем бинарные данные из временного хранилища, сохраним их во временный файл на диске сервера и прочитаем с помощью встроенного объекта 1С ТабличныйДокумент. Этот метод работает невероятно быстро, не требует установленного Microsoft Office и стабилен под любыми операционными системами.

Проанализируем серверную процедуру обработки:


&НаСервере
Процедура ОбработатьФайлНаСервере(АдресХранилища)
    
    // Получаем двоичные данные из хранилища
    ДвоичныеДанныеФайла = ПолучитьИзВременногоХранилища(АдресХранилища);
    
    // Создаем имя временного файла на сервере
    ИмяВременногоФайла = ПолучитьИмяВременногоФайла("xlsx");
    ДвоичныеДанныеФайла.Записать(ИмяВременногоФайла);
    
    // Читаем файл через Табличный Документ
    ТабДок = Новый ТабличныйДокумент;
    Попытка
        ТабДок.Прочитать(ИмяВременногоФайла, СпособЧтенияЗначенийТабличногоДокумента.Значение);
    Исключение
        ВызватьИсключение "Ошибка чтения файла: " + ОписаниеОшибки();
    КонецПопытки;
    
    // Удаляем временный файл после прочтения
    Попытка
        УдалитьФайлы(ИмяВременногоФайла);
    Исключение
        // Игнорируем ошибку удаления, если файл занят операционной системой
    КонецПопытки;
    
    // Создаем ТаблицуЗначений для хранения данных из Excel
    ТаблицаИмпорта = Новый ТаблицаЗначений;
    ТаблицаИмпорта.Колонки.Добавить("КодЕГАИС", Новый ОписаниеТипов("Строка", Новый КвалификаторыСтроки(50)));
    ТаблицаИмпорта.Колонки.Добавить("Наименование", Новый ОписаниеТипов("Строка", Новый КвалификаторыСтроки(150)));
    ТаблицаИмпорта.Колонки.Добавить("НовоеЗначениеРеквизита", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
    
    // Заполняем таблицу значениями из Табличного Документа (начиная со 2-й строки, пропуская шапку)
    ВсегоСтрок = ТабДок.ВысотаТаблицы;
    Для НомерСтроки = 2 По ВсегоСтрок Цикл
        
        НоваяСтрока = ТаблицаИмпорта.Добавить();
        НоваяСтрока.КодЕГАИС = ТабДок.Область(НомерСтроки, 1).Текст;
        НоваяСтрока.Наименование = ТабДок.Область(НомерСтроки, 2).Текст;
        НоваяСтрока.НовоеЗначениеРеквизита = Число(ТабДок.Область(НомерСтроки, 3).Текст);
        
    КонецЦикла;
    
    // Выполняем сопоставление с базой данных ОДНИМ запросом (Оптимальный паттерн)
    СопоставитьИЗаписатьДанные(ТаблицаИмпорта);
    
КонецПроцедуры

Шаг 3. Быстрое сопоставление данных через Запрос

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


&НаСервере
Процедура СопоставитьИЗаписатьДанные(ТаблицаИмпорта)
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ТаблицаИмпорта.КодЕГАИС КАК КодЕГАИС,
        |   ТаблицаИмпорта.Наименование КАК Наименование,
        |   ТаблицаИмпорта.НовоеЗначениеРеквизита КАК НовоеЗначениеРеквизита
        |ПОМЕСТИТЬ ВТ_Импорт
        |ИЗ
        |   &ТаблицаИмпорта КАК ТаблицаИмпорта
        |;
        |
        |////////////////////////////////////////////////////////////////////////////////
        |ВЫБРАТЬ
        |   ВТ_Импорт.КодЕГАИС КАК КодЕГАИС,
        |   ВТ_Импорт.Наименование КАК НаименованиеИзФайла,
        |   ВТ_Импорт.НовоеЗначениеРеквизита КАК НовоеЗначение,
        |   ОсновныеСредства.Ссылка КАК СсылкаНаОбъект,
        |   ОсновныеСредства.ДополнительныйРеквизит КАК СтароеЗначение
        |ИЗ
        |   ВТ_Импорт КАК ВТ_Импорт
        |   ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ОсновныеСредства КАК ОсновныеСредства
        |       ПО ВТ_Импорт.КодЕГАИС = ОсновныеСредства.КодЕГАИС";
        
    Запрос.УстановитьПараметр("ТаблицаИмпорта", ТаблицаИмпорта);
    
    РезультатЗапроса = Запрос.Выполнить();
    Выборка = РезультатЗапроса.Выбрать();
    
    Пока Выборка.Следующий() Цикл
        
        Если ЗначениеЗаполнено(Выборка.СсылкаНаОбъект) Тогда
            
            // Если значение реквизита изменилось, перезаписываем элемент на сервере
            Если Выборка.СтароеЗначение <> Выборка.НовоеЗначение Тогда
                
                ОбъектСпр = Выборка.СсылкаНаОбъект.ПолучитьОбъект();
                ОбъектСпр.ДополнительныйРеквизит = Выборка.НовоеЗначение;
                
                Попытка
                    ОбъектСпр.Записать();
                Исключение
                    ЗаписьЖурналаРегистрации("ИмпортДанных", УровеньЖурналаРегистрации.Ошибка, , , 
                        "Не удалось записать элемент: " + Выборка.СсылкаНаОбъект + ". Описание: " + ОписаниеОшибки());
                КонецПопытки;
                
            КонецЕсли;
            
        Иначе
            // Обработка ситуации, когда элемент не найден (например, вывод уведомления на клиент)
        КонецЕсли;
        
    КонецЦикла;
    
КонецПроцедуры

Шаг 4. Открытие формы элемента справочника и заполнение на клиенте

Иногда бизнес-логика требует не просто фоновой перезаписи объектов на сервере, а интерактивного открытия формы справочника с уже заполненными из Excel реквизитами, чтобы пользователь проверил изменения перед сохранением. Рассмотрим два сценария реализации этой задачи.

Вариант А: Создание нового элемента справочника с автозаполнением

Для интерактивного создания и открытия нового элемента справочника с предзаполненными реквизитами мы воспользуемся встроенным параметром формы ЗначенияЗаполнения. При таком методе платформа автоматически передаст структуру в обработчик модуля объекта ОбработкаЗаполнения.

Код клиентского метода для вызова формы:


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

Вариант Б: Открытие существующего элемента и изменение его реквизитов на клиенте

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

1. Сначала инициируем открытие формы существующего элемента с клиента:


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

2. Перейдем в Модуль формы справочника ОсновныеСредства и перехватим отправленный параметр в событии ПриСозданииНаСервере:


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

Используя данный метод, пользователь увидит открывшуюся форму существующего основного средства с измененным реквизитом. На экране отобразится предупреждение о несохраненных данных, и пользователь сможет осознанно нажать кнопку Записать и закрыть либо отменить изменения.

Резюме

Мы спроектировали оптимальный цикл взаимодействия клиента и сервера. Разделение функционала предотвращает ошибки нарушения прав доступа и исключает зависания системы. Помещение файлов во временное хранилище и сопоставление данных через серверные пакетные запросы — это залог качественной разработки на платформе 1С:Предприятие 8.3.

← На главную