При построении иерархических отчетов вручную с помощью макетов и табличных документов разработчики 1С часто сталкиваются со стандартной дилеммой. Мы последовательно выводим строки сверху вниз. Сначала нам нужно вывести строку заголовка группировки (например, «Продукты всего [Итог]»), а затем — детальные строки (например, «Горох — 100г», «Нут — 150г»). Проблема заключается в том, что на момент вывода строки заголовка общая сумма подчиненных элементов нам еще неизвестна, так как детальные строки еще не обработаны в цикле.
В этой статье мы подробно разберем пять профессиональных способов решения этой задачи: от простого запоминания координат до использования встроенных формул и правильного построения запросов с обходом по группировкам.
Когда мы пишем код последовательного вывода секций макета, платформа 1С заполняет табличный документ построчно. Метод Вывести() просто добавляет новые строки в конец документа. Если параметр суммы находится в строке, которая выводится раньше детальных записей, мы физически не можем передать туда значение, не зная его заранее. Давайте разберем, как обойти это ограничение платформы.
Этот способ основан на совете «Запоминай координаты». Он идеально подходит, если структура данных получается динамически в процессе обхода и мы не можем рассчитать суммы заранее.
При использовании метода Вывести() платформа 1С возвращает нам объект типа ОбластьЯчеекТабличногоДокумента. Этот возвращаемый объект содержит точные координаты строки, которая только что была добавлена в результирующий табличный документ. Мы можем сохранить эту область в переменную, продолжить вывод детальных строк, подсчитать их сумму в цикле, а затем обратиться к сохраненной области и записать туда итоговое значение.
Рассмотрим по шагам, как это реализовать в коде:
// Создаем переменные для накопления итога
СуммаИтог = 0;
// 1. Получаем область шапки группировки из макета
ОбластьШапкиМакет = Макет.ПолучитьОбласть("ШапкаГруппировки");
ОбластьШапкиМакет.Параметры.ИмяГруппировки = "Наименование всего";
// 2. Выводим шапку в табличный документ и ЗАПОМИНАЕМ выведенную область
ВыведеннаяОбластьШапки = ТабДок.Вывести(ОбластьШапкиМакет);
// 3. Выводим детальные строки в цикле
Пока ВыборкаДетали.Следующий() Цикл
ОбластьСтроки = Макет.ПолучитьОбласть("Строка");
ОбластьСтроки.Параметры.Номенклатура = ВыборкаДетали.Номенклатура;
ОбластьСтроки.Параметры.Количество = ВыборкаДетали.Количество;
ТабДок.Вывести(ОбластьСтроки);
// Накапливаем сумму
СуммаИтог = СуммаИтог + ВыборкаДетали.Количество;
КонецЦикла;
// 4. Записываем накопленный итог обратно в выведенную шапку.
// Для этого используем свойства координат сохраненной области: Верх и Лево.
// Допустим, параметр суммы в макете находился во 2-й колонке.
ТабДок.Область(ВыведеннаяОбластьШапки.Верх, 2).Текст = СуммаИтог;
При записи данных в ячейки уже выведенного документа разработчики часто получают ошибку выполнения. Чтобы избежать этого, нужно помнить следующее правило работы с ячейками табличного документа в 1С:
Значение.Текст.Рекомендуем использовать универсальный код для безопасной перезаписи ячеек:
ЦелеваяЯчейка = ТабДок.Область(ВыведеннаяОбластьШапки.Верх, 2);
Если ЦелеваяЯчейка.СодержитЗначение Тогда
ЦелеваяЯчейка.Значение = СуммаИтог;
Иначе
ЦелеваяЯчейка.Текст = Формат(СуммаИтог, "ЧДЦ=3");
КонецЕсли;
Этот способ позволяет переложить задачу суммирования на саму платформу 1С. Мы можем прописать в итоговой ячейке формулу суммирования диапазона строк, которые будут выведены ниже.
Для реализации этого метода нам нужно знать, сколько именно детальных строк будет выведено в текущей группировке. Например, мы можем получить это количество из количества строк в таблице значений или из результата запроса с помощью метода Количество().
Проанализируем пример кода, где мы динамически формируем формулу типа СУММ(R[1]C:R[N]C) в формате относительной адресации ячеек:
КоличествоСтрок = ВыборкаДетали.Количество(); // Получаем количество детальных строк
ОбластьШапкиМакет = Макет.ПолучитьОбласть("ШапкаГруппировки");
ВыведеннаяОбластьШапки = ТабДок.Вывести(ОбластьШапкиМакет);
// Определяем ячейку, куда нужно поместить формулу (например, строка шапки, колонка 2)
ЯчейкаИтога = ТабДок.Область(ВыведеннаяОбластьШапки.Верх, 2);
// Формируем формулу. R[1]C означает "строка ниже на 1, текущая колонка".
// R[КоличествоСтрок]C означает "строка ниже на КоличествоСтрок, текущая колонка".
ЯчейкаИтога.Формула = "СУММ(R[1]C:R[" + Формат(КоличествоСтрок, "ЧГ=0") + "]C)";
// Далее стандартно выводим детальные строки
Пока ВыборкаДетали.Следующий() Цикл
ОбластьСтроки = Макет.ПолучитьОбласть("Строка");
ОбластьСтроки.Параметры.Количество = ВыборкаДетали.Количество;
ТабДок.Вывести(ОбластьСтроки);
КонецЦикла;
Платформа 1С самостоятельно рассчитает итоговую сумму при выводе отчета на экран пользователя. Этот способ очень производителен, так как не требует ручного сложения чисел в коде 1С.
Если автор исходного вопроса утверждает, что у него «нет возможности» посчитать итоги заранее, то чаще всего это связано с тем, что данные обходятся последовательно непосредственно из выборки. Однако в архитектуре 1С практически любые исходные данные можно предварительно выгрузить в промежуточную коллекцию — ТаблицуЗначений.
Подготовив данные заранее, мы всегда будем знать сумму по группировке еще до того, как начнем выводить макет на печать. Посмотрим на пример реализации через свертку таблицы значений:
// Допустим, у нас есть ТаблицаДанных с колонками "Группировка", "Номенклатура", "Количество"
// Создаем копию таблицы для расчета итогов
ТаблицаИтогов = ТаблицаДанных.Скопировать();
// Сворачиваем по группировке, суммируя количество
ТаблицаИтогов.Свернуть("Группировка", "Количество");
ТаблицаИтогов.Индексировать("Группировка");
// Теперь при выводе отчета мы легко получаем готовый итог
Для Каждого СтрокаГруппы Из ТаблицаИтогов Цикл
// Выводим шапку и сразу подставляем заранее рассчитанный итог!
ОбластьШапки = Макет.ПолучитьОбласть("ШапкаГруппировки");
ОбластьШапки.Параметры.ИмяГруппировки = СтрокаГруппы.Группировка;
ОбластьШапки.Параметры.Итог = СтрокаГруппы.Количество; // Сумма уже известна!
ТабДок.Вывести(ОбластьШапки);
// Находим детальные строки для этой группы
Отбор = Новый Структура("Группировка", СтрокаГруппы.Группировка);
ДетальныеСтроки = ТаблицаДанных.НайтиСтроки(Отбор);
Для Каждого СтрокаДетали Из ДетальныеСтроки Цикл
ОбластьСтроки = Макет.ПолучитьОбласть("Строка");
ОбластьСтроки.Параметры.Номенклатура = СтрокаДетали.Номенклатура;
ОбластьСтроки.Параметры.Количество = СтрокаДетали.Количество;
ТабДок.Вывести(ОбластьСтроки);
КонецЦикла;
КонецЦикла;
Этот подход делает код чистым, понятным и избавляет от необходимости манипулировать координатами ячеек.
Это самый правильный и профессиональный способ разработки отчетов в 1С (Best Practice) при отсутствии использования Системы Компоновки Данных (СКД). Мы перекладываем расчет итогов на СУБД, используя предложение ИТОГИ... ПО в тексте запроса.
При обходе результата такого запроса платформа сама сначала возвращает нам строку родительской группировки, в которой уже содержится готовая сумма по всем подчиненным элементам. Выясним, как выглядит этот механизм на практике:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Товары.Группировка КАК Группировка,
| Товары.Номенклатура КАК Номенклатура,
| Товары.Количество КАК Количество
|ИЗ
| ВременнаяТаблицаТоваров КАК Товары
|ИТОГИ
| СУММА(Количество)
|ПО
| Группировка";
РезультатЗапроса = Запрос.Выполнить();
// Начинаем выборку по группировкам первого уровня (родительские группы)
ВыборкаГруппа = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаГруппа.Следующий() Цикл
// СУБД уже посчитала сумму! Мы можем сразу вывести её в шапку
ОбластьШапки = Макет.ПолучитьОбласть("ШапкаГруппировки");
ОбластьШапки.Параметры.ИмяГруппировки = ВыборкаГруппа.Группировка;
ОбластьШапки.Параметры.СуммаВсего = ВыборкаГруппа.Количество; // Готовая сумма!
ТабДок.Вывести(ОбластьШапки);
// Теперь получаем подчиненную выборку детальных записей по текущей группе
ВыборкаДетали = ВыборкаГруппа.Выбрать();
Пока ВыборкаДетали.Следующий() Цикл
ОбластьСтроки = Макет.ПолучитьОбласть("Строка");
ОбластьСтроки.Параметры.Номенклатура = ВыборкаДетали.Номенклатура;
ОбластьСтроки.Параметры.Количество = ВыборкаДетали.Количество;
ТабДок.Вывести(ОбластьСтроки);
КонецЦикла;
КонецЦикла;
Данный подход не только самый быстрый с точки зрения производительности базы данных, но и полностью избавляет разработчика от необходимости писать сложную логику подсчета сумм на стороне встроенного языка 1С.
В конце обсуждения автор темы нашел свое оригинальное решение проблемы. Он использовал промежуточный табличный документ ТабДок2 в качестве буфера.
Алгоритм решения автора выглядит следующим образом:
ТабДок2.ТабДок2. Параллельно в коде накапливается сумма.ТабДок.ТабДок2 целиком выводится в основной ТабДок вслед за шапкой с помощью метода Вывести(ТабДок2).Хотя этот способ рабочий и помог автору решить задачу, он имеет существенные недостатки: создание лишних тяжелых объектов в памяти (Табличных документов) и потенциальные проблемы с производительностью при больших объемах данных. Мы рекомендуем использовать Способ 4 или Способ 1.
Мы проанализировали различные варианты вывода итогов в родительские области табличного документа в 1С. Выбор конкретного метода зависит от источника ваших данных: