Как найти номенклатуру по артикулу из файла Excel и заполнить табличную часть в 1С

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

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

Загрузка данных строка за строкой с поиском через метод НайтиПоРеквизиту крайне неэффективна и создает огромную нагрузку на СУБД. Профессиональный и быстрый способ решения этой задачи — считывание данных из файла во временную таблицу значений, передача этой таблицы в качестве параметра в запрос и выполнение левого соединения со справочником номенклатуры. В этой статье мы подробно разберем, как реализовать этот алгоритм на управляемых формах 1С 8.3, разберем типичные ошибки сопоставления типов и невидимых пробелов.

Общая схема реализации задачи

Для того чтобы наше решение работало стабильно, быстро и соответствовало стандартам разработки платформы 1С:Предприятие 8.3, мы разделим весь процесс на пять последовательных этапов:

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

Давайте подробно проанализируем каждый из этих шагов и посмотрим на практические примеры кода.

Шаг 1. Выбор и чтение файла Excel

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

Рассмотрим процедуру начала выбора файла на клиенте:


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

Обратите внимание, что непосредственное чтение файла Excel методом Прочитать() объекта ТабличныйДокумент рекомендуется производить в серверном контексте, так как это гарантирует более высокую скорость обработки данных и отсутствие проблем с блокировкой файлов операционной системой на клиенте.

Шаг 2. Преобразование в таблицу значений и очистка артикулов

Когда табличный документ прочитан в памяти сервера, нам необходимо преобразовать его строки в плоскую таблицу значений. Для автоматического разбора колонок отлично подходит системный объект ПостроительЗапроса. Он позволяет мгновенно выгрузить область ячеек табличного документа в коллекцию строк.

Однако здесь нас подстерегает самая частая проблема интеграции с Excel — грязные данные. Пользователи часто случайно добавляют пробелы в начале или конце артикулов, а сам Excel при форматировании чисел может автоматически добавлять так называемые неразрывные пробелы (символ с кодом 160) в качестве разделителей групп разрядов. Строка "123 456" (с неразрывным пробелом) никогда не сопоставится в запросе со строкой "123456" из базы 1С.

Поэтому перед отправкой таблицы в запрос мы обязательно выполняем нормализацию каждого артикула. Мы заменим все виды пробелов на пустые строки с помощью функции СтрЗаменить() и метода СокрЛП().

Рассмотрим, как выглядит этот шаг на встроенном языке:


&НаСервере
Функция ПодготовитьТаблицуИзФайла(ПутьКФайлу)
    
    ТабДок = Новый ТабличныйДокумент;
    Попытка
        ТабДок.Прочитать(ПутьКФайлу, СпособЧтенияЗначенийТабличногоДокумента.Значение);
    Исключение
        ВызватьИсключение "Не удалось прочитать файл. Возможно, он открыт в другой программе! " + ОписаниеОшибки();
    КонецПопытки;
    
    // Используем построитель запроса для быстрого импорта данных
    Построитель = Новый ПостроительЗапроса;
    Построитель.ИсточникДанных = Новый ОписаниеИсточникаДанных(ТабДок.Область());
    Построитель.Выполнить();
    
    ТЗ_Исходная = Построитель.Результат.Выгрузить();
    
    // Создаем строго типизированную таблицу значений для передачи в запрос
    ТЗ_Очищенная = Новый ТаблицаЗначений;
    ТЗ_Очищенная.Колонки.Добавить("Артикул", Новый ОписаниеТипов("Строка", Новый КвалификаторыСтроки(100)));
    ТЗ_Очищенная.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
    
    Для Каждого СтрокаИмпорта Из ТЗ_Исходная Цикл
        
        Если НЕ ЗначениеЗаполнено(СтрокаИмпорта.Артикул) Тогда
            Продолжить;
        КонецЕсли;
        
        НоваяСтрока = ТЗ_Очищенная.Добавить();
        
        // Удаляем обычные пробелы, неразрывные пробелы (символ 160) и приводим к верхнему регистру
        ЧистыйАртикул = Строка(СтрокаИмпорта.Артикул);
        ЧистыйАртикул = СтрЗаменить(ЧистыйАртикул, Символ(160), "");
        ЧистыйАртикул = СтрЗаменить(ЧистыйАртикул, " ", "");
        ЧистыйАртикул = СокрЛП(ЧистыйАртикул);
        
        НоваяСтрока.Артикул = ЧистыйАртикул;
        НоваяСтрока.Цена    = Число(СтрокаИмпорта.Цена);
        
    КонецЦикла;
    
    Возврат ТЗ_Очищенная;
    
КонецФункции

Шаг 3. Передача таблицы значений в запрос и сопоставление с Номенклатурой

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

Важно помнить правила работы с таблицами значений в запросах 1С:

