Загрузка прайс-листа из EXCEL в справочник Номенклатура, с картинками и иерархией, без установленного MS Office на компьютере


Загрузка прайс листа из EXCEL или табличного документа 1С MXL с сохранением структуры иерархии.
А так же с картинками.
При этом нет необходимости устанавливать Mcrosoft Office на компьютер.
Загрузка в УТ 11.4

Дисклеймер:

Данная статья является больше описанием моего собственного опыта решения данного вопроса, но не в кой мере каким-то универсальным решением для всех и каждого. Поэтому заранее прошу прощения за некоторые упущения и тонкости, которые я мог упустить в процессе её написания.

Предисловие:

Захотелось как-то помочь всем тем несчастным вроде меня, кто знает, что можно читать EXCEL и уже не раз загружал что-то из него в 1С. Но вот тут возникла потребность загрузить картинки из ячеек, да так, что бы понимать какой строке таблицы соответствует какая картинка, что очень даже важно, если загружаешь прайс-лист поставщика.

Покопавшись немного в интернете появилось понимание, что EXCEL 2010 никак не предоставляет возможности выдернуть из себя картинку. И многие используют сторонние библиотеки, для переноса картинки из буфера обмена в файл. Источник //infostart.ru/public/16800/ К Сожалению у меня это не заработало. Библиотека регистрируется нормально но не работает, по причине не ясной до сих пор.

Тогда было принято решение поступить радикально и работать не с объектом EXCEL, а непосредственно с табличным документом, так как есть методы позволяющие прочитать файл xlsx и загрузить его в табличный документ и работать уже конкретно с ним. К счастью у табличного документа 1С у рисунка есть метод позволяющий сохранить его в файл, что уже огромный плюс.

Но тут уже встала другая проблема. Как понять какая картинка, какой строке соответствует? Наивно пологая, что через ширину столбцов и строк можно получить координаты картинки, я «наступил на огромные грабли». Вообще мне не понятно почему так происходит? Почему координаты ячейки не соответствуют координатам картинки. Почему нет какого-то соответствия или индекса или ещё чего-то в 1С, что бы можно было это сопоставить. Это был тупик, пока я не наткнулся на маленькую статейку с кодом вот тут:
https://medium.com/1c-tricks/-c146cf0af7d1                
Спасибо автору Дмитрий Марочко, теперь хоть и через костыли, но всё же я мог найти координаты картинки для нужной строки.

Дальше были только мелочные проблемы, например в Экселе есть возможность получить уровень группировки с помощью Лист1.rows(Х).OutlineLevel А вот в табличном документе такого свойства нет. Ещё возникали проблемы при загрузке картинки уже непосредственно в 1С, но это лишь от незнания особенностей конфигурации УТ 11.4

Платформа: 1С:Предприятие 8.3 (8.3.13.1690)
Конфигурация:  Управление торговлей, редакция 11 (11.4.6.207)

Хватит рассусоливать, погнали код.

