Как программно создать документ «Поступление товаров и услуг» и загрузить данные из Excel в 1С

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

В практике разработчика 1С часто возникает задача автоматизации ввода документов. Ручной перенос данных из прайс-листов, накладных или спецификаций, предоставленных контрагентами в формате Excel, занимает много времени и приводит к ошибкам. Оптимальное решение — создание обработки, которая считывает файл Excel, проверяет корректность данных и автоматически формирует документ поступления в базе 1С.

В данной статье мы подробно разберем, как программно создать документ ПоступлениеТоваровУслуг, заполнить его реквизитами с формы обработки, импортировать данные из файла Excel, исправить типичные критические ошибки при работе с COM-объектами и организовать надежный контроль ошибок с помощью транзакций.

Часть 1. Программное создание документа и вывод его формы на экран

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

Когда мы вызываем метод СоздатьДокумент() и затем метод Записать(), платформа действительно создает и записывает документ в базу данных. Однако она делает это в фоновом режиме, не открывая форму для пользователя. Если в журнале документов установлены отборы по периоду, организации или контрагенту, новый документ может просто не попасть в текущую видимость, создавая иллюзию того, что код не сработал.

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


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

КонецПроцедуры

Использование конструкции Попытка...Исключение при записи документа является обязательным стандартом разработки. Это позволяет перехватить ошибки блокировок базы данных, нарушения уникальности номеров или ошибки в подписках на события при записи, не аварийно завершая работу всей обработки.

Часть 2. Чтение данных из файла Excel через COM-соединение

Для интеграции с файлами Excel в операционной системе Windows традиционно применяется технология COM (Component Object Model). Проанализируем процесс выбора файла пользователем и подключение к приложению Excel.

Сначала организуем удобный интерактивный выбор файла на клиенте с помощью стандартного диалога:


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

После того как пользователь выбрал файл, мы можем инициализировать COM-соединение с приложением Excel и прочитать структуру таблицы. Ниже приведен пример функции инициализации:


Функция ИнициализироватьExcel(ПутьКФайлу) Экспорт
    
    Попытка
        Excel = Новый COMОбъект("Excel.Application");
    Исключение
        Сообщить("Ошибка: На данном компьютере не установлен Microsoft Excel или отсутствуют права на запуск COM-сервера.", СтатусСообщения.Важное);
        Возврат Неопределено;
    КонецПопытки;
    
    Попытка
        Excel.Workbooks.Open(ПутьКФайлу);
        // Выбираем первый лист книги по умолчанию
        Excel.Sheets(1).Select();
        Возврат Excel;
    Исключение
        Сообщить("Не удалось открыть файл: " + ПутьКФайлу + ". Ошибка: " + ОписаниеОшибки(), СтатусСообщения.Важное);
        Excel.Quit();
        Excel = Неопределено;
        Возврат Неопределено;
    КонецПопытки;
    
КонецФункции

Часть 3. Исправление критических ошибок в алгоритмах загрузки

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


// ВНИМАНИЕ: Данный код содержит грубые ошибки! Не использовать в работе!
Для СЧ = 2 По ВсегоСтрок Цикл
    НовСпр = Справочники.РегистрационныеНомераАлкогольныхСправок.СоздатьЭлемент();
    НовСпр.Наименование = СокрЛП(Excel.Cells(СЧ, 5).Value);
    НовСпр.Записать();
    СЧ = СЧ + 1; // ОШИБКА №1: Ручное искажение счетчика
КонецЦикла;
Excel.Application.Quit(); // ОШИБКА №2: Преждевременное закрытие

Проанализируем эти ошибки и выясним, почему они ломают работу программы:

  1. Искажение счетчика цикла (СЧ = СЧ + 1). В платформе 1С цикл Для...По...Цикл автоматически увеличивает переменную-счетчик на единицу при каждом переходе на следующую итерацию. Добавляя ручное прибавление шага внутри тела цикла, разработчик фактически заставляет систему перепрыгивать через строки. В итоге каждая вторая строка файла (строки 3, 5, 7 и т.д.) будет просто проигнорирована системой при загрузке.
  2. Преждевременное закрытие процесса Excel. Если закрывать приложение методом Excel.Application.Quit() внутри циклов обработки (или до завершения всех процедур чтения), повторный доступ к объекту вызовет критический сбой 1С «Объект COM-соединения не найден». Метод закрытия должен вызываться строго после полной обработки файла.
  3. Утечка памяти и «зависшие» процессы в диспетчере задач. COM-соединение оставляет активные системные процессы EXCEL.EXE в памяти Windows, даже после закрытия формы обработки. Это может быстро исчерпать оперативную память сервера приложений. Чтобы этого избежать, необходимо принудительно обнулять переменную COM-объекта, присваивая ей значение Неопределено в блоке финальной очистки.

