Как вывести итоговые суммы в уже выведенную (родительскую) группировку табличного документа в 1С

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

При построении иерархических отчетов вручную с помощью макетов и табличных документов разработчики 1С часто сталкиваются со стандартной дилеммой. Мы последовательно выводим строки сверху вниз. Сначала нам нужно вывести строку заголовка группировки (например, «Продукты всего [Итог]»), а затем — детальные строки (например, «Горох — 100г», «Нут — 150г»). Проблема заключается в том, что на момент вывода строки заголовка общая сумма подчиненных элементов нам еще неизвестна, так как детальные строки еще не обработаны в цикле.

В этой статье мы подробно разберем пять профессиональных способов решения этой задачи: от простого запоминания координат до использования встроенных формул и правильного построения запросов с обходом по группировкам.

Причина возникновения проблемы

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


Способ 1. Возврат области и точечная запись по координатам (Динамическая перезапись)

Этот способ основан на совете «Запоминай координаты». Он идеально подходит, если структура данных получается динамически в процессе обхода и мы не можем рассчитать суммы заранее.

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

Рассмотрим по шагам, как это реализовать в коде:


// Создаем переменные для накопления итога
СуммаИтог = 0;

// 1. Получаем область шапки группировки из макета
ОбластьШапкиМакет = Макет.ПолучитьОбласть("ШапкаГруппировки");
ОбластьШапкиМакет.Параметры.ИмяГруппировки = "Наименование всего";

// 2. Выводим шапку в табличный документ и ЗАПОМИНАЕМ выведенную область
ВыведеннаяОбластьШапки = ТабДок.Вывести(ОбластьШапкиМакет);

// 3. Выводим детальные строки в цикле
Пока ВыборкаДетали.Следующий() Цикл
    ОбластьСтроки = Макет.ПолучитьОбласть("Строка");
    ОбластьСтроки.Параметры.Номенклатура = ВыборкаДетали.Номенклатура;
    ОбластьСтроки.Параметры.Количество = ВыборкаДетали.Количество;
    
    ТабДок.Вывести(ОбластьСтроки);
    
    // Накапливаем сумму
    СуммаИтог = СуммаИтог + ВыборкаДетали.Количество;
КонецЦикла;

// 4. Записываем накопленный итог обратно в выведенную шапку.
// Для этого используем свойства координат сохраненной области: Верх и Лево.
// Допустим, параметр суммы в макете находился во 2-й колонке.
ТабДок.Область(ВыведеннаяОбластьШапки.Верх, 2).Текст = СуммаИтог;

Важный технический нюанс: Свойство «Текст» против «Значение»

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

  1. Если для ячейки в макете включено свойство «Содержит значение» (в палитре свойств установлен этот флаг и указан тип данных), то изменять данные в выведенном документе нужно строго через свойство Значение.
  2. Если этот флаг выключен, запись производится в свойство Текст.

Рекомендуем использовать универсальный код для безопасной перезаписи ячеек:


ЦелеваяЯчейка = ТабДок.Область(ВыведеннаяОбластьШапки.Верх, 2);
Если ЦелеваяЯчейка.СодержитЗначение Тогда
    ЦелеваяЯчейка.Значение = СуммаИтог;
Иначе
    ЦелеваяЯчейка.Текст = Формат(СуммаИтог, "ЧДЦ=3");
КонецЕсли;

Способ 2. Использование встроенных формул табличного документа (Метод Excel)

Этот способ позволяет переложить задачу суммирования на саму платформу 1С. Мы можем прописать в итоговой ячейке формулу суммирования диапазона строк, которые будут выведены ниже.

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

Проанализируем пример кода, где мы динамически формируем формулу типа СУММ(R[1]C:R[N]C) в формате относительной адресации ячеек:


КоличествоСтрок = ВыборкаДетали.Количество(); // Получаем количество детальных строк

ОбластьШапкиМакет = Макет.ПолучитьОбласть("ШапкаГруппировки");
ВыведеннаяОбластьШапки = ТабДок.Вывести(ОбластьШапкиМакет);

