Как записать результаты запроса в регистр накопления при проведении документа

Программист 1С v8.3 (Управляемые формы) Управленческий учет Промышленность, строительство и АПК
← На главную

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

Постановка задачи и логика процесса

Представим ситуацию: у нас есть документ (например, ДоговорРеализации), который при проведении должен делать записи в регистр накопления ОборотыПоБюджетам. Часть данных (сумма, проект) есть в самом документе, а часть (статья оборотов, ЦФО, аналитика номенклатурной группы) должна подтягиваться из связанных справочников или настроек системы. Проанализируем стандартный алгоритм решения этой задачи:

  1. Подготовка запроса: В процедуре ОбработкаПроведения модуля объекта формируется запрос, который объединяет данные табличных частей документа с данными сторонних таблиц.
  2. Выполнение запроса: Получение таблицы результатов или выборки.
  3. Формирование движений: Обход полученных данных и их запись в коллекцию Движения.
  4. Управление записью: Установка флага необходимости записи набора данных в базу.

Способ 1. Ручное добавление записей в цикле

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


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    // Очищаем существующие движения, если это необходимо
    Движения.бит_ОборотыПоБюджетам.Записывать = Истина;
    Движения.бит_ОборотыПоБюджетам.Очистить();

    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	ТЧ.НоменклатурнаяГруппа КАК НоменклатурнаяГруппа,
        |	ТЧ.Сумма КАК Сумма,
        |	СпрПроекты.СтатьяОборотов КАК СтатьяОборотов
        |ИЗ
        |	Документ.бит_стр_ДоговорРеализации.Помещения КАК ТЧ
        |		ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Проекты КАК СпрПроекты
        |		ПО ТЧ.Ссылка.Проект = СпрПроекты.Ссылка
        |ГДЕ
        |	ТЧ.Ссылка = &Ссылка";

    Запрос.УстановитьПараметр("Ссылка", Ссылка);
    РезультатЗапроса = Запрос.Выполнить();
    Выборка = РезультатЗапроса.Выбрать();

    Пока Выборка.Следующий() Цикл
        // Создаем новую строку движения в регистре
        Движение = Движения.бит_ОборотыПоБюджетам.Добавить();
        
        // Заполняем стандартные реквизиты
        Движение.Период = Дата;
        Движение.Регистратор = Ссылка;
        
        // Заполняем измерения и ресурсы данными из запроса
        Движение.НоменклатурнаяГруппа = Выборка.НоменклатурнаяГруппа;
        Движение.СтатьяОборотов = Выборка.СтатьяОборотов;
        Движение.Сумма = Выборка.Сумма;
    КонецЦикла;

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

В данном примере мы используем метод Добавить() коллекции движений. Важно помнить, что если регистр является регистром накопления вида Остатки, то вместо Добавить() следует использовать методы ДобавитьПриход() или ДобавитьРасход(), чтобы система понимала направление движения.

Способ 2. Использование функции ЗаполнитьЗначенияСвойств

Если названия полей в вашем запросе (псевдонимы) полностью совпадают с именами измерений, ресурсов и реквизитов регистра, код можно значительно сократить и сделать более универсальным. Проанализируем, как упростить перенос данных:


Пока Выборка.Следующий() Цикл
    НоваяЗапись = Движения.ВашРегистр.Добавить();
    
    // Платформа сама найдет совпадающие поля и перенесет значения
    ЗаполнитьЗначенияСвойств(НоваяЗапись, Выборка);
    
    // Дозаполняем то, что не попало в запрос (например, дату документа)
    НоваяЗапись.Период = Дата;
КонецЦикла;

Этот подход уменьшает вероятность ошибки при ручном наборе имен свойств и делает код "чище". Однако помните, что ЗаполнитьЗначенияСвойств работает медленнее, чем прямое присваивание, если строк в документе очень много (тысячи).

Способ 3. Оптимизированная загрузка через Таблицу Значений

