Как заполнить табличный документ номенклатурой с группировкой по родителю и избежать значений NULL?

Программист 1С v8.3 (Управляемые формы)
← На главную

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

Проанализируем причину появления значений NULL

Когда мы выполняем запрос к базе данных и указываем секцию ИТОГИ ПО ПредметЗакупкиРодитель, платформа формирует иерархическую выборку. Если мы получаем данные через РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам), система возвращает нам записи уровня группировки. Проблема заключается в том, что платформа 1С не знает, какое именно значение из множества детальных записей (например, количество, сумму или дату документа) поместить в итоговую строку группы. Поскольку явных указаний нет, система просто возвращает NULL.

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

Правильная настройка итогов в запросе

Рассмотрим подробнее, как нужно изменить запрос. Чтобы получить данные для шапки (документ в целом), затем для родителя номенклатуры и, наконец, для детальных записей, нам потребуется трехуровневая группировка. Также применим функции для числовых полей и реквизитов шапки.


// ... текст выбора полей ...
ГДЕ
    ЗаявкаНаЗакупку.Ссылка = &Документ
УПОРЯДОЧИТЬ ПО
    Ссылка
ИТОГИ 
    СУММА(Количество),
    СУММА(Сумма),
    СУММА(ОстатокНаСкладе),
    МАКСИМУМ(Дата),
    МАКСИМУМ(Номер)
ПО 
    Ссылка,
    ПредметЗакупкиРодитель,
    ПредметЗакупки

В этом примере мы добавили уровень документа (Ссылка). Это позволит нам при первом обходе сразу получить реквизиты шапки. Использование агрегатных функций, таких как СУММА() и МАКСИМУМ(), заставит платформу поднять эти значения на уровень группировок, и вместо NULL мы получим реальные цифры.

Построение структуры вложенных циклов обхода

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


// 1. Уровень шапки документа
ВыборкаДокумент = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаДокумент.Следующий() Цикл
    // Заполняем параметры макета для шапки (Дата, Номер без NULL)
    ОбластьШапка.Параметры.Заполнить(ВыборкаДокумент);
    ТабДок.Вывести(ОбластьШапка);
    
    // 2. Уровень родителя (группы номенклатуры)
    ВыборкаРодитель = ВыборкаДокумент.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
    Пока ВыборкаРодитель.Следующий() Цикл
        // Выводим строку с названием группы (папки)
        ОбластьГруппа.Параметры.ПредметЗакупкиРодитель = ВыборкаРодитель.ПредметЗакупкиРодитель;
        ТабДок.Вывести(ОбластьГруппа);
        
        // 3. Уровень детальных записей (сама номенклатура)
        // Обратите внимание, здесь обход по умолчанию (прямой)
        ВыборкаДетали = ВыборкаРодитель.Выбрать(); 
        Пока ВыборкаДетали.Следующий() Цикл
            // Выводим строки с конкретным товаром, количеством и суммой
            ОбластьСтрока.Параметры.Заполнить(ВыборкаДетали);
            ТабДок.Вывести(ОбластьСтрока);
        КонецЦикла;
    КонецЦикла;
КонецЦикла;

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

Использование конструкции ИЕРАРХИЯ

Альтернативный вариант — использовать в запросе итоги по иерархии: ИТОГИ ПО ПредметЗакупки ИЕРАРХИЯ. Однако при таком подходе есть важный нюанс. Чтобы правильно спуститься внутрь иерархии при обходе, в методе Выбрать обязательно нужно указывать имя группировки вторым параметром.

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


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

Улучшение визуального восприятия: Автогруппировка строк

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

Перед началом вывода первого цикла (уровня родителя) включим автогруппировку строк:

ТабДок.НачатьАвтогруппировкуСтрок();

А после завершения всех вложенных циклов обязательно закроем её:

ТабДок.ЗакончитьАвтогруппировкуСтрок();

Сами области макета при этом просто выводятся методом ТабДок.Вывести(Область), а платформа автоматически расставит нужные уровни группировки.

Современная альтернатива: Использование СКД для печатных форм

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

  1. Создается макет типа СКД.
  2. Настраивается структура вывода (Группировка по документу -> Группировка по родителю -> Детальные записи).
  3. СКД самостоятельно строит иерархию любой глубины и корректно рассчитывает промежуточные итоги (без NULL).
  4. Результат выводится сразу в Табличный документ программно через ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент.

Этот подход требует значительно меньше программного кода и полностью избавляет от типичных ошибок с обходами выборки и пустыми значениями.

← На главную