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