Посмотрим на исправленный, безопасный вариант чтения строк файла и создания элементов справочника:


Процедура ЗагрузитьДанныеИзExcel()
    
    Если ПустаяСтрока(ИмяФайла) Тогда
        Предупреждение("Сначала выберите файл для загрузки!");
        Возврат;
    КонецЕсли;
    
    Excel = ИнициализироватьExcel(ИмяФайла);
    Если Excel = Неопределено Тогда
        Возврат; // Ошибка инициализации уже выведена в функции
    КонецЕсли;
    
    // Определяем границы заполненной области листа
    ВсегоСтрок = Excel.Cells(1, 1).SpecialCells(11).Row;
    
    Попытка
        // Начинаем транзакцию для ускорения записи и обеспечения целостности
        НачатьТранзакцию();
        
        // Правильный цикл без ручного инкремента счетчика
        Для Сч = 2 По ВсегоСтрок Цикл
            
            Артикул = СокрЛП(Excel.Cells(Сч, 1).Value);
            НаименованиеСправки = СокрЛП(Excel.Cells(Сч, 5).Value);
            
            Если ПустаяСтрока(Артикул) Тогда
                Продолжить; // Пропускаем пустые строки в файле
            КонецЕсли;
            
            // Создаем элемент справочника "Регистрационные Номера Алкогольных Справок"
            НовСпр = Справочники.РегистрационныеНомераАлкогольныхСправок.СоздатьЭлемент();
            НовСпр.ФормаСправки = Перечисления.ФормыАлкогольныхСправок.ФормаА;
            НовСпр.Наименование = НаименованиеСправки;
            НовСпр.РегистрационныйНомер = НаименованиеСправки;
            НовСпр.Записать();
            
        КонецЦикла;
        
        ЗафиксироватьТранзакцию();
        Сообщить("Импорт справочников успешно завершен!");
        
    Исключение
        ОтменитьТранзакцию();
        Сообщить("Произошла ошибка при импорте. Все изменения отменены! Описание: " + ОписаниеОшибки(), СтатусСообщения.Важное);
    КонецПопытки;
    
    // ОБЯЗАТЕЛЬНО: Корректно закрываем Excel и освобождаем оперативную память
    Попытка
        Excel.Workbooks.Close();
        Excel.Quit();
    Исключение
        // Игнорируем ошибки закрытия, если процесс уже был завершен внешними средствами
    КонецПопытки;
    
    Excel = Неопределено; // Освобождаем память COM-сервера
    
КонецПроцедуры

Часть 4. Транзакционный контроль и проверка номенклатуры по артикулу

Важнейшим бизнес-требованием при импорте является обеспечение целостности данных. Если файл содержит 100 строк товаров, и в 99-й строке обнаруживается неизвестный системе артикул номенклатуры, система не должна записывать «половинчатый» документ. Либо загружается весь объем данных полностью и без ошибок, либо импорт полностью отклоняется.

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

  1. Открываем транзакцию методом НачатьТранзакцию().
  2. В цикле обходим строки Excel и ищем номенклатуру методом НайтиПоРеквизиту("Артикул", ...).
  3. Если номенклатура не найдена, устанавливаем флаг Отказ = Истина, выводим информативное сообщение пользователю с указанием строки и артикула и прерываем цикл.
  4. Если все проверки пройдены, создаем документ ПоступлениеТоваровУслуг, заполняем его табличную часть Товары найденными позициями и записываем его.
  5. Анализируем состояние флага ошибок. Если обнаружен отказ, вызываем ОтменитьТранзакцию(), стирая все промежуточные изменения в базе. Если ошибок нет — фиксируем транзакцию методом ЗафиксироватьТранзакцию().

