Интеграция 1С с внешними источниками данных, такими как электронные таблицы Excel, — одна из самых частых задач в практике разработчика. На первый взгляд алгоритм выглядит просто: прочитать файл, найти по коду товар в справочнике и заполнить табличную часть документа. Однако на практике программисты регулярно сталкиваются со скрытыми подводными камнями: невидимыми пробелами, неожиданной трансформацией типов данных платформой 1С и критическим снижением производительности базы данных.
В рамках этой статьи мы подробно разберем, почему стандартный поиск по коду может возвращать пустое значение, как бороться с неразрывными пробелами, исправим распространенные ошибки сопоставления типов для перечислений и рассмотрим современные высокопроизводительные альтернативы устаревшему COM-соединению.
Разберем типичную ситуацию. В коде обработки прописано чтение ячейки Excel и последующий поиск элемента:
КодТовара = СокрЛ(Excel.Cells(СчСтроки, НомерКолонкиТовара).Value);
СтрокаТовар = Строка(КодТовара);
ПолучитьТовар = НайтиТоварПоКоду(СтрокаТовар);
В отладчике код выглядит как 47 808 550, в то время как в справочнике 1С этот же код записан как 47808550. Попытки применить метод СтрЗаменить(КодТовара, " ", "") ни к чему не приводят — пробелы остаются на месте, а функция поиска стабильно возвращает «Товар не найден».
Выясним причину этого поведения. Она кроется в автоматическом преобразовании типов платформой 1С:
Строка(КодТовара), платформа применяет к нему стандартное региональное форматирование. По умолчанию для чисел в 1С настроена группировка триад (разделение тысяч).Символы.НПП).Именно поэтому стандартная замена обычного пробела СтрЗаменить(СтрокаТовара, " ", "") не дает никакого результата — она просто не находит нужный символ в строке.
Для гарантированного приведения числового кода к строковому виду без лишних разделителей групп существует два надежных метода.
Способ 1. Использование функции Формат (Рекомендуемый)
Самый изящный способ — сразу форматировать число при преобразовании в строку, явно указав платформе, что группировка не требуется (параметр ЧГ=0):
КодИзExcel = Excel.Cells(СчСтроки, НомерКолонкиТовара).Value;
СтрокаКода = Формат(КодИзExcel, "ЧГ=0");
// Теперь СтрокаКода гарантированно содержит "47808550" вместо "47 808 550"
Способ 2. Явное удаление неразрывных пробелов
Если в коде по какой-то причине уже получена строка с разделителями, мы можем принудительно удалить из нее все неразрывные пробелы:
ТекущийКод = Строка(КодТовара);
// Удаляем неразрывные пробелы
ТекущийКод = СтрЗаменить(ТекущийКод, Символы.НПП, "");
// Дополнительно страхуемся от обычных пробелов
ТекущийКод = СтрЗаменить(ТекущийКод, " ", "");
Использование любого из этих вариантов позволит функции НайтиПоКоду() успешно сопоставить строку с кодом в базе данных.
Рассмотрим вторую проблему, часто возникающую при сопоставлении реквизитов документов. Допустим, в Excel есть колонка «Валюта», принимающая числовые значения (1 — доллар, 2 — сум). Разработчик пытается сопоставить эти данные с перечислением в 1С:
Функция ПолучитьВалюту(Вал) Экспорт
Если Вал = 1 Тогда
Возврат Перечисления.Валюты.Доллар;
КонецЕсли;
Если Вал = 2 Тогда
Возврат Перечисления.Валюты.Сум;
КонецЕсли;
КонецФункции
// В процедуре обработки:
Валюта = СокрЛП(Excel.Cells(СчСтроки, НомерКолонкиВалюты).Value);
Если Валюта = ОбщегоНазначения.ПолучитьВалюту(1) Тогда
РаботаСДокументами.РасчетСуммыСКурсом(НоваяСтрока, КурсДоллара);
КонецЕсли;
При выполнении этого кода сумма расчетов в отладчике всегда равна нулю, так как условие внутри Если ... Тогда игнорируется. Проанализируем, почему это происходит.
Переменная Валюта хранит строковое значение "1" (или число 1), полученное из Excel. В то же время функция ПолучитьВалюту(1) возвращает объект типа ПеречислениеСсылка.Валюты (в данном случае — Перечисления.Валюты.Доллар). Платформа 1С не может напрямую сравнить ссылку на объект базы данных и примитивный тип (строку или число). Такое сравнение всегда возвращает значение Ложь.
Чтобы сравнение отрабатывало корректно, нам необходимо сравнивать однотипные данные. Перепишем логику сопоставления. Сначала получим числовое значение из Excel, а затем сравним ссылки:
// Приводим считанное из Excel значение к числу
ЗначениеВалютыExcel = Число(Excel.Cells(СчСтроки, НомерКолонкиВалюты).Value);
// Получаем ссылку на элемент перечисления в 1С
ВыбраннаяВалюта = ПолучитьВалюту(ЗначениеВалютыExcel);
// Теперь сравниваем ссылку со ссылкой
Если ВыбраннаяВалюта = Перечисления.Валюты.Доллар Тогда
РаботаСДокументами.РасчетСуммыСКурсом(НоваяСтрока, КурсДоллара);
КонецЕсли;
Обратим внимание на архитектурную ошибку в исходном коде процедуры ОбработкаExcel. В цикле обхода строк файла для каждого товара вызывается серверная функция поиска:
Для СчСтроки = 2 По ФайлСтрок Цикл
...
ПолучитьТовар = НайтиТоварПоКоду(СтрокаТовар);
...
КонецЦикла;
Под капотом функции НайтиПоКоду() платформа выполняет прямой SQL-запрос к базе данных. Если в загружаемом файле содержится 5 000 строк, система совершит 5 000 отдельных запросов к СУБД. Это классический антипаттерн «запрос в цикле», который приводит к лавинообразному падению производительности и зависанию сеанса пользователя.
Разберем правильный, профессиональный подход к оптимизации этой операции. Вместо поиска каждого товара по отдельности выполним сопоставление пакетно:
Соответствие, где ключом будет выступать код товара (строка), а значением — ссылка на номенклатуру.Посмотрим на пример реализации такого подхода:
&НаСервере
Функция ПолучитьСоответствиеНоменклатурыПоКодам(МассивКодов)
СоответствиеКодов = Новый Соответствие;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Код КАК Код
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Код В(&МассивКодов)";
Запрос.УстановитьПараметр("МассивКодов", МассивКодов);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
СоответствиеКодов.Вставить(Выборка.Код, Выборка.Ссылка);
КонецЦикла;
Возврат СоответствиеКодов;
КонецФункции
Использование этого метода сократит время загрузки больших файлов с нескольких минут до пары секунд.
Использование технологии COM (`COMОбъект("Excel.Application")`) имеет огромные недостатки в современных реалиях разработки:
Начиная с версии 8.3, платформа 1С предоставляет мощный встроенный инструмент — ТабличныйДокумент, который умеет напрямую считывать файлы форматов .xls и .xlsx без использования внешних библиотек и стороннего ПО. Это решение полностью кроссплатформенно и работает в разы быстрее.
Рассмотрим пример загрузки данных с использованием ТабличногоДокумента:
&НаКлиенте
Процедура ЗагрузитьФайлБезExcel(ПутьКФайлу)
ТабДок = Новый ТабличныйДокумент;
Попытка
// Метод считывает файл напрямую с диска
ТабДок.Прочитать(ПутьКФайлу, СпособЧтенияЗначенийТабличногоДокумента.Значение);
Исключение
ПоказатьПредупреждение(, "Не удалось прочитать файл! Возможно, он поврежден или открыт в другой программе.");
Возврат;
КонецПопытки;
// Передаем табличный документ на сервер для пакетной обработки
ОбработатьТабличныйДокументНаСервере(ТабДок);
КонецПроцедуры
&НаСервере
Процедура ОбработатьТабличныйДокументНаСервере(ТабДок)
КоличествоСтрок = ТабДок.ВысотаТаблицы;
МассивКодов = Новый Массив;
ТаблицаДанных = Новый ТаблицаЗначений;
ТаблицаДанных.Колонки.Добавить("Код", Новый ОписаниеТипов("Строка"));
ТаблицаДанных.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
// Шаг 1. Собираем данные в таблицу и формируем массив для поиска
Для НомерСтроки = 2 По КоличествоСтрок Цикл
// Получаем чистые значения из ячеек
КодТовараЧисло = ТабДок.ПолучитьОбласть("R" + Формат(НомерСтроки, "ЧГ=0") + "C1").ТекущаяОбласть.Значение;
Количество = ТабДок.ПолучитьОбласть("R" + Формат(НомерСтроки, "ЧГ=0") + "C2").ТекущаяОбласть.Значение;
Если ЗначениеЗаполнено(КодТовараЧисло) Тогда
// Преобразуем код к строке без пробелов
СтрокаКода = Формат(КодТовараЧисло, "ЧГ=0");
НоваяСтрока = ТаблицаДанных.Добавить();
НоваяСтрока.Код = СтрокаКода;
НоваяСтрока.Количество = Число(Количество);
МассивКодов.Добавить(СтрокаКода);
КонецЕсли;
КонецЦикла;
// Шаг 2. Получаем соответствие номенклатуры одним запросом
СоответствиеНоменклатуры = ПолучитьСоответствиеНоменклатурыПоКодам(МассивКодов);
// Шаг 3. Заполняем табличную часть документа из таблицы данных
Для Каждого СтрокаДанных Из ТаблицаДанных Цикл
НайденнаяСсылка = СоответствиеНоменклатуры.Получить(СтрокаДанных.Код);
Если НайденнаяСсылка <> Неопределено Тогда
НоваяСтрокаДок = Объект.ПрайсЛист.Добавить();
НоваяСтрокаДок.Номенклатура = НайденнаяСсылка;
НоваяСтрокаДок.Количество = СтрокаДанных.Количество;
Иначе
ОбщегоНазначенияКлиентСервер.СообщитьПользователю(
НСтр("ru = 'Товар с кодом " + СтрокаДанных.Код + " не найден в справочнике!'")
);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Применение предложенных подходов решает сразу несколько ключевых проблем: гарантирует стабильное чтение кодов любой длины независимо от региональных настроек ОС, исключает падение скорости работы СУБД за счет оптимизации запросов и обеспечивает полную кроссплатформенность решения при работе в веб-клиенте или на серверах под Linux.