Я выложу основные моменты в виде текста, что бы любой мог ими воспользоваться. Но для особо ленивых будет доступна обработка, в которой уже всё готово.  А так же пример прайс-листа. 

  1. Самое простое – это прочитать Эксель в табличный документ. Тут ничего сложно. Единственное есть нюанс, что чтение происходит на сервере и файл у вас должен быть доступен со стороны сервера. У меня это условие выполнялось, так что тут я особо не заморачивался.
    EXCEL открывается в табличном документе, при этом даже не обязательно, что бы на компьютере был установлен какой-то из продуктов Microsoft Office.

    Объект.ТабДок.Прочитать(Объект.ПутьКФайлу);

    В данном случае Объект.ТабДок – это реквизит на форме типа ТабличныйДокумент. Таким образом, у нас файл EXCEL открывается в табличном документе, при этом даже не обязательно, что бы на компьютере был установлен какой-то из продуктов Microsoft Office.

    Следующий код выполняется на клиенте

  2. Следующим этапом надо определить, где находятся нужные нам колонки. Что бы ни подгонять каждый раз таблицу под какой-то определённый шаблон, чего конечные пользователи делать не будут, я написал алгоритм, который позволяет автоматически определять, в каких колонках какие данные находятся.
    При этом, список данных, которые нужны заранее задан как колонки таблицы значений на форме. В моём случае это: Артикул Аналоги, Бренд, Номенклатура, Применяемость, Цена. Алгоритм анализирует начало таблицы в поисках имён и сам определяет по наименованию в какой колонке какие данные лежат. При этом у меня был довольно таки сложный прайс лист, так как в нём присутствовали повторяющиеся названия в колонках, например слово «номенклатура» встречалось несколько раз. Поэтому было дополнительное условие, выбирающее название колонки с наименьшей длинной.

     

    //Создадим структуру, которая будет хранить номер строки и номер колонки табличного документа в которых расположены соответсвующие данные
    КолПрайсЛист = Новый Структура;
    Для каждого Эл из Элементы.ТабЛист.ПодчиненныеЭлементы Цикл
    КолИмя = НРЕГ(СтрЗаменить(Эл.Имя,"ТабЛист",""));
    КолПрайсЛист.Вставить(КолИмя+"Строка",0);
    КолПрайсЛист.Вставить(КолИмя+"Колонка",0);
    КолПрайсЛист.Вставить(КолИмя+"Длинна",99);//Так как названия колонок могут повторятся, нам нужно выбрать название с минимальной длинной
    КонецЦикла;
    
    Для Х=1 По КоличествоКолонок Цикл  //что бы не по всей таблице ведь мы ищем только шапку а она где-то вверху
    Для У=1 По КоличествоКолонок Цикл
    
    ТекстХУ = НРЕГ(Объект.ТабДок.Область(Х,У,Х,У).Текст);
    
    Если СтрНайти(ТекстХУ,"бренд")<>0 тогда
    КолПрайсЛист.брендСтрока = Х;
    КолПрайсЛист.брендКолонка = У;
    КонецЕсли;
    
    Если СтрНайти(ТекстХУ,"номенклатура")<>0 тогда //как раз таки слово "Номенклатура" у нас встречается несколько раз
    Если СтрДлина(ТекстХУ)<=КолПрайсЛист.номенклатураДлинна Тогда
    КолПрайсЛист.номенклатураСтрока = Х;
    КолПрайсЛист.номенклатураКолонка = У;
    КолПрайсЛист.номенклатураДлинна =  СтрДлина(ТекстХУ);
    КонецЕсли;
    КонецЕсли;
    
    Если СтрНайти(ТекстХУ,"артикул")<>0 тогда
    КолПрайсЛист.артикулСтрока = Х;
    КолПрайсЛист.артикулКолонка = У;
    КонецЕсли;
    
    Если СтрНайти(ТекстХУ,"аналог")<>0 тогда
    КолПрайсЛист.аналогиСтрока = Х;
    КолПрайсЛист.аналогиКолонка = У;
    КонецЕсли;
    
    Если СтрНайти(ТекстХУ,"применяемость")<>0 тогда
    КолПрайсЛист.применяемостьСтрока = Х;
    КолПрайсЛист.применяемостьКолонка = У;
    КонецЕсли;
    
    Если СтрНайти(ТекстХУ,"цена")<>0 тогда  //Так же как и цена, так же может быть не одна
    Если СтрДлина(ТекстХУ)<=КолПрайсЛист.ценаДлинна Тогда
    КолПрайсЛист.ценаСтрока = Х;
    КолПрайсЛист.ценаКолонка = У;
    КолПрайсЛист.ценаДлинна =  СтрДлина(ТекстХУ);
    КонецЕсли;
    КонецЕсли;
    
    Если СтрНайти(ТекстХУ,"картинка")<>0 тогда
    КолПрайсЛист.картинкаСтрока = Х;
    КолПрайсЛист.картинкаКолонка = У;
    КонецЕсли;
    
    КонецЦикла;
    КонецЦикла;
    
  3. Таким образом мы находим всю шапку табличного документа и здесь же понимаем с какой строки начинать загрузку непосредственно данных. Конкретно в моём случае данные начинались через две строки после шапки, так как и сама шапка высотой в две ячейки.
     

    максимальныйX = 1;
    Для каждого Эл из КолПрайсЛист Цикл
    Если СтрНайти(Эл.Ключ,"Строка")<>0 Тогда
    Если максимальныйX<Эл.Значение Тогда
    максимальныйX = Эл.Значение;
    КонецЕсли;
    КонецЕсли;
    КонецЦикла;
    Сообщить("Обход начинается с "+Строка(максимальныйX)+"+2");
    
  4. Дальше происходит непосредственно обход табличного документа по строкам с изъятием данных из колонок, которые мы нашли и добавлением в таблицу значений. Всё это происходит на клиенте.
     

    максимальныйX = максимальныйX + 2;
    ТабЛист.Очистить();
    ВремФ = КаталогВременныхФайлов()+"tempIMG.";
    УровеньГруппировки = 0;
    
    //обход по таблице с данными до конца прайс листа.
    //Предполагается что у прайс листа нет никаких подвалов,
    //тем не менее этот момент тоже предусмотрен и если подвал всё же есть,
    //то в таблицу значений не будут добавлены пустые строки
    Для Х=максимальныйX По КоличествоСтрок Цикл
    Если ПустаяСтрока(Объект.ТабДок.Область(Х,КолПрайсЛист.номенклатураКолонка,Х,КолПрайсЛист.номенклатураКолонка).текст) Тогда //Если колонка с номенклатурой пуста, то это скорее всего начало группы. А значит добавляем наименование группы
    Если НЕ ПустаяСтрока(Объект.ТабДок.Область(Х,КолПрайсЛист.брендКолонка,Х,КолПрайсЛист.брендКолонка).текст) Тогда //Если нет названия группы, то и не будем ничего создавать
    Нов = ТабЛист.Добавить();
    Нов.группа = Объект.ТабДок.Область(Х,КолПрайсЛист.брендКолонка,Х,КолПрайсЛист.брендКолонка).текст;
    //вот тут интересно. У Экселя есть Лист1.rows(Х).OutlineLevel
    //а у табличного документа такого нет, но есть "Отступ (Indent)" который может, хоть и косвенно, но указывать на иерархию, если выгрузка прайс листа идёт из 1С
    УровеньГруппировки = Объект.ТабДок.Область(Х,КолПрайсЛист.брендКолонка,Х,КолПрайсЛист.брендКолонка).Отступ/2;//каждый уровень иерархии прибавляет по 2 символа отступа
    Нов.УровеньГруппировки = УровеньГруппировки;
    КонецЕсли;
    Иначе
    Нов = ТабЛист.Добавить();
    Нов.Бренд = Объект.ТабДок.Область(Х,КолПрайсЛист.брендКолонка).текст;
    Нов.Номенклатура = Объект.ТабДок.Область(Х,КолПрайсЛист.номенклатураКолонка).текст;
    Нов.Артикул = Объект.ТабДок.Область(Х,КолПрайсЛист.артикулКолонка).текст;
    Нов.Аналоги = Объект.ТабДок.Область(Х,КолПрайсЛист.аналогиКолонка).текст;
    Нов.Применяемость = Объект.ТабДок.Область(Х,КолПрайсЛист.применяемостьКолонка).текст;
    Попытка
    Нов.Цена = Объект.ТабДок.Область(Х,КолПрайсЛист.ценаКолонка).text;
    Исключение
    Сообщить("Не число в "+Строка(Х)+" "+ОписаниеОшибки());
    КонецПопытки;
    Нов.УровеньГруппировки = УровеньГруппировки+1; //уровень группировки элемента на уровень больше группы
    //Создадим временную картинку.
    ВремКартинка = Объект.ТабДок.Рисунки.Добавить(ТипРисункаТабличногоДокумента.Текст);
    ВремКартинка.Имя = "Линейка"+Формат(Х,"ЧН=; ЧГ=");
    //расположим её в следующей колонке после самой последней, в строке, в которой сейчас находимся
    ВремКартинка.Расположить(Объект.ТабДок.Область(Х,КоличествоКолонок+1,Х,КоличествоКолонок+1));
    //Таким образом мы получаем точный отступ сверху для картинки, которая содержит рисунок нашей номенклатуры
    ТочноеПоложениеКартинки = ВремКартинка.Верх;
    //Временная картинка больше не нужна
    Объект.ТабДок.Рисунки.Удалить(ВремКартинка);
    
    //Обходим коллекцию рисунков и сравниваем отступ сверху с тем, который мы запомнили для данной строки
    Для каждого Изо ИЗ Объект.ТабДок.Рисунки Цикл
    Если ИЗО.Верх = ТочноеПоложениеКартинки Тогда
    ФК = Изо.Картинка.Формат();
    Нов.Расширение = Строка(ФК);
    Изо.Картинка.Записать(ВремФ+Нов.Расширение);  //Когда нашли, сохраняем картинку в файл
    Нов.Картинка = Новый Картинка(ВремФ+Нов.Расширение); //после этого из файла добавляем её в нашу таблицу значений
    Прервать;//Для экономии времени, если картинка нашлась, не нужно дальше обходить оставшиеся элементы коллекции
    КонецЕсли;
    КонецЦикла;
    
    КонецЕсли;
    КонецЦикла;
    

     

  5. После всего этого весь прайс лист у нас в таблице значений на клиенте. И дальше мы можем обойти таблицу значений и создать как группы справочника, так и элементы с нужными значениями. Заострять на этом внимания я не буду. Тут многое зависит от конфигурации. Лишь покажу, как извлечь картинку из таблицы значений и поместить её в справочник номенклатура. Хоть данный код и справедлив только для УТ 11.4.6.207 но я думаю многим он будет полезен. (Потому что раньше это было просто. Добавляешь объект в ХранилищеДополнительнойИнформации и всё. А вот тут оказалось всё по другому)

     

    Если Не ПустаяСтрока(РасширениеКартинки) Тогда //Это колонка Стр.расширение,, если она не пустая, значит есть картинка в колонке Стр.Картинка
    Найд = Справочники.НоменклатураПрисоединенныеФайлы.НайтиПоРеквизиту("ВладелецФайла",НовНом.Ссылка);
    Если ЗначениеЗаполнено(Найд) Тогда  //Если вообще какой-то файл или картинка был загружен ранее, то он удаляется.
    Найд.ПолучитьОбъект().Удалить();
    КонецЕсли;
    //Помещаем двоичные данные картинки во временное хранилище
    АдресФайлаВХранилище = ПоместитьВоВременноеХранилище(КартинкаСтроки.ПолучитьДвоичныеДанные(), УникальныйИдентификатор);
    //Создаём структуру
    ПараметрыФайлаКартинки = Новый Структура();
    ПараметрыФайлаКартинки.Вставить("Автор", Пользователи.АвторизованныйПользователь());
    ПараметрыФайлаКартинки.Вставить("ВладелецФайлов", НовНом.Ссылка);
    ПараметрыФайлаКартинки.Вставить("ИмяБезРасширения", НовНом.Наименование);
    ПараметрыФайлаКартинки.Вставить("РасширениеБезТочки", "PNG");
    ПараметрыФайлаКартинки.Вставить("ВремяИзмененияУниверсальное", ТекущаяДата());
    //И используем один из механизмов, доступных в УТ 11.4.6.207
    ПрисоединенныйФайл = РаботаСФайлами.ДобавитьФайл(
    ПараметрыФайлаКартинки,
    АдресФайлаВХранилище,
    "",
    "НоменклатураПрисоединенныеФайлы");
    //Который возвращает нам ссылку на Справочники.НоменклатураПрисоединенныеФайлы
    НовНом.ФайлКартинки = ПрисоединенныйФайл;
    //Это значение присваивается реквизиту справочника номенклатура с именем "ФайлКартинки"
    КонецЕсли;
    

     

  6. По большей части всё. Дальше просто создаёте номенклатуру, затем номенклатуру поставщика, создаёте между ними связь. А затем регистрируете цены поставщика.

Надеюсь, мой пример сэкономит кому-то время и нервы. Я собирал всю информацию около недели, так как нет всего этого в одном месте. Где-то одно, где-то другое. Много всего перепробовал, много не получилось, много ошибок допустил и исправил.R03;R03;R03;R03;R03;R03;R03;

1 Comment

  1. 2casp

    Не совсем универсальный механизм заполнения ТабЛист, по которому в дальнейшем создается номенклатура. Сильно привязано к текущему прайс листу.

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *