Импорт данных из внешних файлов — одна из самых частых задач в практике программиста 1С. На первый взгляд алгоритм кажется очевидным: прочитать таблицу Excel, обойти строки в цикле и создать элементы справочника. Однако при попытке реализовать это на практике разработчики сталкиваются с целым рядом трудностей. Как правильно определить, кем является контрагент — юридическим или физическим лицом? Как создать для него подчиненный договор и связать их в базе данных? Как корректно записать сразу несколько видов контактной информации?
В этой статье мы подробно разберем типичные ошибки начинающих разработчиков при импорте контрагентов из Excel и напишем надежный, оптимизированный алгоритм для решения этой задачи.
Проанализируем исходный код, с которым у автора темы возникли сложности, и выделим ключевые проблемные места, которые мешали корректной работе обработки.
Ошибка 1. Логическое условие проверки типа контрагента
В исходном коде была предпринята попытка определить тип контрагента (ЮрЛицо или ФизЛицо) следующим образом:
Если НовыйОбъект.ЮрФизЛицо = СтрДлина("12345678") И НовыйОбъект.ЮрФизЛицо <> Неопределено Тогда
НовыйОбъект.ЮрФизЛицо = Перечисления.ЮрФизЛицо.ФизЛицо;
Иначе
НовыйОбъект.ЮрФизЛицо = Перечисления.ЮрФизЛицо.ЮрЛицо;
КонецЕсли;
Разберем, почему эта конструкция абсолютно неработоспособна. Во-первых, функция СтрДлина("12345678") всегда возвращает числовое значение 8 (длину переданной строки-литерала). Во-вторых, реквизит ЮрФизЛицо имеет тип ссылки на перечисление ПеречислениеСсылка.ЮрФизЛицо. Сравнение ссылки на перечисление с числом 8 всегда будет возвращать Ложь. Нам необходимо проверять длину реального кода ЕДРПОУ (или ИНН в российских конфигурациях), полученного из Excel.
Ошибка 2. Использование «ЭтотОбъект» вместо «Ссылка»
Для создания связи между контрагентом и договором или контактной информацией автор использовал конструкцию:
КонтрагентСсылка = НовыйОбъект.ЭтотОбъект;
Свойство ЭтотОбъект возвращает сам прикладной объект типа СправочникОбъект.Контрагенты, находящийся в оперативной памяти. Однако реквизит Владелец подчиненного справочника Договоры или измерение Объект в регистре сведений требуют уникальный идентификатор записи в базе данных — то есть объект типа СправочникСсылка.Контрагенты. Передача объекта вместо ссылки приводит к ошибкам записи подчиненных сущностей.
Ошибка 3. Отсутствие вызова метода записи договора
В коде создавался новый элемент справочника ДоговорыКонтрагентов, заполнялись его реквизиты, но в конце процедуры отсутствовал вызов метода НовыйДоговор.Записать(). Без этого вызова объект существовал только в оперативной памяти и бесследно уничтожался после завершения работы процедуры.
Ошибка 4. Некорректная запись контактной информации
Для сохранения телефона использовался менеджер записи регистра сведений. Такой подход допустим для одиночной записи, но если у одного контрагента нужно сохранить несколько видов контактной информации (например, телефон, электронную почту и адрес), менеджер записи начнет перезаписывать или блокировать предыдущие данные. Правильный путь — использование набора записей с установленным отбором по объекту-владельцу.
---
В исходном примере используется подключение через COM-соединение COMОбъект("Excel.Application"). Этот метод имеет критические недостатки: он требует обязательного наличия установленного Microsoft Excel на локальном компьютере, не работает в среде Linux (на сервере 1С) и работает медленно.
Рассмотрим современный, быстрый и кроссплатформенный способ чтения файлов Excel с помощью встроенного объекта ТабличныйДокумент:
ТабДок = Новый ТабличныйДокумент;
Попытка
ТабДок.Прочитать(ИмяГлавногоФайла, СпособЧтенияЗначенийТабличногоДокумента.Значение);
Исключение
Сообщить("Не удалось прочитать файл Excel: " + ОписаниеОшибки());
Возврат;
КонецПопытки;
// Создаем таблицу значений для обработки данных
тзКонтр = Новый ТаблицаЗначений;
тзКонтр.Колонки.Добавить("Код");
тзКонтр.Колонки.Добавить("Наименование");
тзКонтр.Колонки.Добавить("ПолноеНаименование");
тзКонтр.Колонки.Добавить("КодПоЭГРПОУ");
тзКонтр.Колонки.Добавить("Телефон");
тзКонтр.Колонки.Добавить("Почта");
тзКонтр.Колонки.Добавить("Валюта");
// Обходим строки табличного документа (начиная со 2-й строки, так как 1-я — шапка)
Для Строка = 2 По ТабДок.ВысотаТаблицы Цикл
НоваяСтрока = тзКонтр.Добавить();
НоваяСтрока.Код = ТабДок.ПолучитьОбласть(Строка, 1).ТекущийОбласть.Текст;
НоваяСтрока.Наименование = ТабДок.ПолучитьОбласть(Строка, 3).ТекущийОбласть.Текст;
НоваяСтрока.ПолноеНаименование = ТабДок.ПолучитьОбласть(Строка, 19).ТекущийОбласть.Текст;
НоваяСтрока.КодПоЭГРПОУ = СокрЛП(ТабДок.ПолучитьОбласть(Строка, 16).ТекущийОбласть.Текст);
НоваяСтрока.Телефон = ТабДок.ПолучитьОбласть(Строка, 10).ТекущийОбласть.Текст;
НоваяСтрока.Почта = ТабДок.ПолучитьОбласть(Строка, 11).ТекущийОбласть.Текст;
НоваяСтрока.Валюта = ТабДок.ПолучитьОбласть(Строка, 15).ТекущийОбласть.Текст;
КонецЦикла;
---
Выясним причину, как правильно распределить контрагентов по категориям. Согласно законодательству (в частности, украинскому учету, где фигурирует термин ЕГРПОУ/ЕДРПОУ):
Запишем корректное условие проверки длины кода, полученного из таблицы данных:
КодОчищенный = СокрЛП(СтрокаТаблицы.КодПоЭГРПОУ);
ДлинаКода = СтрДлина(КодОчищенный);
Если ДлинаКода = 8 Тогда
НовыйОбъект.ЮрФизЛицо = Перечисления.ЮрФизЛицо.ЮрЛицо;
ИначеЕсли ДлинаКода = 10 Или ПустаяСтрока(КодОчищенный) Тогда
НовыйОбъект.ЮрФизЛицо = Перечисления.ЮрФизЛицо.ФизЛицо;
Иначе
// Резервный вариант, если длина кода нестандартная
НовыйОбъект.ЮрФизЛицо = Перечисления.ЮрФизЛицо.ЮрЛицо;
КонецЕсли;
---
Чтобы подчиненные объекты (договоры и контактная информация) успешно создавались, объект «Контрагент» должен быть физически записан в базу данных. Только в момент вызова метода Записать() СУБД генерирует для элемента уникальный идентификатор (UID), который возвращается через свойство Ссылка.
Проанализируем правильную последовательность действий:
Справочники.Контрагенты.СоздатьЭлемент().Записать().КонтрагентСсылка = НовыйОбъект.Ссылка.---
Для записи телефонов, адресов и электронной почты контрагента воспользуемся объектом РегистрСведенийНаборЗаписей. Это гарантирует, что мы сможем добавить несколько разных видов контактной информации за один раз, не нарушая целостность существующих данных.
Разберем программную реализацию этого шага по этапам:
// Создаем набор записей регистра сведений "КонтактнаяИнформация"
НаборЗаписейКИ = РегистрыСведений.КонтактнаяИнформация.СоздатьНаборЗаписей();
// Устанавливаем жесткий отбор по нашему созданному контрагенту
НаборЗаписейКИ.Отбор.Объект.Установить(КонтрагентСсылка);
// Добавляем запись для номера телефона
Если НЕ ПустаяСтрока(СтрокаТаблицы.Телефон) Тогда
НоваяЗапись = НаборЗаписейКИ.Добавить();
НоваяЗапись.Объект = КонтрагентСсылка;
НоваяЗапись.Тип = Перечисления.ТипыКонтактнойИнформации.Телефон;
НоваяЗапись.Вид = Справочники.ВидыКонтактнойИнформации.ТелефонКонтрагента;
НоваяЗапись.Представление = СтрокаТаблицы.Телефон;
НоваяЗапись.Поле1 = СтрокаТаблицы.Телефон; // Дополнительное заполнение для совместимости
КонецЕсли;
// Добавляем запись для адреса электронной почты
Если НЕ ПустаяСтрока(СтрокаТаблицы.Почта) Тогда
НоваяЗапись = НаборЗаписейКИ.Добавить();
НоваяЗапись.Объект = КонтрагентСсылка;
НоваяЗапись.Тип = Перечисления.ТипыКонтактнойИнформации.АдресЭлектроннойПочты;
НоваяЗапись.Вид = Справочники.ВидыКонтактнойИнформации.EmailКонтрагента;
НоваяЗапись.Представление = СтрокаТаблицы.Почта;
КонецЕсли;
// Записываем весь набор данных для контрагента в базу данных
НаборЗаписейКИ.Записать();
---
При создании договора обязательно нужно назначить его владельца (ссылку на контрагента), заполнить обязательные аналитические разрезы взаиморасчетов и вызвать метод записи. Рассмотрим пример заполнения:
НовыйДоговор = Справочники.ДоговорыКонтрагентов.СоздатьЭлемент();
НовыйДоговор.Владелец = КонтрагентСсылка; // Передаем именно Ссылку!
НовыйДоговор.Наименование = "Основной договор";
// Определяем валюту договора. Если в файле валюта не указана, берем регламентированную (например, рубль или гривна)
ВалютаДоговора = Справочники.Валюты.НайтиПоНаименованию(СтрокаТаблицы.Валюта);
Если ВалютаДоговора.Пустая() Тогда
ВалютаДоговора = Справочники.Валюты.НайтиПоКоду("643"); // Пример для Рубля
КонецЕсли;
НовыйДоговор.ВалютаВзаиморасчетов = ВалютаДоговора;
// Заполняем параметры ведения расчетов
НовыйДоговор.ВедениеВзаиморасчетов = Перечисления.ВедениеВзаиморасчетовПоДоговорам.ПоДоговоруВЦелом;
НовыйДоговор.ВидДоговора = Перечисления.ВидыДоговоровКонтрагентов.СПокупателем;
// Обязательно записываем договор в базу данных!
Попытка
НовыйДоговор.Записать();
Исключение
Сообщить("Не удалось создать договор для контрагента: " + КонтрагентСсылка + ". Ошибка: " + ОписаниеОшибки());
КонецПопытки;
---
Для ускорения процесса записи больших объемов данных (от 1000 контрагентов) рекомендуется использовать транзакции. Обернув весь цикл создания в одну транзакцию, мы ускорим работу обработки в 5–10 раз, снизив нагрузку на жесткий диск и СУБД. Ниже представлен полностью готовый, рабочий код процедуры импорта для обычных форм:
Процедура КнопкаВыполнитьНажатие(Кнопка)
ИмяФайла = СокрЛП(ИмяГлавногоФайла);
Если ПустаяСтрока(ИмяФайла) Тогда
Предупреждение("Укажите файл для импорта данных!");
Возврат;
КонецЕсли;
// Чтение файла Excel через ТабличныйДокумент
ТабДок = Новый ТабличныйДокумент;
Попытка
ТабДок.Прочитать(ИмяФайла, СпособЧтенияЗначенийТабличногоДокумента.Значение);
Исключение
Сообщить("Ошибка чтения файла: " + ОписаниеОшибки());
Возврат;
КонецПопытки;
// Подготовка таблицы значений
тзКонтр = Новый ТаблицаЗначений;
тзКонтр.Колонки.Добавить("Код");
тзКонтр.Колонки.Добавить("Наименование");
тзКонтр.Колонки.Добавить("ПолноеНаименование");
тзКонтр.Колонки.Добавить("КодПоЭГРПОУ");
тзКонтр.Колонки.Добавить("Телефон");
тзКонтр.Колонки.Добавить("Почта");
тзContr.Колонки.Добавить("Валюта");
// Заполняем таблицу значений данными из документа Excel
Для Строка = 2 По ТабДок.ВысотаТаблицы Цикл
НоваяСтрока = тзКонтр.Добавить();
НоваяСтрока.Код = ТабДок.ПолучитьОбласть(Строка, 1).ТекущаяОбласть.Текст;
НоваяСтрока.Наименование = ТабДок.ПолучитьОбласть(Строка, 3).ТекущийОбласть.Текст;
НоваяСтрока.ПолноеНаименование = ТабДок.ПолучитьОбласть(Строка, 19).ТекущийОбласть.Текст;
НоваяСтрока.КодПоЭГРПОУ = СокрЛП(ТабДок.ПолучитьОбласть(Строка, 16).ТекущийОбласть.Текст);
НоваяСтрока.Телефон = ТабДок.ПолучитьОбласть(Строка, 10).ТекущийОбласть.Текст;
НоваяСтрока.Почта = ТабДок.ПолучитьОбласть(Строка, 11).ТекущийОбласть.Текст;
НоваяСтрока.Валюта = ТабДок.ПолучитьОбласть(Строка, 15).ТекущийОбласть.Текст;
КонецЦикла;
// Вывод таблицы на форму для визуального контроля пользователем
ЭлементыФормы.ТабличноеПоле1.Значение = тзКонтр;
ЭлементыФормы.ТабличноеПоле1.СоздатьКолонки();
// Запускаем процесс создания объектов в базе данных
СозданиеОбъектовБазыДанных(тзКонтр);
КонецПроцедуры
Процедура СозданиеОбъектовБазыДанных(ТаблицаДанных)
РодительПокупатели = Справочники.Контрагенты.НайтиПоНаименованию("Покупатели");
РегионДефолт = Справочники.Регионы.НайтиПоКоду("000000001");
ОсновнаяОрганизация = УправлениеПользователями.ПолучитьЗначениеПоУмолчанию(глЗначениеПеременной("глТекущийПользователь"), "ОсновнаяОрганизация");
ТекстОшибок = "";
ВсегоСоздано = 0;
// Начинаем транзакцию для максимального ускорения записи
НачатьТранзакцию();
Попытка
Для Каждого СтрокаТаблицы Из ТаблицаДанных Цикл
// 1. Создание контрагента
НовыйОбъект = Справочники.Контрагенты.СоздатьЭлемент();
НовыйОбъект.Родитель = РодительПокупатели;
НовыйОбъект.Наименование = СтрокаТаблицы.Наименование;
НовыйОбъект.НаименованиеПолное = СтрокаТаблицы.ПолноеНаименование;
// Логика определения Юр/Физ лица по длине ЕГРПОУ
КодОчищенный = СокрЛП(СтрокаТаблицы.КодПоЭГРПОУ);
Если СтрДлина(КодОчищенный) = 8 Тогда
НовыйОбъект.ЮрФизЛицо = Перечисления.ЮрФизЛицо.ЮрЛицо;
Иначе
НовыйОбъект.ЮрФизЛицо = Перечисления.ЮрФизЛицо.ФизЛицо;
КонецЕсли;
НовыйОбъект.Покупатель = Истина;
НовыйОбъект.Регион = РегионДефолт;
НовыйОбъект.Код = СтрокаТаблицы.Код;
НовыйОбъект.КодПоЕДРПОУ = СтрокаТаблицы.КодПоЭГРПОУ;
Попытка
НовыйОбъект.Записать();
КонтрагентСсылка = НовыйОбъект.Ссылка;
Исключение
ТекстОшибок = ТекстОшибок + Символы.ПС + "Ошибка создания контрагента: " + СтрокаТаблицы.Наименование + " - " + ОписаниеОшибки();
Продолжить;
КонецПопытки;
// 2. Создание контактной информации
НаборКИ = РегистрыСведений.КонтактнаяИнформация.СоздатьНаборЗаписей();
НаборКИ.Отбор.Объект.Установить(КонтрагентСсылка);
Если НЕ ПустаяСтрока(СтрокаТаблицы.Телефон) Тогда
НоваяЗапись = НаборКИ.Добавить();
НоваяЗапись.Объект = КонтрагентСсылка;
НоваяЗапись.Тип = Перечисления.ТипыКонтактнойИнформации.Телефон;
НоваяЗапись.Вид = Справочники.ВидыКонтактнойИнформации.ТелефонКонтрагента;
НоваяЗапись.Представление = СтрокаТаблицы.Телефон;
НоваяЗапись.Поле1 = СтрокаТаблицы.Телефон;
КонецЕсли;
Если НЕ ПустаяСтрока(СтрокаТаблицы.Почта) Тогда
НоваяЗапись = НаборКИ.Добавить();
НоваяЗапись.Объект = КонтрагентСсылка;
НоваяЗапись.Тип = Перечисления.ТипыКонтактнойИнформации.АдресЭлектроннойПочты;
НоваяЗапись.Вид = Справочники.ВидыКонтактнойИнформации.EmailКонтрагента;
НоваяЗапись.Представление = СтрокаТаблицы.Почта;
КонецЕсли;
Если НаборКИ.Количество() > 0 Тогда
НаборКИ.Записать();
КонецЕсли;
// 3. Создание договора контрагента
НовыйДоговор = Справочники.ДоговорыКонтрагентов.СоздатьЭлемент();
НовыйДоговор.Организация = ОсновнаяОрганизация;
НовыйДоговор.Владелец = КонтрагентСсылка;
НовыйДоговор.Наименование = "Основной договор";
НовыйДоговор.ВедениеВзаиморасчетов = Перечисления.ВедениеВзаиморасчетовПоДоговорам.ПоДоговоруВЦелом;
ВалютаДоговора = Справочники.Валюты.НайтиПоНаименованию(СтрокаТаблицы.Валюта);
Если ВалютаДоговора.Пустая() Тогда
ВалютаДоговора = Справочники.Валюты.НайтиПоКоду("643");
КонецЕсли;
НовыйДоговор.ВалютаВзаиморасчетов = ВалютаДоговора;
НовыйДоговор.ВидДоговора = Перечисления.ВидыДоговоровКонтрагентов.СПокупателем;
Попытка
НовыйДоговор.Записать();
Исключение
ТекстОшибок = ТекстОшибок + Символы.ПС + "Ошибка создания договора для: " + СтрокаТаблицы.Наименование + " - " + ОписаниеОшибки();
КонецПопытки;
ВсегоСоздано = ВсегоСоздано + 1;
КонецЦикла;
// Фиксируем все изменения в базе данных
ЗафиксироватьТранзакцию();
Исключение
// В случае критической ошибки отменяем всю транзакцию полностью
ОтменитьТранзакцию();
Сообщить("Произошла критическая ошибка выполнения транзакции! Все изменения отменены. Ошибка: " + ОписаниеОшибки());
Возврат;
КонецПопытки;
// Вывод результатов пользователю
Сообщить("Импорт успешно завершен! Создано контрагентов: " + ВсегоСоздано);
Если НЕ ПустаяСтрока(ТекстОшибок) Тогда
Сообщить("В ходе импорта возникли следующие ошибки:" + ТекстОшибок);
КонецЕсли;
КонецПроцедуры
Рассмотренный нами алгоритм является полностью законченным и оптимизированным решением. Благодаря использованию нативного чтения файлов, транзакций и корректному извлечению объектных ссылок, данный код с легкостью справляется с загрузкой списков объемом в несколько тысяч контрагентов за считанные секунды.