При разработке механизмов импорта данных из внешних файлов, таких как Excel, программисты часто сталкиваются с проблемой появления дубликатов записей в справочниках. Ситуация усугубляется, когда код ведет себя непредсказуемо: поиск существующей номенклатуры не срабатывает, а счетчик обрабатываемых строк внезапно «сбрасывается» или начинает идти по второму кругу. Разберем подробно, почему это происходит и как выстроить надежную архитектуру загрузки.
Проанализируем представленный в теме код. Главная методическая ошибка заключается в том, что процедура создания элементов справочника Номенклатура помещена в обработчик события ДанныеЛистаПриВыводеСтроки. Рассмотрим подробнее, к чему это приводит:
ПриВыводеСтроки предназначено исключительно для визуального оформления табличного поля (например, изменения цвета текста или подстановки картинки в ячейку). Оно вызывается системой 1С каждый раз, когда строка отрисовывается на экране. Это происходит при прокрутке списка, при изменении размера окна, при сворачивании и даже если вы просто перекрыли окно 1С другим приложением и вернулись обратно.ЭлементыФормы.ДанныеЛиста.ОбновитьСтроки(). Это провоцирует принудительную перерисовку всех видимых строк, что снова вызывает событие ПриВыводеСтроки. В итоге программа попадает в бесконечный цикл или выполняет одну и ту же операцию записи данных десятки раз для одной строки.ДанныеСтроки «прыгает» или возвращается к началу.Важное правило: Никогда не используйте события отрисовки интерфейса (ПриВыводеСтроки, ПриПолученииДанных) для модификации данных в базе данных (создание, изменение, удаление объектов).
Часто программисты жалуются, что метод НайтиПоНаименованию() не находит элемент, хотя визуально строки в Excel и в 1С идентичны. Выясним причину этой ситуации. Текст, получаемый из ячеек ТабличногоДокумента, может содержать невидимые спецсимволы:
Для 1С строки "Товар" и "Товар " — это разные значения. Функция СокрЛП() удаляет только стандартные пробелы. Посмотрим на пример кода, который делает поиск более надежным:
// Очистка строки от специфических символов Excel
ТекНаименование = СокрЛП(Наименование);
ТекНаименование = СтрЗаменить(ТекНаименование, Символы.НПП, ""); // Убираем неразрывный пробел
ТекНаименование = СтрЗаменить(ТекНаименование, Символы.ПС, ""); // Убираем переносы строк
ИмеющеесяНаименование = Справочники.Номенклатура.НайтиПоНаименованию(ТекНаименование, Истина);
Если ИмеющеесяНаименование.Пустая() Тогда
// Создаем новый элемент
Иначе
// Элемент найден, пропускаем или обновляем
КонецЕсли;
Обратите внимание на второй параметр функции НайтиПоНаименованию. Использование Истина гарантирует поиск по полному соответствию наименования, что критично для предотвращения дублей.
В исходном коде автора внутри цикла (события) каждый раз вызывается чтение файла с диска: ТабДок.Прочитать(ЭтотОбъект.ИмяФайла). Если в файле 1000 строк, и каждая отрисовывается по 5 раз, система прочитает файл 5000 раз. Это неизбежно приведет к зависанию системы.
Разберем правильный алгоритм загрузки:
ТабличныйДокумент или таблицу значений.Для каждого по строкам полученных данных.Рассмотрим, как должен выглядеть надежный код загрузки номенклатуры с картинками. Мы откажемся от событий интерфейса и перейдем к линейной обработке данных.
Процедура ВыполнитьЗагрузкуПоКнопке()
ТабДок = Новый ТабличныйДокумент;
Попытка
ТабДок.Прочитать(Объект.ПутьКФайлу, СпособЧтенияЗначенийТабличногоДокумента.Значение);
Исключение
Сообщить("Ошибка чтения файла: " + ОписаниеОшибки());
Возврат;
КонецПопытки;
// Определяем границы загрузки
ВсегоСтрок = ТабДок.ВысотаТаблицы;
Для НомерСтроки = НомерПервойСтроки По ВсегоСтрок Цикл
НаименованиеИзФайла = ТабДок.ПолучитьОбласть("R" + Формат(НомерСтроки, "ЧГ=0") + "C" + НомерКолонкиНаименования).ТекущаяОбласть.Текст;
НаименованиеЧистое = СокрЛП(НаименованиеИзФайла);
// Поиск существующей номенклатуры
СсылкаНоменклатура = Справочники.Номенклатура.НайтиПоНаименованию(НаименованиеЧистое, Истина);
Если СсылкаНоменклатура.Пустая() Тогда
НовыйОбъект = Справочники.Номенклатура.СоздатьЭлемент();
НовыйОбъект.Наименование = НаименованиеЧистое;
// Заполнение остальных реквизитов...
НовыйОбъект.Записать();
СсылкаНоменклатура = НовыйОбъект.Ссылка;
Сообщить("Создан новый элемент: " + НаименованиеЧистое);
КонецЕсли;
// Обработка картинок
ОбластьКартинки = ТабДок.Область(НомерСтроки, НомерКолонкиКартинки);
Для Каждого Рисунок Из ТабДок.Рисунки Цикл
// Проверяем, находится ли рисунок в границах нужной ячейки
Если Рисунок.Расположение.Лево = ОбластьКартинки.Лево И Рисунок.Расположение.Верх = ОбластьКартинки.Верх Тогда
ЗагрузитьКартинкуВНоменклатуру(СсылкаНоменклатура, Рисунок.Картинка);
Прервать;
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецПроцедуры
При загрузке изображений важно помнить, что в типовых конфигурациях (таких как УПП, КА 1.1 или старые версии БП) картинки хранятся в отдельном справочнике ХранилищеДополнительнойИнформации. Чтобы связать картинку с номенклатурой, нужно выполнить несколько шагов:
Картинка из данных рисунка табличного документа.ХранилищеДополнительнойИнформации.ХранилищеЗначения.ОсновноеИзображение.Проанализируем ситуацию с ОсновнымИзображением. Если вы записываете картинку при каждом «выводе строки», вы не только создаете дубли номенклатуры, но и забиваете базу данных гигабайтами одинаковых файлов изображений, так как для каждой отрисовки создается новый объект в справочнике хранилища.
Подведем итог нашему разбору. Чтобы загрузка номенклатуры работала корректно и без дублей:
НайтиПоНаименованию с параметром ТочноеСоответствие = Истина и предварительной очисткой строки от мусорных символов.НайтиПоРеквизиту("Артикул", ...)), так как это более надежный идентификатор, чем наименование.Соблюдение этих правил позволит вам создавать быстрые и надежные механизмы импорта, которые не портят данные в информационной базе.