При работе со списками документов или регистров в обычных формах часто возникает задача: рассчитать итог по определенной колонке (например, по приходу) программно, учитывая текущие отборы пользователя, но не заставляя его вручную накладывать дополнительные фильтры. Попытки использовать для этого ПостроительОтчета "в лоб" часто приводят к ошибкам типов, когда платформа ругается на некорректное значение или определяет тип системного поля как Неопределено.
В этой статье мы подробно разберем причины этой проблемы и рассмотрим несколько надежных способов ее решения.
Проанализируем исходный код, который часто вызывает ошибку:
Поле = Построитель.Отбор.ПолучитьДоступныеПоля().ВидДвижения;
Поле.ТипЗначения = Новый ОписаниеТипов(ВидДвиженияНакопления.Приход);
Здесь кроются сразу две системные ошибки:
ВидДвиженияНакопления.Приход, тогда как конструктор ожидает строку с именем типа (например, "ВидДвиженияНакопления") или сам объект Тип.ЗаполнитьНастройки(), построитель не сможет автоматически определить метаданные для полей источника данных, из-за чего их тип останется равен Неопределено.Выясним, какими способами можно обойти эти ограничения и решить задачу корректно.
Если мы хотим продолжить использовать ПостроительОтчета на базе источника данных списка, нам необходимо явно вызвать метод инициализации настроек, а также исправить типизацию поля. Рассмотрим правильный вариант кода:
Процедура ДействияФормыИтогиРеестра(Кнопка)
Построитель = Новый ПостроительОтчета;
Построитель.ИсточникДанных = Новый ОписаниеИсточникаДанных(РегистрНакопленияСписок);
// Обязательно инициализируем настройки построителя
Построитель.ЗаполнитьНастройки();
// Если тип поля не определился автоматически, задаем его корректно через имя типа в виде строки
ПолеВидДвижения = Построитель.Отбор.ПолучитьДоступныеПоля().ВидДвижения;
ПолеВидДвижения.ТипЗначения = Новый ОписаниеТипов("ВидДвиженияНакопления");
ПолеВидДвижения.Отбор = Истина;
// Добавляем отбор по Виду движения
НовыйОтбор = Построитель.Отбор.Добавить("ВидДвижения");
НовыйОтбор.ВидСравнения = ВидСравнения.Равно;
НовыйОтбор.Значение = ВидДвиженияНакопления.Приход;
НовыйОтбор.Использование = Истина;
// Выполняем построитель и рассчитываем итог
ТаблицаРезультата = Построитель.Результат.Выгрузить();
СуммаПриход = ТаблицаРезультата.Итог("База");
Предупреждение("Итог по приходу: " + СуммаПриход);
КонецПроцедуры
Как справедливо замечено на форуме, использовать тяжеловесный ПостроительОтчета только для получения одной цифры нецелесообразно. Гораздо более производительным и стабильным решением является применение объекта ПостроительЗапроса. Он позволяет жестко зафиксировать структуру запроса в коде, избежав проблем с динамическим определением типов метаданных, но при этом сохраняет возможность наложить на результат все пользовательские фильтры из экранной формы.
Рассмотрим по шагам, как реализовать этот подход:
Процедура ДействияФормыИтогиРеестраПостроительЗапроса(Кнопка)
Построитель = Новый ПостроительЗапроса;
// Задаем явный текст запроса с возможностью построения отборов {ГДЕ ...}
Построитель.Текст =
"ВЫБРАТЬ
| Рег.База КАК База
|ИЗ
| РегистрНакопления.ИмяВашегоРегистра КАК Рег
|ГДЕ
| Рег.ВидДвижения = &ВидДвиженияПриход
|{ГДЕ
| Рег.Номенклатура.*,
| Рег.Организация.*,
| Рег.Склад.*}"; // здесь перечисляем поля, по которым пользователь может наложить отборы в списке
Построитель.Параметры.Вставить("ВидДвиженияПриход", ВидДвиженияНакопления.Приход);
Построитель.ЗаполнитьНастройки();
// Переносим интерактивные отборы пользователя из списка на форме в построитель запроса
Для Каждого ЭлементОтбора Из РегистрНакопленияСписок.Отбор Цикл
Если ЭлементОтбора.Использование Тогда
ИмяПоля = ЭлементОтбора.ПутьКДанным;
// Проверяем, есть ли такое поле в доступных полях отбора построителя
Если Построитель.Отбор.Найти(ИмяПоля) <> Неопределено Тогда
ЗаполнитьЗначенияСвойств(Построитель.Отбор[ИмяПоля], ЭлементОтбора);
КонецЕсли;
КонецЕсли;
КонецЦикла;
// Выполняем оптимальный запрос
РезультатЗапроса = Построитель.Результат.Выгрузить();
СуммаПриход = РезультатЗапроса.Итог("База");
ПоказатьПредупреждение(, "Сумма прихода с учетом фильтров: " + СуммаПриход);
КонецПроцедуры
Если специфика регистра позволяет, то для получения итогов по приходу правильнее обращаться не к реальной таблице движений физического регистра, а к виртуальной таблице оборотов (РегистрНакопления.ИмяРегистра.Обороты). Поле СуммаПриход в ней уже посчитано на уровне СУБД, что значительно снизит нагрузку на сервер при больших объемах данных.
Для этого мы можем написать обычный объект Запрос, программно обойти коллекцию РегистрНакопленияСписок.Отбор, сформировать текст условий динамически и передать параметры в запрос. Это исключит использование построителей вообще и гарантирует максимальное быстродействие.