Для высоконагруженных систем или документов с огромным количеством строк (например, загрузка данных из внешних систем) рекомендуется использовать метод Загрузить(). Этот метод позволяет разом поместить всю таблицу данных в набор записей регистра, что работает быстрее, чем добавление строк по одной в цикле.

Рассмотрим алгоритм реализации:

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

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

// Добавляем служебные колонки, если их нет в результате запроса
ТаблицаДвижений.Колонки.Добавить("Период", Новый ОписаниеТипов("Дата"));
ТаблицаДвижений.Колонки.Добавить("Регистратор");

// Заполняем колонки для всех строк таблицы
ТаблицаДвижений.ЗаполнитьЗначения(Дата, "Период");
ТаблицаДвижений.ЗаполнитьЗначения(Ссылка, "Регистратор");

// Загружаем всю таблицу в движения одним методом
Движения.ВашРегистр.Записывать = Истина;
Движения.ВашРегистр.Загрузить(ТаблицаДвижений);

Критическая ошибка: запрос к регистру по текущему документу

Проанализируем распространенную ошибку новичков, которая часто приводит к получению "пустого результата" в движениях. Выясним причину: если внутри процедуры ОбработкаПроведения вы делаете запрос к тому же самому регистру, в который собираетесь писать, и устанавливаете фильтр по текущему документу (ГДЕ Регистратор = &Ссылка), запрос вернет пустой результат.

Это происходит потому, что в момент выполнения кода в ОбработкаПроведения новые движения еще не записаны в базу данных, а старые (если документ перепроводится) платформа уже пометила на удаление или очистила в начале транзакции. Правило: в процедуре проведения всегда берите данные из Объекта (табличных частей ЭтотОбъект.ТабличнаяЧасть) или временных таблиц, сформированных на основе этих данных, но не из итоговых таблиц регистра по этому же документу.

Особенности работы в отраслевых решениях (на примере БИТ.Строительство)

Если вы работаете с конфигурациями вроде БИТ.Строительство, важно учитывать специфику ключевых регистров, таких как бит_ОборотыПоБюджетам. Эти регистры имеют жесткую структуру аналитики. Рассмотрим важные моменты:

Использование временных таблиц для сложной логики

Когда данных много и связи между таблицами сложные, использование одного огромного запроса с множеством ЛЕВОЕ СОЕДИНЕНИЕ может замедлить проведение документа. Разберем по шагам, как оптимизировать этот процесс:

  1. Сначала поместите данные табличной части документа во временную таблицу.
  2. Проиндексируйте временную таблицу по полям связи (например, по номенклатуре).
  3. Вторым запросом в этом же пакете соедините временную таблицу со справочниками.
  4. Получите финальный результат для формирования движений.

Пример структуры пакетного запроса:


Запрос.Текст = 
    "ВЫБРАТЬ
    |	ТЧ.Номенклатура КАК Номенклатура,
    |	ТЧ.Количество КАК Количество
    |ПОМЕСТИТЬ ВТ_ДанныеДокумента
    |ИЗ
    |	&ТЧ_Документа КАК ТЧ
    |;
    |
    |ВЫБРАТЬ
    |	ВТ.Номенклатура,
    |	ВТ.Количество,
    |	Спр.ЕдиницаИзмерения
    |ИЗ
    |	ВТ_ДанныеДокумента КАК ВТ
    |		ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Спр
    |		ПО ВТ.Номенклатура = Спр.Ссылка";

Запрос.УстановитьПараметр("ТЧ_Документа", Помещения.Выгрузить());

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

Заключение

Мы рассмотрели три основных способа записи результатов запроса в регистр накопления. Выбор конкретного метода зависит от объема данных и сложности вашей логики. Для простых задач идеально подходит цикл с ЗаполнитьЗначенияСвойств, для сложных и объемных — работа с временными таблицами и метод Загрузить(). Помните о транзакционной целостности: всегда устанавливайте Движения.ИмяРегистра.Записывать = Истина, чтобы система корректно завершила запись данных после окончания процедуры проведения.

← На главную