Как в форме списка или журнала документов вывести итоговые суммы с учетом отборов?

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

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

Попытки решить задачу «в лоб» через обработчик события ПриПолученииДанныхНаСервере часто заходят в тупик, так как этот метод выполняется во внеконтекстном режиме, и напрямую передать рассчитанные данные на форму не удается. Использование временных хранилищ или настроек СКД без понимания механизмов их работы также не приносит желаемого результата.

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

Каждый из этих методов имеет свои особенности, область применения и влияние на производительность системы. Рассмотрим их пошагово.


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

Этот метод является наиболее надежным, так как он учитывает абсолютно все изменения, которые пользователь мог внести в список: быстрые отборы, настройки панели отборов, параметры и даже стандартную строку поиска (вызываемую по Ctrl+F или Alt+F). Начиная с версии платформы 8.3.10, у динамического списка появились методы, позволяющие программно получить ту схему и те настройки, которые применил пользователь в текущий момент времени. Для работы с данными списка через систему компоновки можно использовать инструмент для формирования отчета СКД на основе динамического списка с сохранением отборов.

Давайте по шагам разберем, как реализовать этот алгоритм программно.

Шаг 1. Подготовка формы

Для начала нам нужно создать реквизит формы, в котором будет храниться рассчитанное значение итога. Назовем его, например, ИтогСуммы с типом Число (длина 15, точность 2). Затем в свойствах элемента формы — нашей колонки списка (например, «Сумма документа») — найдем свойство ПутьКДаннымПодвала и укажем туда наш реквизит формы: ИтогСуммы. Не забудьте включить видимость подвала у самой таблицы динамического списка, установив свойство Подвал в значение Истина.

Шаг 2. Создание серверной процедуры расчета

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


&НаСервере
Функция РассчитатьИтогНаСервере() Экспорт

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

КонецФункции

Шаг 3. Вызов расчета и оптимизация производительности

Запускать этот расчет при каждом движении мыши или мгновенно при любом изменении отбора крайне не рекомендуется, особенно если в списке отображаются сотни тысяч документов. Это создаст избыточную нагрузку на СУБД. Существует два правильных подхода к вызову этой процедуры:

  1. По кнопке пользователя: Добавить на командную панель списка кнопку «Рассчитать итоги». Метод самый безопасный для производительности. Пользователь сам решает, когда ему нужно актуализировать цифру.
  2. Асинхронно через обработчик ожидания: Мы можем повесить вызов расчета на обработчик ожидания с задержкой в 1.5–2 секунды. При изменении отбора мы взводим таймер, и если пользователь перестал активно менять настройки, система в фоновом режиме или просто через короткую паузу рассчитывает итог.

Способ 2. Использование пакетных запросов в Динамическом списке (Без сложного кодинга)

Этот метод базируется на интересной особенности работы платформы 1С с динамическими списками. Когда мы пишем запрос для динамического списка, платформа перед отправкой его в СУБД трансформирует этот запрос, чтобы применить пользовательские отборы. Если наш запрос является пакетным, то все пользовательские настройки и отборы автоматически накладываются платформой на самый первый запрос в пакете.

Мы можем использовать это поведение для автоматического подсчета суммы. Проанализируем схему построения такого запроса.

Алгоритм настройки запроса динамического списка:

1. Откроем свойства динамического списка, установим флаг Произвольный запрос и перейдем в конструктор запроса.

2. Напишем пакетный запрос, состоящий из трех частей:


// ЗАПРОС №1: Выбираем ключевые данные и помещаем во временную таблицу.
// Именно на эту таблицу платформа спроецирует все пользовательские отборы!
ВЫБРАТЬ
	ЖурналДокументов.Ссылка КАК Ссылка,
	ЖурналДокументов.Сумма КАК Сумма
ПОМЕСТИТЬ ВТ_ОтфильтрованныеДанные
ИЗ
	ЖурналДокументов.Игры КАК ЖурналДокументов
;

