Загрузка данных из внешних файлов Excel — одна из самых частых задач в практике программиста 1С. Однако при написании кода импорта легко допустить логические ошибки, которые приводят к неожиданному поведению системы. Типичный пример такой проблемы — остановка импорта с ошибкой «Значение поля "Номер" не уникально», даже если в самом файле Excel все номера уникальны и дублей нет.
В этой статье мы подробно разберем, почему возникает эта ошибка, проанализируем уязвимости в кодовой базе, разберем правильный вариант работы с COM-объектами и рассмотрим современную, более быструю и кроссплатформенную альтернативу загрузки через встроенный ТабличныйДокумент.
Выясним первопричину ошибки на примере кода, предоставленного автором темы. Разработчик пытается заполнить массив структур данными из листа Excel, а затем в цикле создать документы ПриходныйКассовыйОрдер (ПКО). Но при записи второго документа система выдает ошибку уникальности номера.
Проанализируем структуру исходного кода и найдем критические ошибки логики:
Обратите внимание на строки кода автора:
Номер = СокрЛП(Лист.Cells(Сч2, 2).Value);
ДатаДок = СокрЛП(Лист.Cells(Сч2, 3).Value);
// ... и так далее для остальных реквизитов
Этот блок находится вне цикла обхода строк Excel. Переменная Сч2 к моменту выполнения этого блока уже завершила свой внутренний цикл по колонкам и содержит фиксированное число (индекс последней колонки + 1). В результате программа считывает данные только один раз из одной случайной ячейки.
Поскольку чтение произошло один раз, переменные Номер, ДатаДокумента, Сумма и другие получили фиксированные значения. Далее в цикле создания документов Для Каждого Элемент Из МассивДанных Цикл программа пытается создать множество документов ПКО, присваивая им одни и те же значения реквизитов. Как только код пытается записать второй документ с тем же номером, платформа 1С блокирует транзакцию из-за нарушения уникальности номера.
В коде автора используется выражение "Колонка" + Строка(Сч2). В платформе 1С стандартная функция Строка(Число) при форматировании больших чисел (или при определенных региональных настройках системы) может добавлять неразрывные пробелы в качестве разделителей групп (например, превращая число 1000 в строку "1 000"). Это приводит к тому, что ключи структуры формируются некорректно, и при попытке обратиться к свойству Элемент.Колонка2 система возвращает пустые значения. В результате номер документа записывается как пустая строка, вызывая ошибку дублирования при создании следующего пустого документа.
Рассмотрим, как правильно перестроить логику чтения файла через COM-соединение, исправив все архитектурные ошибки. Мы объединим чтение ячеек и создание документов в один проход, обеспечим корректную обработку типов данных и добавим проверку на существующие в базе документы.
Выполним исправление по шагам:
Эксель = Новый COMОбъект("Excel.Application");
Попытка
Книга = Эксель.WorkBooks.Open(ИмяФайла);
Лист = Книга.WorkSheets(1);
Исключение
Сообщить("Не удалось открыть файл Excel! " + ОписаниеОшибки());
Эксель.Quit();
Возврат;
КонецПопытки;
// Получаем координаты заполненной области листа
ВсегоСтрок = Лист.Cells(1,1).SpecialCells(11).Row;
// Запускаем цикл обхода строк со второй строки (пропуская шапку таблицы)
Для Сч = 2 по ВсегоСтрок Цикл
// Считываем значения непосредственно внутри цикла для каждой строки
НомерСтрока = СокрЛП(Лист.Cells(Сч, 2).Value);
ДатаИзExcel = Лист.Cells(Сч, 3).Value;
СуммаДок = Число(Лист.Cells(Сч, 4).Value);
КассаНаименование = СокрЛП(Лист.Cells(Сч, 5).Value);
ОрганизацияНаименование = СокрЛП(Лист.Cells(Сч, 9).Value);
// Если номер строки пустой, пропускаем итерацию
Если ПустаяСтрока(НомерСтрока) Тогда
Продолжить;
КонецЕсли;
// Преобразуем дату. Если в Excel колонка имеет формат Даты, 1С получит ее корректно.
// Если дата прочитана как строка, выполним безопасное преобразование.
Если ТипЗнч(ДатаИзExcel) <> Тип("Дата") Тогда
Попытка
// Предполагаем стандартный формат даты ДД.ММ.ГГГГ
ДатаДокумента = Дата(Прав(ДатаИзExcel, 4) + Сред(ДатаИзExcel, 4, 2) + Лев(ДатаИзExcel, 2));
Исключение
ДатаДокумента = ТекущаяДата(); // Дефолтное значение при ошибке парсинга
КонецПопытки;
Иначе
ДатаДокумента = ДатаИзExcel;
КонецЕсли;
// Предотвращаем дублирование: ищем, нет ли уже такого документа в базе
ПКОСсылка = Документы.ПриходныйКассовыйОрдер.НайтиПоНомеру(НомерСтрока, ДатаДокумента);
Если ПКОСсылка.Пустая() Тогда
ПКО = Документы.ПриходныйКассовыйОрдер.СоздатьДокумент();
ПКО.Номер = НомерСтрока; // Заполняем номер только для новых документов
Иначе
// Если документ найден, мы можем обновить его или пропустить
ПКО = ПКОСсылка.ПолучитьОбъект();
КонецЕсли;
ПКО.Дата = ДатаДокумента;
ПКО.ХозяйственнаяОперация = Перечисления.ХозяйственныеОперации.ПоступлениеДенежныхСредствИзКассыККМ;
// Поиск ссылочных данных в справочниках по наименованию
ПКО.Касса = Справочники.Кассы.НайтиПоНаименованию(КассаНаименование);
ПКО.КассаККМ = Справочники.КассыККМ.НайтиПоНаименованию(КассаНаименование);
ПКО.Организация = Справочники.Организации.НайтиПоНаименованию(ОрганизацияНаименование);
ПКО.СуммаДокумента = СуммаДок;
// Безопасная запись документа
Попытка
ПКО.Записать(РежимЗаписиДокумента.Запись);
Исключение
Сообщить("Не удалось записать документ № " + НомерСтрока + " по причине: " + ОписаниеОшибки());
КонецПопытки;
КонецЦикла;
// Обязательно закрываем процесс Excel в памяти
Книга.Close(Ложь);
Эксель.Quit();
Использование COM-объекта Excel.Application считается устаревшим подходом. Он имеет три критических недостатка:
EXCEL.EXE, перегружающие оперативную память сервера.Проанализируем современную альтернативу — чтение данных с помощью встроенного объекта 1С ТабличныйДокумент. Этот метод работает на любых операционных системах, не требует стороннего ПО и считывает файлы в десятки раз быстрее.
Рассмотрим пример реализации загрузки через ТабличныйДокумент:
ТабДок = Новый ТабличныйДокумент;
Попытка
// Метод Прочитать автоматически определяет формат XLSX
ТабДок.Прочитать(ИмяФайла, СпособЧтенияЗначенийТабличногоДокумента.Значение);
Исключение
Сообщить("Ошибка чтения файла: " + ОписаниеОшибки());
Возврат;
КонецПопытки;
КоличествоСтрок = ТабДок.ВысотаТаблицы;
Для Шаг = 2 По КоличествоСтрок Цикл
// Считываем текстовые представления и значения ячеек напрямую
НомерСтрока = СокрЛП(ТабДок.Область(Шаг, 2).Текст);
ЗначениеДаты = ТабДок.Область(Шаг, 3).Значение;
СуммаДок = Число(ТабДок.Область(Шаг, 4).Значение);
КассаНаименование = СокрЛП(ТабДок.Область(Шаг, 5).Текст);
ОрганизацияНаименование = СокрЛП(ТабДок.Область(Шаг, 9).Текст);
Если ПустаяСтрока(НомерСтрока) Тогда
Продолжить;
КонецЕсли;
// Если ячейка в Excel была отформатирована как дата, платформа считает ее как тип Дата
Если ТипЗнч(ЗначениеДаты) = Тип("Дата") Тогда
ДатаДокумента = ЗначениеДаты;
Иначе
// Попытка преобразовать строковое представление даты
ДатаДокумента = ТекущаяДата();
КонецЕсли;
// Проверка уникальности номера перед созданием нового документа
ПКОСсылка = Документы.ПриходныйКассовыйОрдер.НайтиПоНомеру(НомерСтрока, ДатаДокумента);
Если ПКОСсылка.Пустая() Тогда
ПКО = Документы.ПриходныйКассовыйОрдер.СоздатьДокумент();
ПКО.Номер = НомерСтрока;
Else
ПКО = ПКОСсылка.ПолучитьОбъект();
КонецЕсли;
ПКО.Дата = ДатаДокумента;
ПКО.ХозяйственнаяОперация = Перечисления.ХозяйственныеОперации.ПоступлениеДенежныхСредствИзКассыККМ;
// Заполнение аналитики сопоставлением по наименованию
ПКО.Касса = Справочники.Кассы.НайтиПоНаименованию(КассаНаименование);
ПКО.КассаККМ = Справочники.КассыККМ.НайтиПоНаименованию(КассаНаименование);
ПКО.Организация = Справочники.Организации.НайтиПоНаименованию(ОрганизацияНаименование);
ПКО.СуммаДокумента = СуммаДок;
Попытка
ПКО.Записать(РежимЗаписиДокумента.Запись);
Исключение
Сообщить("Строка " + Шаг + ": Не удалось записать документ. Ошибка: " + ОписаниеОшибки());
КонецПопытки;
КонецЦикла;
Для того чтобы избежать системных сбоев автонумерации при загрузке внешних данных, рекомендуем придерживаться следующих правил:
НайтиПоНомеру() позволяет определить, загружался ли этот документ ранее. Это исключает дублирование записей при повторном импорте того же файла.Номер вручную. Оставьте его пустым, и при записи документа платформа 1С автоматически сгенерирует правильный уникальный номер. Если требуется задать префикс информационной базы или организации, используйте метод:
// Пример автоматической установки номера с префиксом организации
ПКО.УстановитьНовыйНомер(ПКО.Организация.Префикс);
"0000-000125"). Если записать номер как "125", автоматический нумератор 1С в дальнейшем может "сломаться", так как не сможет корректно инкрементировать номер без лидирующих нулей.Использование правильной архитектуры циклов, отказ от устаревшего COM-соединения в пользу кроссплатформенного ТабличногоДокумента и соблюдение правил автонумерации платформы гарантируют быструю, стабильную и безошибочную загрузку любых реестров документов в базы данных 1С.