Как вывести итог табличной части в шапку печатной формы в 1С:Предприятие 8

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

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

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

Исходная ситуация и анализ проблемы

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


// Фрагмент исходного кода вывода печатной формы
Пока Выборка.Следующий() Цикл
    ТабДок.Вывести(ОбластьЗаголовок);
    
    // В этот момент сумма еще не рассчитана!
    Шапка.Параметры.Заполнить(Выборка);
    ТабДок.Вывести(Шапка);
    
    ТабДок.Вывести(ОбластьСписокШапка);
    
    ВыборкаСписок = Выборка.Список.Выбрать();
    ИтогоСумма = 0;
    Пока ВыборкаСписок.Следующий() Цикл
        ОбластьСписок.Параметры.Заполнить(ВыборкаСписок);
        ИтогоСумма = ИтогоСумма + ВыборкаСписок.Сумма;
        ТабДок.Вывести(ОбластьСписок);
    КонецЦикла;
    
    ОбластьСписокИтого.Параметры.ИтогоСумма = ИтогоСумма;
    ТабДок.Вывести(ОбластьСписокИтого);
КонецЦикла;

Как мы видим, на момент вывода объекта Шапка переменная ИтогоСумма равна нулю. Чтобы решить эту проблему, нам нужно предварительно рассчитать сумму до того, как область шапки будет выведена в итоговый табличный документ. Рассмотрим пошагово рабочие методы реализации этой логики.

Способ 1. Прямой расчет суммы через объектную модель (для одиночной печати)

Если печатная форма всегда формируется строго для одного документа (например, при нажатии кнопки «Печать» непосредственно из формы документа), самым простым и лаконичным решением будет использование метода Итог() табличной части объекта.

Для этого нам достаточно получить объект документа (или обратиться к его табличной части через ссылку) и рассчитать сумму одной строкой кода еще до начала вывода макета:


// Получаем итог напрямую из табличной части ссылки
ИтоговаяСуммаДокумента = Ссылка.Список.Итог("Сумма");

// Заполняем параметр шапки перед выводом
Шапка.Параметры.ИтогоСумма = ИтоговаяСуммаДокумента;
ТабДок.Вывести(Шапка);

Преимущества метода:

  1. Минимальное количество строк кода.
  2. Простота понимания и отладки.

Недостатки метода:

  1. Обращение через точку к реквизитам и табличным частям объекта (Ссылка.Список) в цикле при массовой печати может приводить к избыточным запросам к базе данных и существенному снижению производительности.

Способ 2. Буферизация вывода через временный табличный документ

Этот способ незаменим, когда мы не хотим менять структуру основного запроса, но нам нужно сначала сформировать строки, узнать сумму, а уже затем вывести шапку. Идея заключается в использовании вспомогательного («временного») табличного документа.

Проанализируем алгоритм работы этого метода:

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

Посмотрим, как выглядит программная реализация этого подхода:


Пока Выборка.Следующий() Цикл
    // Создаем буфер для табличной части
    ВремТабДок = Новый ТабличныйДокумент;
    ВремТабДок.Вывести(ОбластьСписокШапка);
    
    ВыборкаСписок = Выборка.Список.Выбрать();
    ИтогоСумма = 0;
    
    Пока ВыборкаСписок.Следующий() Цикл
        ОбластьСписок.Параметры.Заполнить(ВыборкаСписок);
        ОбластьСписок.Параметры.ТабНом = ВыборкаСписок.ФИО.Код;
        ОбластьСписок.Параметры.МестоРабот = ВыборкаСписок.МестоПроведенияРабот;
        
        ИтогоСумма = ИтогоСумма + ВыборкаСписок.Сумма;
        
        // Выводим строки во временный документ
        ВремТабДок.Вывести(ОбластьСписок);
    КонецЦикла;
    
    // Заполняем итог под ТЧ во временном документе
    ОбластьСписокИтого.Параметры.ИтогоСумма = ИтогоСумма;
    ВремТабДок.Вывести(ОбластьСписокИтого);
    
    // Теперь выводим основную форму в главный ТабДок
    ТабДок.Вывести(ОбластьЗаголовок);
    
    // Шапка заполняется уже известной суммой!
    Шапка.Параметры.Заполнить(Выборка);
    Шапка.Параметры.ИтогоСумма = ИтогоСумма; 
    ТабДок.Вывести(Шапка);
    
    // Объединяем документы
    ТабДок.Вывести(ВремТабДок);
    
    // Выводим подвал
    Подвал.Параметры.Заполнить(Выборка);
    ТабДок.Вывести(Подвал);
КонецЦикла;

Преимущества: Позволяет гибко управлять порядком вывода без изменения логики получения данных из БД.

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

Способ 3. Расчет итогов на уровне СУБД (Левое соединение с подзапросом)

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

Рассмотрим структуру такого запроса:


ВЫБРАТЬ
    Ведомость.Ссылка КАК Ссылка,
    Ведомость.Номер КАК Номер,
    Ведомость.Дата КАК Дата,
    Ведомость.Ответственный КАК Ответственный,
    ЕСТЬNULL(ТаблицаСумм.ИтогоСумма, 0) КАК ИтогоСумма
ИЗ
    Документ.ВедомостьПодотчет КАК Ведомость
    ЛЕВОЕ СОЕДИНЕНИЕ (
        ВЫБРАТЬ
            Список.Ссылка КАК Ссылка,
            СУММА(Список.Сумма) КАК ИтогоСумма
        ИЗ
            Документ.ВедомостьПодотчет.Список КАК Список
        ГРУППИРОВАТЬ ПО
            Список.Ссылка
    ) КАК ТаблицаСумм ПО Ведомость.Ссылка = ТаблицаСумм.Ссылка
ГДЕ
    Ведомость.Ссылка В (&Ссылка)

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


Пока Выборка.Следующий() Цикл
    ТабДок.Вывести(ОбластьЗаголовок);
    
    Шапка.Параметры.Заполнить(Выборка);
    // Сумма уже есть в выборке!
    Шапка.Параметры.ИтогоСумма = Выборка.ИтогоСумма; 
    ТабДок.Вывести(Шапка);
    
    // Далее стандартно выводим строки...
КонецЦикла;

Преимущества: Высокая скорость работы на стороне СУБД, чистота программного кода 1С, отсутствие лишних объектов в памяти.

Способ 4. Использование классического обхода по группировкам «ИТОГИ ПО»

Этот подход является «золотым стандартом» платформы 1С для построения иерархических отчетов и сложных печатных форм. С помощью предложения ИТОГИ мы указываем платформе автоматически рассчитать агрегатные функции на разных уровнях выборки.

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


ВЫБРАТЬ
    Ведомость.Ссылка КАК Ссылка,
    Ведомость.Номер КАК Номер,
    Ведомость.Дата КАК Дата,
    Список.ФИО КАК ФИО,
    Список.Сумма КАК Сумма
ИЗ
    Документ.ВедомостьПодотчет КАК Ведомость
    ЛЕВОЕ СОЕДИНЕНИЕ Документ.ВедомостьПодотчет.Список КАК Список 
        ПО Ведомость.Ссылка = Список.Ссылка
ГДЕ
    Ведомость.Ссылка В (&Ссылка)
ИТОГИ 
    СУММА(Сумма) 
ПО 
    Ссылка

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


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

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

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

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

Способ 5. Буферизация через массив областей макета

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

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


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

Преимущества: Исключает проблемы несовпадения ширины колонок табличного документа, не требует изменения запроса, потребляет минимум ресурсов.

Резюме: какой метод выбрать?

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

← На главную