Как в 1С программно создать документы ввода остатков по складам и заполнить их табличные части

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

При переносе данных между информационными базами (например, из 1С:Бухгалтерия предприятия в 1С:Управление торговлей через COM-соединение) часто возникает задача формирования документов ВводОстатков. Типичная ошибка начинающих разработчиков — создание нового документа на каждую строку выборки запроса. В результате вместо одного документа на каждый склад с полным перечнем номенклатуры формируются сотни документов с одной строкой в табличной части.

Разберем причины этой ошибки, рассмотрим правильные алгоритмы группировки данных в языке запросов 1С и изучим сопутствующие нюансы работы с остатками и COM-соединением.

Анализ исходной ошибки в коде

Посмотрим на фрагмент кода, приводящий к созданию лишних документов:


выб0 = Запр_.Выполнить().Выбрать();
Пока выб0.Следующий() Цикл
    вно = Документы.ВводОстатков.СоздатьДокумент();
    // Заполнение шапки и одной строки ТЧ
    строка = вно.Товары.Добавить();
    // ...
    вно.Записать();
КонецЦикла;

Проанализируем, почему этот алгоритм работает некорректно:

  1. Создание документа внутри линейного цикла: вызов метода СоздатьДокумент() и последующая запись вно.Записать() выполняются на каждой итерации перебора записей, поэтому каждая позиция номенклатуры записывается в отдельный документ.
  2. Отсутствие иерархической выборки: в запросе не использована секция ИТОГИ ПО, что не позволяет платформе разделить группировки по складам и детальные строки номенклатуры.
  3. Потенциальное деление на ноль: выражение строка.Сумма / строка.Количество приведет к аварийной остановке программы, если в исходных бухгалтерских данных присутствуют суммовые остатки без количества (например, после ручных корректировок стоимости).
  4. Параметры виртуальной таблицы: передача НачалоПериода и КонецПериода в виртуальную таблицу остатков некорректна. Таблица Остатки принимает только дату среза остатка (точку во времени), а не интервал дат.

Способ 1. Использование секции ИТОГИ в запросе и двухуровневой выборки

Наиболее эффективный и правильный способ решения задачи средствами платформы 1С:Предприятие — использование предложения ИТОГИ ПО в тексте запроса и последующий обход выборки с использованием типа обхода ПоГруппировкам.

Разберем реализацию этого подхода по шагам:

  1. В тексте запроса рассчитываем остатки на нужную дату и группируем итоги по складу (или субконто склада).
  2. Выполняем обход первого уровня выборки (по складам) — на этом уровне создаем документ и заполняем реквизиты шапки.
  3. Выполняем вложенный обход второго уровня выборки (детальные записи номенклатуры) — на этом уровне добавляем строки в табличную часть Товары.
  4. Записываем документ после завершения вложенного цикла.

Посмотрим на практический пример кода:


// Настраиваем дату среза остатков
ГраницаОстатков = Новый Граница(КонецДня(Дата(2016, 12, 31)), ВидГраницы.Включая);

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ХозрасчетныйОстатки.Организация КАК Организация,
    |	ХозрасчетныйОстатки.Субконто3 КАК Склад,
    |	ХозрасчетныйОстатки.Субконто1 КАК Номенклатура,
    |	ХозрасчетныйОстатки.КоличествоОстатокДт КАК Количество,
    |	ХозрасчетныйОстатки.СуммаОстатокДт КАК Сумма
    |ИЗ
    |	РегистрБухгалтерии.Хозрасчетный.Остатки(&Период, Счет В (&Счета), , ) КАК ХозрасчетныйОстатки
    |ИТОГИ
    |	СУММА(Количество),
    |	СУММА(Сумма)
    |ПО
    |	Организация,
    |	Склад";

Запрос.УстановитьПараметр("Период", ГраницаОстатков);
Запрос.УстановитьПараметр("Счета", МассивСчетов);

РезультатЗапроса = Запрос.Выполнить();

// Обход верхнего уровня - Склады
ВыборкаСклад = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

