При автоматизации складского учета или заказов часто возникает ситуация, когда внешний файл Excel имеет «горизонтальную» структуру: в строках указаны товары, а в колонках — магазины или контрагенты, и на пересечении стоит количество. Такая структура удобна для человека, но неудобна для загрузки в типовые документы 1С, где контрагент обычно является реквизитом шапки, а товары — строками табличной части. В этой статье мы разберем, как правильно организовать чтение таких данных, нормализовать их и вывести на управляемую форму 1С 8.3.
Рассмотрим исходную задачу: файл содержит около 100 товаров и 50 магазинов. Если пытаться загрузить данные «как есть», создав табличную часть с 50 колонками, мы столкнемся с жестким ограничением метаданных — колонки в табличных частях объектов (документов, справочников) нельзя создавать «на лету» в режиме 1С:Предприятие. Более того, работа с ТаблицейЗначений на управляемых формах существенно отличается от старых «обычных» форм (v8.2), где использовался элемент Табличное поле с методом СоздатьКолонки().
Проанализируем классический способ чтения Excel с использованием COM-соединения. Этот метод позволяет напрямую обращаться к ячейкам листа и выгружать их в массивы 1С. Рассмотрим пример кода для получения данных в ТаблицуЗначений:
// Создаем подключение к Excel
Попытка
Excel = Новый COMОбъект("Excel.Application");
Книга = Excel.Workbooks.Open(ПутьКФайлу);
ТекЛист = Книга.Worksheets(1);
Исключение
Сообщить("Ошибка при открытии Excel: " + ОписаниеОшибки());
Возврат;
КонецПопытки;
КолСтрок = ТекЛист.UsedRange.Rows.Count;
КолСтолбцов = ТекЛист.UsedRange.Columns.Count;
// Получаем данные одним массивом для ускорения
СомОбластьДанных = ТекЛист.Range(ТекЛист.Cells(1,1), ТекЛист.Cells(КолСтрок, КолСтолбцов));
Данные = СомОбластьДанных.Value.Выгрузить();
// Формируем ТаблицуЗначений
ТЗ = Новый ТаблицаЗначений;
Для Сч = 0 По КолСтолбцов - 1 Цикл
ТЗ.Колонки.Добавить("Колонка" + Сч);
КонецЦикла;
Для Сч = 1 По КолСтрок Цикл
ТЗ.Добавить();
КонецЦикла;
Для Сч = 0 По КолСтолбцов - 1 Цикл
ТЗ.ЗагрузитьКолонку(Данные[Сч], "Колонка" + Сч);
КонецЦикла;
Excel.Quit();
Важный нюанс: метод Value.Выгрузить() работает значительно быстрее, чем перебор каждой ячейки в цикле Cells(x, y).Value. Однако помните, что COM-объект требует установленного Microsoft Excel на компьютере, где выполняется код (обычно на клиенте), и может работать медленно при больших объемах данных.
Вместо того чтобы мучиться с отображением 50 колонок магазинов на форме, проанализируем ситуацию с точки зрения архитектуры 1С. Для создания документов нам нужна «плоская» структура. Выясним причину, почему это лучше: плоскую таблицу проще сортировать, группировать и искать в ней расхождения.
Рассмотрим алгоритм трансформации:
ТаблицуЗначений.ТаблицуЗначений с тремя колонками: Магазин, Товар, Количество.Посмотрим на пример трансформации:
ТЗ_Результат = Новый ТаблицаЗначений;
ТЗ_Результат.Колонки.Добавить("Магазин");
ТЗ_Результат.Колонки.Добавить("Товар");
ТЗ_Результат.Колонки.Добавить("Количество");
// Предположим, первая колонка в Excel - Товар, остальные - Магазины
Для НомСтроки = 1 По ТЗ_Загрузки.Количество() - 1 Цикл
ТоварСсылка = НайтиТовар(ТЗ_Загрузки[НомСтроки].Колонка0);
Для НомКол = 1 По ТЗ_Загрузки.Колонки.Количество() - 1 Цикл
Количество = ТЗ_Загрузки[НомСтроки]["Колонка" + НомКол];
Если ЗначениеЗаполнено(Количество) Тогда
НоваяСтрока = ТЗ_Результат.Добавить();
НоваяСтрока.Магазин = ТЗ_Загрузки[0]["Колонка" + НомКол]; // Заголовок колонки
НоваяСтрока.Товар = ТоварСсылка;
НоваяСтрока.Количество = Количество;
КонецЕсли;
КонецЦикла;
КонецЦикла;
Теперь у вас есть готовый набор данных для создания 50 документов (по одному на каждый уникальный магазин) простым перебором с группировкой.
Если бизнес-задача все же требует показать пользователю таблицу именно в том виде, в котором она была в Excel (с магазинами в колонках), нам придется программно изменять состав реквизитов формы. В управляемом интерфейсе 1С 8.3 для этого используется метод ИзменитьРеквизиты().
Разберем по шагам процесс вывода динамической ТаблицыЗначений:
РеквизитФормы.ЭтаФорма.ИзменитьРеквизиты(МассивДобавляемыхРеквизитов).ПолеФормы.Пример программного создания колонок:
&НаСервере
Процедура ВывестиТЗНаФорму(ТаблицаДанных)
// 1. Создаем массив новых реквизитов
МассивРеквизитов = Новый Массив;
Для Каждого Колонка Из ТаблицаДанных.Колонки Цикл
МассивРеквизитов.Добавить(Новый РеквизитФормы(Колонка.Имя, Колонка.ТипЗначения, "ТЗ_НаФорме"));
КонецЦикла;
// 2. Добавляем реквизиты в структуру формы
ЭтаФорма.ИзменитьРеквизиты(МассивРеквизитов);
// 3. Создаем визуальные элементы (колонки в таблице на форме)
Для Каждого Колонка Из ТаблицаДанных.Колонки Цикл
НовыйЭлемент = Элементы.Добавить("ТЗ_НаФорме" + Колонка.Имя, Тип("ПолеФормы"), Элементы.ТаблицаИнтерфейс);
НовыйЭлемент.Вид = ВидПоляФормы.ПолеВвода;
НовыйЭлемент.ПутьКДанным = "ТЗ_НаФорме." + Колонка.Имя;
КонецЦикла;
// 4. Загружаем данные
ЗначениеВРеквизитФормы(ТаблицаДанных, "ТЗ_НаФорме");
КонецПроцедуры
Рассмотрим более современный и быстрый способ чтения Excel без использования COM. Платформа 1С позволяет загрузить файл напрямую в ТабличныйДокумент. Это работает стабильнее на стороне сервера.
ТабДок = Новый ТабличныйДокумент;
ТабДок.Прочитать(ПутьКФайлу, СпособЧтенияЗначенийТабличногоДокумента.Значение);
// Далее работаем с ТабДок как с матрицей
ВсегоСтрок = ТабДок.ВысотаТаблицы;
ВсегоКолонок = ТабДок.ШиринаТаблицы;
Для НомСтроки = 2 По ВсегоСтрок Цикл
ТоварНаименование = ТабДок.Область(НомСтроки, 1).Текст;
// ... логика обработки
КонецЦикла;
Проанализировав ситуацию, можно сделать следующие выводы:
ИзменитьРеквизиты(), но помните о производительности: 100+ динамических колонок могут замедлить отрисовку интерфейса.ТабличныйДокумент.Прочитать(), так как он не зависит от стороннего ПО и работает на сервере под Linux.ЗначениеВРеквизитФормы() и РеквизитФормыВЗначение() для быстрой передачи данных между программными объектами (ТЗ) и экранными формами.Таким образом, комбинируя правильную структуру данных (нормализацию) и механизмы динамического управления формой, вы сможете создать гибкий и надежный инструмент для загрузки любых кросс-таблиц из Excel в вашу базу 1С.