Реализуем этот надежный алгоритм в коде:


Процедура СоздатьПоступлениеСКонтролем()
    
    Excel = ИнициализироватьExcel(ИмяФайла);
    Если Excel = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    ВсегоСтрок = Excel.Cells(1, 1).SpecialCells(11).Row;
    Отказ = Ложь;
    
    НачатьТранзакцию();
    
    Попытка
        // Шаг 1. Создаем шапку документа
        НовДок = Документы.ПоступлениеТоваровУслуг.СоздатьДокумент();
        НовДок.Дата = ТекущаяДата();
        НовДок.УстановитьНовыйНомер();
        НовДок.Организация = Организация;
        НовДок.Контрагент = Контрагент;
        НовДок.СкладОрдер = Склад;
        НовДок.ОтражатьВБухгалтерскомУчете = Истина;
        НовДок.ОтражатьВНалоговомУчете = Истина;
        НовДок.ОтражатьВУправленческомУчете = Истина;
        НовДок.ВидПоступления = Перечисления.ВидыПоступленияТоваров.НаСклад;
        
        // Шаг 2. Цикл проверки номенклатуры и заполнения табличной части
        Для Сч = 2 По ВсегоСтрок Цикл
            
            АртикулФайла = СокрЛП(Excel.Cells(Сч, 1).Value);
            Количество = Число(Excel.Cells(Сч, 3).Value);
            Цена = Число(Excel.Cells(Сч, 4).Value);
            
            Если ПустаяСтрока(АртикулФайла) Тогда
                Продолжить;
            КонецЕсли;
            
            // Ищем номенклатуру по реквизиту в базе данных
            НайденнаяНоменклатура = Справочники.Номенклатура.НайтиПоРеквизиту("Артикул", АртикулФайла);
            
            Если НайденнаяНоменклатура.Пустая() Тогда
                Сообщить("Строка " + Сч + ": Не найдена номенклатура с артикулом [" + АртикулФайла + "]!", СтатусСообщения.Важное);
                Отказ = Истина;
                Прервать; // Выходим из цикла, продолжать чтение бессмысленно
            КонецЕсли;
            
            // Если номенклатура найдена, добавляем строку в табличную часть "Товары"
            НоваяСтрока = НовДок.Товары.Добавить();
            НоваяСтрока.Номенклатура = НайденнаяНоменклатура;
            НоваяСтрока.Количество = Количество;
            НоваяСтрока.Цена = Цена;
            НоваяСтрока.Сумма = Количество * Цена;
            
        КонецЦикла;
        
        // Шаг 3. Анализ результатов проверки и завершение транзакции
        Если Отказ Тогда
            ОтменитьТранзакцию();
            Сообщить("Загрузка отменена. Исправьте ошибки в файле Excel или справочнике Номенклатура.", СтатусСообщения.Внимание);
        Иначе
            // Ошибок нет, записываем и проводим документ
            НовДок.Записать(РежимЗаписиДокумента.Проведение);
            ЗафиксироватьТранзакцию();
            
            Сообщить("Документ проведен успешно: " + НовДок.Ссылка);
            НовДок.ПолучитьФорму().Открыть();
        EndIf;
        
    Исключение
        ОтменитьТранзакцию();
        Сообщить("Критическая ошибка при выполнении операции. Транзакция отменена!", СтатусСообщения.Важное);
        Сообщить("Описание ошибки: " + ОписаниеОшибки());
    КонецПопытки;
    
    // Закрываем Excel в любом случае
    Попытка
        Excel.Workbooks.Close();
        Excel.Quit();
    Исключение
    КонецПопытки;
    Excel = Неопределено;
    
КонецПроцедуры

Разберем, почему данный код гарантирует стабильность системы. Если в процессе проведения документа НовДок.Записать(РежимЗаписиДокумента.Проведение) произойдет ошибка (например, нехватка остатков по другим регистрам или незаполненные субконто), управление перейдет в блок Исключение, где сработает ОтменитьТранзакцию(). Никакие некорректные или частично заполненные объекты не останутся в базе данных.

Резюме и рекомендации по оптимизации

Мы спроектировали и реализовали законченный механизм интеграции. Для обеспечения максимальной производительности при реальной эксплуатации на больших базах данных придерживайтесь следующих правил:

← На главную