// Определяем ячейку, куда нужно поместить формулу (например, строка шапки, колонка 2)
ЯчейкаИтога = ТабДок.Область(ВыведеннаяОбластьШапки.Верх, 2);

// Формируем формулу. R[1]C означает "строка ниже на 1, текущая колонка".
// R[КоличествоСтрок]C означает "строка ниже на КоличествоСтрок, текущая колонка".
ЯчейкаИтога.Формула = "СУММ(R[1]C:R[" + Формат(КоличествоСтрок, "ЧГ=0") + "]C)";

// Далее стандартно выводим детальные строки
Пока ВыборкаДетали.Следующий() Цикл
    ОбластьСтроки = Макет.ПолучитьОбласть("Строка");
    ОбластьСтроки.Параметры.Количество = ВыборкаДетали.Количество;
    ТабДок.Вывести(ОбластьСтроки);
КонецЦикла;

Платформа 1С самостоятельно рассчитает итоговую сумму при выводе отчета на экран пользователя. Этот способ очень производителен, так как не требует ручного сложения чисел в коде 1С.


Способ 3. Предварительный расчет итогов с помощью Таблицы Значений

Если автор исходного вопроса утверждает, что у него «нет возможности» посчитать итоги заранее, то чаще всего это связано с тем, что данные обходятся последовательно непосредственно из выборки. Однако в архитектуре 1С практически любые исходные данные можно предварительно выгрузить в промежуточную коллекцию — ТаблицуЗначений.

Подготовив данные заранее, мы всегда будем знать сумму по группировке еще до того, как начнем выводить макет на печать. Посмотрим на пример реализации через свертку таблицы значений:


// Допустим, у нас есть ТаблицаДанных с колонками "Группировка", "Номенклатура", "Количество"
// Создаем копию таблицы для расчета итогов
ТаблицаИтогов = ТаблицаДанных.Скопировать();
// Сворачиваем по группировке, суммируя количество
ТаблицаИтогов.Свернуть("Группировка", "Количество");
ТаблицаИтогов.Индексировать("Группировка");

// Теперь при выводе отчета мы легко получаем готовый итог
Для Каждого СтрокаГруппы Из ТаблицаИтогов Цикл
    
    // Выводим шапку и сразу подставляем заранее рассчитанный итог!
    ОбластьШапки = Макет.ПолучитьОбласть("ШапкаГруппировки");
    ОбластьШапки.Параметры.ИмяГруппировки = СтрокаГруппы.Группировка;
    ОбластьШапки.Параметры.Итог = СтрокаГруппы.Количество; // Сумма уже известна!
    ТабДок.Вывести(ОбластьШапки);
    
    // Находим детальные строки для этой группы
    Отбор = Новый Структура("Группировка", СтрокаГруппы.Группировка);
    ДетальныеСтроки = ТаблицаДанных.НайтиСтроки(Отбор);
    
    Для Каждого СтрокаДетали Из ДетальныеСтроки Цикл
        ОбластьСтроки = Макет.ПолучитьОбласть("Строка");
        ОбластьСтроки.Параметры.Номенклатура = СтрокаДетали.Номенклатура;
        ОбластьСтроки.Параметры.Количество = СтрокаДетали.Количество;
        ТабДок.Вывести(ОбластьСтроки);
    КонецЦикла;
    
КонецЦикла;

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


Способ 4. Использование итогов в запросе и обхода по группировкам (Рекомендуемый стандарт)

Это самый правильный и профессиональный способ разработки отчетов в 1С (Best Practice) при отсутствии использования Системы Компоновки Данных (СКД). Мы перекладываем расчет итогов на СУБД, используя предложение ИТОГИ... ПО в тексте запроса.

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


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

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

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

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


Способ 5. Использование временного табличного документа (Решение автора темы)

В конце обсуждения автор темы нашел свое оригинальное решение проблемы. Он использовал промежуточный табличный документ ТабДок2 в качестве буфера.

Алгоритм решения автора выглядит следующим образом:

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

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


Резюме и рекомендации

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

← На главную