// ЗАПРОС №2: Считаем общую сумму по отфильтрованным данным
ВЫБРАТЬ
	СУММА(ВТ_ОтфильтрованныеДанные.Сумма) КАК ОбщийИтог
ПОМЕСТИТЬ ВТ_ИтоговаяСумма
ИЗ
	ВТ_ОтфильтрованныеДанные КАК ВТ_ОтфильтрованныеДанные
;

// ЗАПРОС №3: Основной запрос, который возвращает данные для списка
// и левым соединением присоединяет к каждой строке наш общий итог
ВЫБРАТЬ
	Данные.Ссылка КАК Ссылка,
	Данные.Дата КАК Дата,
	Данные.Номер КАК Номер,
	Данные.Сумма КАК Сумма,
	Итог.ОбщийИтог КАК ОбщийИтог
ИЗ
	ВТ_ОтфильтрованныеДанные КАК Данные
	ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ИтоговаяСумма КАК Итог ПО ИСТИНА

3. В настройках динамического списка обязательно укажем свойство Основная таблица (например, Документ.Продажи или ваш журнал), чтобы платформа понимала, к какому объекту метаданных относятся данные, и стандартные команды списка (создание, пометка удаления) продолжали корректно работать.

4. Переходим к элементам управляемой формы. У нашей колонки «Сумма» в свойстве ПутьКДаннымПодвала пишем путь к новому полю динамического списка через текущие данные: Элементы.Список.ТекущиеДанные.ОбщийИтог.

Преимущества метода: Мы не пишем ни строчки кода на встроенном языке 1С для вычисления и передачи параметров. Платформа делает все сама средствами СУБД при каждом обновлении списка.

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


Способ 3. Быстрый расчет по выделенным строкам (Клиентский метод)

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

Рассмотрим, как реализовать этот алгоритм.

1. Убедимся, что у нашей таблицы на форме свойство РежимВыделения установлено в значение Множественный.

2. Создадим на форме обработчик события таблицы ПриАктивизацииСтроки.

3. Напишем в модуле формы следующий код:


&НаКлиенте
Процедура СписокПриАктивизацииСтроки(Элемент)
	
	// Вызываем процедуру расчета с небольшой задержкой через подключение обработчика ожидания,
	// чтобы избежать зависаний интерфейса при быстром перемещении стрелками по списку
	ПодключитьОбработчикОжидания("ВыполнитьРасчетВыделенныхСтрок", 0.1, Истина);
	
КонецПроцедуры

&НаКлиенте
Процедура ВыполнитьРасчетВыделенныхСтрок()
	
	ВыделенныеСтроки = Элементы.Список.ВыделенныеСтроки;
	КоличествоСтрок = ВыделенныеСтроки.Количество();
	
	Если КоличествоСтрок = 0 Тогда
		Элементы.СписокСумма.ТекстПодвала = "0.00";
		Возврат;
	КонецЕсли;
	
	// Если выделена только одна строка, нет смысла суммировать, покажем её значение или общую сумму
	СуммаИтога = 0;
	
	Для Каждого ИдентификаторСтроки Из ВыделенныеСтроки Цикл
		ДанныеСтроки = Элементы.Список.ДанныеСтроки(ИдентификаторСтроки);
		Если ДанныеСтроки <> Неопределено Тогда
			СуммаИтога = СуммаИтога + ДанныеСтроки.Сумма;
		КонецЕсли;
	КонецЦикла;
	
	// Выводим результат непосредственно в текст подвала колонки формы
	Элементы.СписокСумма.ТекстПодвала = "Выделено (" + КоличествоСтрок + "): " + Формат(СуммаИтога, "ЧДЦ=2");
	
КонецПроцедуры

Этот простой и элегантный способ крайне популярен среди конечных пользователей, так как имитирует привычную им работу с выделенными ячейками в таблицах MS Excel.


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

Мы проанализировали три кардинально разных подхода к выводу итогов в управляемых формах 1С:

← На главную