При интеграции 1С с внешними учетными системами часто возникает задача загрузки данных из формата DBF. Типичная сложность заключается в том, что файл представляет собой плоскую таблицу, где каждая строка содержит информацию об одной позиции товара. В то же время в 1С нам необходимо создать один документ Поступление (акт, накладная, УПД), который будет содержать несколько строк в табличной части, сгруппированных по номеру и дате документа. Рассмотрим подробно, как реализовать этот механизм программно.
Этот способ считается наиболее надежным и «чистым» с точки зрения архитектуры 1С. Мы сначала считываем все данные из файла в ТаблицаЗначений, а затем передаем эту таблицу в запрос. Запрос позволяет нам использовать мощный механизм группировок ИТОГИ ПО, что значительно упрощает обход данных.
Разберем реализацию этого метода. Сначала подготовим таблицу значений с нужной структурой:
ТипСтрока = Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(50));
ТипЧисло = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2));
ТЗ_Данные = Новый ТаблицаЗначений;
ТЗ_Данные.Колонки.Добавить("НомерДокумента", ТипСтрока);
ТЗ_Данные.Колонки.Добавить("ДатаДокумента", ТипСтрока);
ТЗ_Данные.Колонки.Добавить("ИННКонтрагента", ТипСтрока);
ТЗ_Данные.Колонки.Добавить("Номенклатура", ТипСтрока);
ТЗ_Данные.Колонки.Добавить("Цена", ТипЧисло);
ТЗ_Данные.Колонки.Добавить("Количество", ТипЧисло);
ТЗ_Данные.Колонки.Добавить("Сумма", ТипЧисло);
Затем загрузим данные из DBF в эту таблицу. Обратите внимание на важность кодировки. Если в файле используется кириллица, стоит проверить тип кодировки (обычно это ТипКодировкиXBase.ANSI или OEM).
ФайлDBF = Новый XBase;
ФайлDBF.Кодировка = ТипКодировкиXBase.ANSI;
ФайлDBF.ОткрытьФайл(ПутьКФайлу);
Если НЕ ФайлDBF.Открыта() Тогда
Возврат;
КонецЕсли;
Пока ФайлDBF.Следующая() Цикл
НоваяСтрока = ТЗ_Данные.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, ФайлDBF); // Поля в DBF должны совпадать с именами колонок ТЗ
КонецЦикла;
ФайлDBF.ЗакрытьФайл();
Теперь проанализируем ситуацию с группировкой через запрос. Мы создаем уникальный ключ, объединяя номер и дату, чтобы корректно отделить документы друг от друга.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ * ПОМЕСТИТЬ ВремТаблица ИЗ &ТЗ КАК ТЗ;
|ВЫБРАТЬ
| ВремТаблица.НомерДокумента + ""_"" + ВремТаблица.ДатаДокумента КАК УникальныйКлюч,
| МАКСИМУМ(ВремТаблица.НомерДокумента) КАК НомерДокумента,
| МАКСИМУМ(ВремТаблица.ДатаДокумента) КАК ДатаДокумента,
| МАКСИМУМ(ВремТаблица.ИННКонтрагента) КАК ИННКонтрагента,
| ВремТаблица.Номенклатура,
| ВремТаблица.Цена,
| ВремТаблица.Количество,
| ВремТаблица.Сумма
|ИЗ ВремТаблица КАК ВремТаблица
|ИТОГИ МАКСИМУМ(НомерДокумента), МАКСИМУМ(ДатаДокумента), МАКСИМУМ(ИННКонтрагента)
|ПО УникальныйКлюч";
Запрос.УстановитьПараметр("ТЗ", ТЗ_Данные);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДокументов = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаДокументов.Следующий() Цикл
// Здесь создаем заголовок документа
НовыйДокумент = Документы.ПоступлениеТоваровУслуг.СоздатьДокумент();
НовыйДокумент.Дата = Дата(ВыборкаДокументов.ДатаДокумента);
НовыйДокумент.НомерВходящегоДокумента = ВыборкаДокументов.НомерДокумента;
// Поиск контрагента по ИНН
КонтрагентСсылка = Справочники.Контрагенты.НайтиПоРеквизиту("ИНН", ВыборкаДокументов.ИННКонтрагента);
НовыйДокумент.Контрагент = КонтрагентСсылка;
ВыборкаСтрок = ВыборкаДокументов.Выбрать();
Пока ВыборкаСтрок.Следующий() Цикл
// Заполнение табличной части
НоваяСтрокаТЧ = НовыйДокумент.Товары.Добавить();
НоваяСтрокаТЧ.Количество = ВыборкаСтрок.Количество;
НоваяСтрокаТЧ.Цена = ВыборкаСтрок.Цена;
// Здесь должна быть логика поиска или создания номенклатуры
КонецЦикла;
НовыйДокумент.Записать(РежимЗаписиДокумента.Запись);
КонецЦикла;
Если файл DBF содержит десятки тысяч строк, перегонять его в таблицу значений может быть накладно для памяти. В этом случае выясним причину медленной работы и воспользуемся индексацией самого файла. Объект XBase позволяет работать с индексами (файлы .cdx или .idx), которые упорядочивают записи при чтении.
Если мы проиндексируем файл по полю DOCNUM (номер документа), то при обходе методом Следующая() записи одного документа будут идти строго друг за другом. Рассмотрим пример реализации:
ФайлДанных = Новый XBase;
// Добавляем индекс программно, если его нет
ФайлДанных.Поля.Добавить("DOCNUM", "S", 20);
ФайлДанных.Индексы.Добавить("IDX_NUM", "DOCNUM");
ФайлДанных.СоздатьИндексныйФайл(ПутьККндексу);
ФайлДанных.ОткрытьФайл(ПутьКФайлу, ПутьКИндексу);
ФайлДанных.ТекущийИндекс = ФайлДанных.Индексы.IDX_NUM;
ТекущийНомерДок = "";
ДокументОбъект = Неопределено;
Если ФайлДанных.Первая() Тогда
Пока Истина Цикл
Если ФайлДанных.DOCNUM <> ТекущийНомерДок Тогда
// Записываем предыдущий документ, если он был создан
Если ДокументОбъект <> Неопределено Тогда
ДокументОбъект.Записать();
КонецЕсли;
// Создаем новый документ
ДокументОбъект = Документы.ПоступлениеТоваровУслуг.СоздатьДокумент();
ТекущийНомерДок = ФайлДанных.DOCNUM;
// Заполнение шапки...
КонецЕсли;
// Добавление строки в текущий документ
НоваяСтрока = ДокументОбъект.Товары.Добавить();
// Заполнение строки...
Если НЕ ФайлДанных.Следующая() Тогда
Прервать;
КонецЕсли;
КонецЦикла;
// Не забываем записать последний документ
Если ДокументОбъект <> Неопределено Тогда
ДокументОбъект.Записать();
КонецЕсли;
КонецЕсли;
ФайлДанных.ЗакрытьФайл();
Для того чтобы загрузка прошла успешно, проанализируем несколько критических моментов:
ИНН + КПП. Для номенклатуры — Артикул или уникальный код из внешней системы. Если объект не найден, рекомендуется фиксировать это в логе и не прерывать всю загрузку.НачатьТранзакцию() и ЗафиксироватьТранзакцию().СокрЛП() для строковых полей.Подводя итог, можно сказать, что выбор метода зависит от объема данных. Для небольших файлов (до 5-10 тысяч строк) идеально подходит Метод 1 с использованием запроса к таблице значений — он наиболее нагляден и прост в отладке. Для огромных массивов данных лучше использовать Метод 2 с прямой индексацией файла.