Как загрузить данные из Excel в 1С 8.3 на управляемых формах с клиента на сервер?

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

При разработке обработок загрузки данных в клиент-серверной архитектуре 1С разработчики часто сталкиваются с типовой проблемой: диалог выбора файла и локальный файл доступны только на клиенте, в то время как создание и запись документов должны выполняться на стороне сервера. Кроме того, классический подход с использованием COMОбъект("Excel.Application") требует наличия установленного Microsoft Excel на сервере, не работает в среде Linux и существенно замедляет загрузку больших объемов данных.

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

Схема взаимодействия: Клиент — Временное хранилище — Сервер

В современных версиях платформы 1С:Предприятие 8.3 для работы с файлами применяется следующий стандартный алгоритм:

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

Вариант 1. Загрузка через ТабличныйДокумент (без использования Excel на сервере)

Начиная с версии платформы 8.3.6, объект ТабличныйДокумент поддерживает метод Прочитать, который умеет напрямую открывать файлы форматов .xls и .xlsx без установки сторонних программ и библиотек.

Разберем реализацию этого способа. На форме создадим команду и опишем клиентские процедуры:


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

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

Теперь напишем серверную часть, в которой прочитаем файл и создадим документ:


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

Вариант 2. Загрузка через ADODB (быстрое чтение на Windows-серверах)

Если на сервере установлена ОС Windows и настроен OLE DB-провайдер Microsoft.ACE.OLEDB, для чтения больших таблиц можно применить подключение через ADODB. Это позволяет обращаться к листу Excel с помощью привычных SQL-запросов.

Посмотрим на пример функции чтения данных через ADO:


&НаСервере
Функция ПрочитатьExcelЧерезADO(ПутьКФайлу)
    
    ТаблицаРезультат = Новый ТаблицаЗначений;
    ТаблицаРезультат.Колонки.Добавить("ТабНомер", Новый ОписаниеТипов("Строка"));
    ТаблицаРезультат.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
    
    Соединение = Новый COMОбъект("ADODB.Connection");
    СтрокаПодключения = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + ПутьКФайлу + ";Extended Properties=""Excel 12.0;HDR=YES;IMEX=1;"";";
    
    Попытка
        Соединение.Open(СтрокаПодключения);
    Исключение
        ВызватьИсключение "Ошибка подключения к Excel через OLEDB: " + ОписаниеОшибки();
    КонецПопытки;
    
    Каталог = Новый COMОбъект("ADOX.Catalog");
    Каталог.ActiveConnection = Соединение;
    ИмяПервогоЛиста = Каталог.Tables.Item(0).Name;
    
    НаборЗаписей = Новый COMОбъект("ADODB.Recordset");
    ТекстSQL = "SELECT * FROM [" + ИмяПервогоЛиста + "]";
    НаборЗаписей.Open(ТекстSQL, Соединение, 3, 1);
    
    Пока НЕ НаборЗаписей.EOF Цикл
        
        ТабНомер = СокрЛП(Строка(НаборЗаписей.Fields.Item(1).Value));
        Сумма = Число(НаборЗаписей.Fields.Item(2).Value);
        
        Если ЗначениеЗаполнено(ТабНомер) Тогда
            НовСтр = ТаблицаРезультат.Добавить();
            НовСтр.ТабНомер = ТабНомер;
            НовСтр.Сумма = Сумма;
        КонецЕсли;
        
        НаборЗаписей.MoveNext();
    КонецЦикла;
    
    НаборЗаписей.Close();
    Соединение.Close();
    
    Возврат ТаблицаРезультат;
    
КонецФункции

Типичные ошибки и способы их устранения

  1. Ошибка преобразования к числу: При чтении текста ячейки ТабличныйДокумент.Область().Текст разделителями разрядов часто выступают неразрывные пробелы (Символы.НПП). Всегда очищайте строку перед вызовом функции Число().
  2. Ошибка «Доступ к файлу не может быть получен»: Возникает, когда временный файл не закрыт другим процессом либо у учетной записи пользователя сервера (например, USR1CV8) отсутствуют права на чтение/запись в каталоге временных файлов. Всегда оборачивайте работу с файлами в блок Попытка ... Исключение и очищайте временные данные через УдалитьФайлы().
  3. Медленный поиск ссылок в цикле: Вызов методов НайтиПоКоду или НайтиПоНаименованию на каждой итерации цикла создает множество отдельных обращений к СУБД. При обработке больших файлов заранее соберите все коды в массив и выполните один пакетный запрос к базе с условием ГДЕ Код В (&МассивКодов), выгрузив результат в объект Соответствие.
← На главную