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