При переносе данных между информационными базами (например, из 1С:Бухгалтерия предприятия в 1С:Управление торговлей через COM-соединение) часто возникает задача формирования документов ВводОстатков. Типичная ошибка начинающих разработчиков — создание нового документа на каждую строку выборки запроса. В результате вместо одного документа на каждый склад с полным перечнем номенклатуры формируются сотни документов с одной строкой в табличной части.
Разберем причины этой ошибки, рассмотрим правильные алгоритмы группировки данных в языке запросов 1С и изучим сопутствующие нюансы работы с остатками и COM-соединением.
Посмотрим на фрагмент кода, приводящий к созданию лишних документов:
выб0 = Запр_.Выполнить().Выбрать();
Пока выб0.Следующий() Цикл
вно = Документы.ВводОстатков.СоздатьДокумент();
// Заполнение шапки и одной строки ТЧ
строка = вно.Товары.Добавить();
// ...
вно.Записать();
КонецЦикла;
Проанализируем, почему этот алгоритм работает некорректно:
СоздатьДокумент() и последующая запись вно.Записать() выполняются на каждой итерации перебора записей, поэтому каждая позиция номенклатуры записывается в отдельный документ.ИТОГИ ПО, что не позволяет платформе разделить группировки по складам и детальные строки номенклатуры.строка.Сумма / строка.Количество приведет к аварийной остановке программы, если в исходных бухгалтерских данных присутствуют суммовые остатки без количества (например, после ручных корректировок стоимости).НачалоПериода и КонецПериода в виртуальную таблицу остатков некорректна. Таблица Остатки принимает только дату среза остатка (точку во времени), а не интервал дат.Наиболее эффективный и правильный способ решения задачи средствами платформы 1С:Предприятие — использование предложения ИТОГИ ПО в тексте запроса и последующий обход выборки с использованием типа обхода ПоГруппировкам.
Разберем реализацию этого подхода по шагам:
Товары.Посмотрим на практический пример кода:
// Настраиваем дату среза остатков
ГраницаОстатков = Новый Граница(КонецДня(Дата(2016, 12, 31)), ВидГраницы.Включая);
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ХозрасчетныйОстатки.Организация КАК Организация,
| ХозрасчетныйОстатки.Субконто3 КАК Склад,
| ХозрасчетныйОстатки.Субконто1 КАК Номенклатура,
| ХозрасчетныйОстатки.КоличествоОстатокДт КАК Количество,
| ХозрасчетныйОстатки.СуммаОстатокДт КАК Сумма
|ИЗ
| РегистрБухгалтерии.Хозрасчетный.Остатки(&Период, Счет В (&Счета), , ) КАК ХозрасчетныйОстатки
|ИТОГИ
| СУММА(Количество),
| СУММА(Сумма)
|ПО
| Организация,
| Склад";
Запрос.УстановитьПараметр("Период", ГраницаОстатков);
Запрос.УстановитьПараметр("Счета", МассивСчетов);
РезультатЗапроса = Запрос.Выполнить();
// Обход верхнего уровня - Склады
ВыборкаСклад = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаСклад.Следующий() Цикл
// Создаем один документ на каждый склад
ДокВвод = Документы.ВводОстатков.СоздатьДокумент();
ДокВвод.ТипОперации = Перечисления.ТипыОперацийВводаОстатков.ОстаткиСобственныхТоваров;
ДокВвод.Дата = Дата(2017, 1, 1);
ДокВвод.Организация = Справочники.Организации.НайтиПоНаименованию(ВыборкаСклад.Организация.Наименование, Истина);
ДокВвод.Склад = Справочники.Склады.НайтиПоНаименованию(ВыборкаСклад.Склад.Наименование, Истина);
ДокВвод.НалогообложениеНДС = Перечисления.ТипыНалогообложенияНДС.ПродажаНеОблагаетсяНДС;
// Обход вложенного уровня - Номенклатура по текущему складу
ВыборкаТовары = ВыборкаСклад.Выбрать();
Пока ВыборкаТовары.Следующий() Цикл
Если ВыборкаТовары.Количество <= 0 И ВыборкаТовары.Сумма <= 0 Тогда
Продолжить;
КонецЕсли;
СтрокаТЧ = ДокВвод.Товары.Добавить();
СтрокаТЧ.Номенклатура = Справочники.Номенклатура.НайтиПоНаименованию(ВыборкаТовары.Номенклатура.Наименование, Истина);
СтрокаТЧ.Количество = ВыборкаТовары.Количество;
СтрокаТЧ.КоличествоУпаковок = ВыборкаТовары.Количество;
СтрокаТЧ.Сумма = ВыборкаТовары.Сумма;
СтрокаТЧ.СуммаБезНДС = ВыборкаТовары.Сумма;
СтрокаТЧ.СтавкаНДС = Перечисления.СтавкиНДС.НДС0;
// Защита от деления на ноль
Если ВыборкаТовары.Количество <> 0 Тогда
СтрокаТЧ.Цена = ВыборкаТовары.Сумма / ВыборкаТовары.Количество;
Иначе
СтрокаТЧ.Цена = 0;
КонецЕсли;
КонецЦикла;
// Записываем документ после заполнения всех строк
Если ДокВвод.Товары.Количество() > 0 Тогда
ДокВвод.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;
КонецЦикла;
Если данные получены без итогов или переданы через внешний источник данных, можно выгрузить результат в объект ТаблицаЗначений и сгруппировать строки программно.
Рассмотрим реализацию через поиск строк:
ТЗ_Данные = Запрос.Выполнить().Выгрузить();
// Получаем список уникальных складов
ТЗ_Склады = ТЗ_Данные.Скопировать();
ТЗ_Склады.Свернуть("Организация, Склад");
Для Каждого СтрСклад Из ТЗ_Склады Цикл
ДокВвод = Документы.ВводОстатков.СоздатьДокумент();
ДокВвод.Дата = Дата(2017, 1, 1);
ДокВвод.Организация = Справочники.Организации.НайтиПоНаименованию(СтрСклад.Организация.Наименование, Истина);
ДокВвод.Склад = Справочники.Склады.НайтиПоНаименованию(СтрСклад.Склад.Наименование, Истина);
// Отбираем строки только по текущему складу
ПараметрыОтбора = Новый Структура("Организация, Склад", СтрСклад.Организация, СтрСклад.Склад);
НайденныеСтроки = ТЗ_Данные.НайтиСтроки(ПараметрыОтбора);
Для Каждого СтрТовар Из НайденныеСтроки Цикл
СтрокаТЧ = ДокВвод.Товары.Добавить();
СтрокаТЧ.Номенклатура = Справочники.Номенклатура.НайтиПоНаименованию(СтрТовар.Номенклатура.Наименование, Истина);
СтрокаТЧ.Количество = СтрТовар.Количество;
СтрокаТЧ.Сумма = СтрТовар.Сумма;
Если СтрТовар.Количество <> 0 Тогда
СтрокаТЧ.Цена = СтрТовар.Сумма / СтрТовар.Количество;
КонецЕсли;
КонецЦикла;
Если ДокВвод.Товары.Количество() > 0 Тогда
ДокВвод.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;
КонецЦикла;
При переносе данных по COM-соединению важно учитывать следующие технические особенности:
выб0.Субконто1.Наименование) порождает отдельный сетевой или межпроцессный вызов. Выполняйте запрос целиком в базе-источнике, сериализуйте данные в XML, JSON или массив простых структур, передавайте один пакет и заполняйте документы локально.НайтиПоНаименованию создает повышенную нагрузку на СУБД и приводит к ошибкам при наличии невидимых символов или дублей. Надежнее сопоставлять элементы по уникальному идентификатору (GUID), коду, артикулу или ИНН/КПП (для организаций).НачатьТранзакцию() и ЗафиксироватьТранзакцию()) блоками по 10–50 документов, чтобы избежать зависаний и частичной записи поврежденных данных.Соединение = Неопределено;), чтобы не удерживать клиентскую лицензию и фоновый сеанс на сервере 1С.