В практике разработчика 1С часто возникает задача автоматизации ввода документов. Ручной перенос данных из прайс-листов, накладных или спецификаций, предоставленных контрагентами в формате Excel, занимает много времени и приводит к ошибкам. Оптимальное решение — создание обработки, которая считывает файл Excel, проверяет корректность данных и автоматически формирует документ поступления в базе 1С.
В данной статье мы подробно разберем, как программно создать документ ПоступлениеТоваровУслуг, заполнить его реквизитами с формы обработки, импортировать данные из файла 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 = Неопределено;
Возврат Неопределено;
КонецПопытки;
КонецФункции
Рассмотрим по шагам типичные и крайне опасные ошибки, которые часто допускают разработчики при написании циклов импорта данных из Excel. Ниже представлен фрагмент кода, содержащий три критические уязвимости:
// ВНИМАНИЕ: Данный код содержит грубые ошибки! Не использовать в работе!
Для СЧ = 2 По ВсегоСтрок Цикл
НовСпр = Справочники.РегистрационныеНомераАлкогольныхСправок.СоздатьЭлемент();
НовСпр.Наименование = СокрЛП(Excel.Cells(СЧ, 5).Value);
НовСпр.Записать();
СЧ = СЧ + 1; // ОШИБКА №1: Ручное искажение счетчика
КонецЦикла;
Excel.Application.Quit(); // ОШИБКА №2: Преждевременное закрытие
Проанализируем эти ошибки и выясним, почему они ломают работу программы:
СЧ = СЧ + 1). В платформе 1С цикл Для...По...Цикл автоматически увеличивает переменную-счетчик на единицу при каждом переходе на следующую итерацию. Добавляя ручное прибавление шага внутри тела цикла, разработчик фактически заставляет систему перепрыгивать через строки. В итоге каждая вторая строка файла (строки 3, 5, 7 и т.д.) будет просто проигнорирована системой при загрузке.Excel.Application.Quit() внутри циклов обработки (или до завершения всех процедур чтения), повторный доступ к объекту вызовет критический сбой 1С «Объект 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-сервера
КонецПроцедуры
Важнейшим бизнес-требованием при импорте является обеспечение целостности данных. Если файл содержит 100 строк товаров, и в 99-й строке обнаруживается неизвестный системе артикул номенклатуры, система не должна записывать «половинчатый» документ. Либо загружается весь объем данных полностью и без ошибок, либо импорт полностью отклоняется.
Для реализации этой логики мы должны объединить весь процесс создания элементов и документа в единую транзакцию. Рассмотрим, как это сделать по шагам:
НачатьТранзакцию().НайтиПоРеквизиту("Артикул", ...).Отказ = Истина, выводим информативное сообщение пользователю с указанием строки и артикула и прерываем цикл.ПоступлениеТоваровУслуг, заполняем его табличную часть Товары найденными позициями и записываем его.ОтменитьТранзакцию(), стирая все промежуточные изменения в базе. Если ошибок нет — фиксируем транзакцию методом ЗафиксироватьТранзакцию().Реализуем этот надежный алгоритм в коде:
Процедура СоздатьПоступлениеСКонтролем()
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 = Неопределено;
КонецПроцедуры
Разберем, почему данный код гарантирует стабильность системы. Если в процессе проведения документа НовДок.Записать(РежимЗаписиДокумента.Проведение) произойдет ошибка (например, нехватка остатков по другим регистрам или незаполненные субконто), управление перейдет в блок Исключение, где сработает ОтменитьТранзакцию(). Никакие некорректные или частично заполненные объекты не останутся в базе данных.
Мы спроектировали и реализовали законченный механизм интеграции. Для обеспечения максимальной производительности при реальной эксплуатации на больших базах данных придерживайтесь следующих правил:
ПолучитьФорму().Открыть(), чтобы он мог визуально проконтролировать результат работы обработки.Close(), Quit() для COM-объектов и обнулять ссылки на них (Excel = Неопределено), предотвращая утечки памяти на рабочих местах и серверах приложений.