Проанализируем текст и структуру оптимального запроса:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ВремТЗ.Артикул КАК Артикул,
    |	ВремТЗ.Цена КАК Цена
    |ПОМЕСТИТЬ ВТ_ДанныеФайла
    |ИЗ
    |	&ТаблицаДанных КАК ВремТЗ
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |	ВТ_ДанныеФайла.Артикул КАК Артикул,
    |	ВТ_ДанныеФайла.Цена КАК Цена,
    |	ЕСТЬNULL(СпрНоменклатура.Ссылка, ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)) КАК Номенклатура
    |ИЗ
    |	ВТ_ДанныеФайла КАК ВТ_ДанныеФайла
    |		ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК СпрНоменклатура
    |		ПО ВТ_ДанныеФайла.Артикул = СпрНоменклатура.Артикул
    |ГДЕ
    |	НЕ СпрНоменклатура.ПометкаУдаления";

Запрос.УстановитьПараметр("ТаблицаДанных", ТЗ_Очищенная);
РезультатСопоставления = Запрос.Выполнить().Выгрузить();

В данном запросе мы соединяем временную таблицу ВТ_ДанныеФайла со справочником Номенклатура по равенству полей артикулов. Условие ГДЕ НЕ СпрНоменклатура.ПометкаУдаления гарантирует, что мы не выберем устаревшие или помеченные на удаление дубли карточек товаров.

Шаг 4. Перенос сопоставленных данных в табличную часть формы

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

Чтобы избежать мерцания интерфейса и повысить скорость работы, перед заполнением рекомендуется очистить старые строки (если это предполагает бизнес-логика) и производить обход коллекции в одном цикле:


&НаСервере
Процедура ЗаполнитьТабличнуюЧастьНаСервере(РезультатСопоставления)
    
    // Получаем коллекцию строк табличной части формы
    ТабличнаяЧастьФормы = ЭтаФорма.БазовыеЦены;
    ТабличнаяЧастьФормы.Очистить();
    
    Для Каждого СтрокаРезультата Из РезультатСопоставления Цикл
        
        НоваяСтрока = ТабличнаяЧастьФормы.Добавить();
        НоваяСтрока.Артикул = СтрокаРезультата.Артикул;
        НоваяСтрока.Цена = СтрокаРезультата.Цена;
        
        Если ЗначениеЗаполнено(СтрокаРезультата.Номенклатура) Тогда
            НоваяСтрока.Номенклатура = СтрокаРезультата.Номенклатура;
        Иначе
            // Опционально: красивое информирование пользователя о ненайденном товаре
            ОбщегоНазначенияКлиентСервер.СообщитьПользователю(
                НСтр("ru = 'Внимание! Товар с артикулом """ + СтрокаРезультата.Aртикул + """ не найден в справочнике!'")
            );
        КонецЕсли;
        
    КонецЦикла;
    
КонецПроцедуры

Тонкости, о которых важно помнить разработчику

Рассмотрим несколько критических нюансов, которые часто приводят к ошибкам при сопоставлении артикулов на реальных проектах:

  1. Проблема типов данных «Строка» и «Число». Если в Excel артикулы состоят только из цифр (например, 000145), Excel может прочитать их как число 145 и отбросить ведущие нули. На стороне 1С артикул останется строковым типом "000145". В результате левое соединение вернет пустую ссылку. Решение: Либо настройте жесткий формат ячеек в Excel как текстовый, либо программно дополняйте числовые артикулы ведущими нулями с помощью функции Прав("00000000" + АртикулИзExcel, ДлинаАртикулаВБазе).
  2. Дублирование артикулов в справочнике. В отличие от кода, уникальность артикула в 1С по умолчанию не контролируется платформой. Если в вашей базе есть два разных товара с одинаковым артикулом, то при левом соединении СУБД размножит строки, и в документ добавится две одинаковых строки с разными товарами. Решение: Всегда анализируйте результат запроса на наличие дубликатов по артикулу, либо используйте группировку по максимуму/минимуму ссылки, чтобы гарантировать получение ровно одного сопоставленного элемента.
  3. Передача Таблицы Значений между контекстами. Помните, что тип ТаблицаЗначений не может существовать на клиенте в тонком клиенте 1С. Все операции с ТЗ (включая создание, заполнение параметрами запроса и сам запрос) должны производиться в процедурах с директивой &НаСервере или &НаСервереБезКонтекста.

Полный листинг готового модуля загрузки

Для удобства использования объединим все наши шаги в один красивый и структурированный рабочий блок кода, который вы можете перенести в модуль своей управляемой формы:


&НаКлиенте
Процедура ВыполнитьЗагрузку(Команда)
    
    ДиалогВыбора = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
    ДиалогВыбора.Заголовок = "Выберите файл для импорта";
    ДиалогВыбора.Фильтр = "Таблицы Excel (*.xls; *.xlsx)|*.xls; *.xlsx";
    
    Если ДиалогВыбора.Выбрать() Тогда
        ФайлИмя = ДиалогВыбора.ПолноеИмяФайла;
        ОбработатьИЗаполнитьНаСервере(ФайлИмя);
        ПоказатьПредупреждение(, "Загрузка и сопоставление номенклатуры успешно завершены!");
    КонецЕсли;
    
КонецПроцедуры

&НаСервере
Процедура ОбработатьИЗаполнитьНаСервере(ПутьКФайлу)
    
    // Шаг 1 и 2: Чтение и чистка данных
    ТаблицаДанных = ПодготовитьТаблицуИзФайла(ПутьКФайлу);
    
    // Шаг 3: Сопоставление через СУБД
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	ВремТЗ.Артикул КАК Артикул,
        |	ВремТЗ.Цена КАК Цена
        |ПОМЕСТИТЬ ВТ_ДанныеФайла
        |ИЗ
        |	&ТаблицаДанных КАК ВремТЗ
        |;
        |
        |////////////////////////////////////////////////////////////////////////////////
        |ВЫБРАТЬ
        |	ВТ_ДанныеФайла.Артикул КАК Артикул,
        |	ВТ_ДанныеФайла.Цена КАК Цена,
        |	ЕСТЬNULL(СпрНоменклатура.Ссылка, ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)) КАК Номенклатура
        |ИЗ
        |	ВТ_ДанныеФайла КАК ВТ_ДанныеФайла
        |		ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК СпрНоменклатура
        |		ПО ВТ_ДанныеФайла.Артикул = СпрНоменклатура.Артикул
        |ГДЕ
        |	НЕ СпрНоменклатура.ПометкаУдаления";
        
    Запрос.УстановитьПараметр("ТаблицаДанных", ТаблицаДанных);
    РезультатЗапроса = Запрос.Выполнить().Выгрузить();
    
    // Шаг 4: Заполнение управляемой формы
    ТабличнаяЧастьФормы = ЭтаForm.БазовыеЦены;
    ТабличнаяЧастьФормы.Очистить();
    
    Для Каждого СтрокаДанных Из РезультатЗапроса Цикл
        
        НоваяСтрока = ТабличнаяЧастьФормы.Добавить();
        НоваяСтрока.Артикул = СтрокаДанных.Артикул;
        НоваяСтрока.Цена = СтрокаДанных.Цена;
        
        Если ЗначениеЗаполнено(СтрокаДанных.Номенклатура) Тогда
            НоваяСтрока.Номенклатура = СтрокаДанных.Номенклатура;
        Иначе
            Сообщение = Новый СообщениеПользователю;
            Сообщение.Текст = "Не найден товар с артикулом: " + СтрокаДанных.Артикул;
            Сообщение.Сообщить();
        КонецЕсли;
        
    КонецЦикла;
    
КонецПроцедуры

&НаСервере
Функция ПодготовитьТаблицуИзФайла(ПутьКФайлу)
    
    ТабДок = Новый ТабличныйДокумент;
    ТабДок.Прочитать(ПутьКФайлу, СпособЧтенияЗначенийТабличногоДокумента.Значение);
    
    Построитель = Новый ПостроительЗапроса;
    Построитель.ИсточникДанных = Новый ОписаниеИсточникаДанных(ТабДок.Область());
    Построитель.Выполнить();
    
    ТЗ_Исходная = Построитель.Результат.Выгрузить();
    
    ТЗ_Очищенная = Новый ТаблицаЗначений;
    ТЗ_Очищенная.Колонки.Добавить("Артикул", Новый ОписаниеТипов("Строка", Новый КвалификаторыСтроки(100)));
    ТЗ_Очищенная.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
    
    Для Каждого Стр Из ТЗ_Исходная Цикл
        
        Если НЕ ЗначениеЗаполнено(Стр.Артикул) Тогда
            Продолжить;
        КонецЕсли;
        
        НоваяСтрока = ТЗ_Очищенная.Добавить();
        
        // Очищаем артикул от всех видов пробелов
        ЧистыйАртикул = Строка(Стр.Артикул);
        ЧистыйАртикул = СтрЗаменить(ЧистыйАртикул, Символ(160), "");
        ЧистыйАртикул = СтрЗаменить(ЧистыйАртикул, " ", "");
        ЧистыйАртикул = СокрЛП(ЧистыйАртикул);
        
        НоваяСтрока.Артикул = ЧистыйАртикул;
        НоваяСтрока.Цена = Число(Стр.Цена);
        
    КонецЦикла;
    
    Возврат ТЗ_Очищенная;
    
КонецФункции

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

← На главную