Пока ВыборкаСклад.Следующий() Цикл
    
    // Создаем один документ на каждый склад
    ДокВвод = Документы.ВводОстатков.СоздатьДокумент();
    ДокВвод.ТипОперации = Перечисления.ТипыОперацийВводаОстатков.ОстаткиСобственныхТоваров;
    ДокВвод.Дата = Дата(2017, 1, 1);
    ДокВвод.Организация = Справочники.Организации.НайтиПоНаименованию(ВыборкаСклад.Организация.Наименование, Истина);
    ДокВвод.Склад = Справочники.Склады.НайтиПоНаименованию(ВыборкаСклад.Склад.Наименование, Истина);
    ДокВвод.НалогообложениеНДС = Перечисления.ТипыНалогообложенияНДС.ПродажаНеОблагаетсяНДС;
    
    // Обход вложенного уровня - Номенклатура по текущему складу
    ВыборкаТовары = ВыборкаСклад.Выбрать();
    
    Пока ВыборкаТовары.Следующий() Цикл
        
        Если ВыборкаТовары.Количество <= 0 И ВыборкаТовары.Сумма <= 0 Тогда
            Продолжить;
        КонецЕсли;
        
        СтрокаТЧ = ДокВвод.Товары.Добавить();
        СтрокаТЧ.Номенклатура = Справочники.Номенклатура.НайтиПоНаименованию(ВыборкаТовары.Номенклатура.Наименование, Истина);
        СтрокаТЧ.Количество = ВыборкаТовары.Количество;
        СтрокаТЧ.КоличествоУпаковок = ВыборкаТовары.Количество;
        СтрокаТЧ.Сумма = ВыборкаТовары.Сумма;
        СтрокаТЧ.СуммаБезНДС = ВыборкаТовары.Сумма;
        СтрокаТЧ.СтавкаНДС = Перечисления.СтавкиНДС.НДС0;
        
        // Защита от деления на ноль
        Если ВыборкаТовары.Количество <> 0 Тогда
            СтрокаТЧ.Цена = ВыборкаТовары.Сумма / ВыборкаТовары.Количество;
        Иначе
            СтрокаТЧ.Цена = 0;
        КонецЕсли;
        
    КонецЦикла;
    
    // Записываем документ после заполнения всех строк
    Если ДокВвод.Товары.Количество() > 0 Тогда
        ДокВвод.Записать(РежимЗаписиДокумента.Запись);
    КонецЕсли;
    
КонецЦикла;

Способ 2. Группировка через Таблицу Значений

Если данные получены без итогов или переданы через внешний источник данных, можно выгрузить результат в объект ТаблицаЗначений и сгруппировать строки программно.

Рассмотрим реализацию через поиск строк:


ТЗ_Данные = Запрос.Выполнить().Выгрузить();

// Получаем список уникальных складов
ТЗ_Склады = ТЗ_Данные.Скопировать();
ТЗ_Склады.Свернуть("Организация, Склад");

Для Каждого СтрСклад Из ТЗ_Склады Цикл
    
    ДокВвод = Документы.ВводОстатков.СоздатьДокумент();
    ДокВвод.Дата = Дата(2017, 1, 1);
    ДокВвод.Организация = Справочники.Организации.НайтиПоНаименованию(СтрСклад.Организация.Наименование, Истина);
    ДокВвод.Склад = Справочники.Склады.НайтиПоНаименованию(СтрСклад.Склад.Наименование, Истина);
    
    // Отбираем строки только по текущему складу
    ПараметрыОтбора = Новый Структура("Организация, Склад", СтрСклад.Организация, СтрСклад.Склад);
    НайденныеСтроки = ТЗ_Данные.НайтиСтроки(ПараметрыОтбора);
    
    Для Каждого СтрТовар Из НайденныеСтроки Цикл
        СтрокаТЧ = ДокВвод.Товары.Добавить();
        СтрокаТЧ.Номенклатура = Справочники.Номенклатура.НайтиПоНаименованию(СтрТовар.Номенклатура.Наименование, Истина);
        СтрокаТЧ.Количество = СтрТовар.Количество;
        СтрокаТЧ.Сумма = СтрТовар.Сумма;
        
        Если СтрТовар.Количество <> 0 Тогда
            СтрокаТЧ.Цена = СтрТовар.Сумма / СтрТовар.Количество;
        КонецЕсли;
    КонецЦикла;
    
    Если ДокВвод.Товары.Количество() > 0 Тогда
        ДокВвод.Записать(РежимЗаписиДокумента.Запись);
    КонецЕсли;
    
КонецЦикла;

Оптимизация работы с COM-соединением и правила сопоставления

При переносе данных по COM-соединению важно учитывать следующие технические особенности:

  1. Минимизация межпроцессных вызовов: каждое обращение к свойствам COM-объекта (например, выб0.Субконто1.Наименование) порождает отдельный сетевой или межпроцессный вызов. Выполняйте запрос целиком в базе-источнике, сериализуйте данные в XML, JSON или массив простых структур, передавайте один пакет и заполняйте документы локально.
  2. Отказ от поиска по наименованию в цикле: метод НайтиПоНаименованию создает повышенную нагрузку на СУБД и приводит к ошибкам при наличии невидимых символов или дублей. Надежнее сопоставлять элементы по уникальному идентификатору (GUID), коду, артикулу или ИНН/КПП (для организаций).
  3. Транзакции записи: при пакетном создании документов используйте явные транзакции (НачатьТранзакцию() и ЗафиксироватьТранзакцию()) блоками по 10–50 документов, чтобы избежать зависаний и частичной записи поврежденных данных.
  4. Освобождение COM-сеанса: после завершения работы всегда очищайте переменную COM-подключения (Соединение = Неопределено;), чтобы не удерживать клиентскую лицензию и фоновый сеанс на сервере 1С.
← На главную