При разработке отчетов по остаткам товаров с иерархической группировкой разработчики часто сталкиваются с двумя распространенными проблемами: задвоением элементов номенклатуры в табличном документе и отсутствием данных по остаткам для групп (папок) справочника. Разберем причинно-следственную связь этих ошибок и рассмотрим правильные методы их устранения.
Рассмотрим стандартный запрос, в котором выбираются остатки товаров, цены из среза последних и рассчитываются итоги по иерархии номенклатуры:
ВЫБРАТЬ
ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
ТоварыНаСкладахОстатки.Номенклатура.ЭтоГруппа КАК ЭтоГруппа,
ТоварыНаСкладахОстатки.КоличествоОстаток КАК Остаток,
Цены.Цена КАК Цена
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки КАК ТоварыНаСкладахОстатки
ЛЕВОЕ СОЕДИНЕНИЕ (
ВЫБРАТЬ
ЦеныНоменклатурыСрезПоследних.Номенклатура КАК Номенклатура,
МАКСИМУМ(ЦеныНоменклатурыСрезПоследних.Цена) КАК Цена
ИЗ
РегистрСведений.ЦеныНоменклатуры.СрезПоследних КАК ЦеныНоменклатурыСрезПоследних
СГРУППИРОВАТЬ ПО
ЦеныНоменклатурыСрезПоследних.Номенклатура
) КАК Цены
ПО ТоварыНаСкладахОстатки.Номенклатура = Цены.Номенклатура
ИТОГИ
СУММА(Остаток)
ПО
Номенклатура ИЕРАРХИЯ;
Если выполнять обход такого запроса через обычный метод Результат.Выбрать() без указания параметров обхода, платформа совершает линейное считывание всех записей. В этом случае в выборку попадают как иерархические узлы (итоги), так и детальные записи для каждого элемента. В результате один и тот же элемент номенклатуры выводится дважды: сначала как родительский узел иерархии, а затем как конечная детальная строка.
Кроме того, причиной задвоения физических строк в запросе может служить неполная фильтрация в присоединяемых виртуальных таблицах. Если в регистре сведений ЦеныНоменклатуры.СрезПоследних присутствуют цены разных типов, а фильтр по конкретному типу цены не установлен в параметрах виртуальной таблицы, одна позиция номенклатуры вернет несколько записей с разными ценами.
Для корректного вывода дерева иерархии в табличный документ необходимо использовать обход по группировкам. Для этого методу Выбрать() передается специальный параметр ОбходРезультатаЗапроса.ПоГруппировкамСИерархией.
Разберем пример кода программного вывода иерархического отчета:
Результат = Запрос.Выполнить();
ТабДок.НачатьАвтогруппировкуСтрок();
// Указываем тип обхода по группировкам с иерархией
ВыборкаИерархия = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией);
Пока ВыборкаИерархия.Следующий() Цикл
// Заполняем параметры области макета
ОбластьГруппа.Параметры.Заполнить(ВыборкаИерархия);
Если ВыборкаИерархия.ЭтоГруппа Тогда
ОбластьГруппа.Область("R1C1:R1C18").ЦветФона = WebЦвета.СветлоКоричневый;
Иначе
ОбластьГруппа.Область("R1C1:R1C18").ЦветФона = WebЦвета.Белый;
КонецЕсли;
// Выводим строку с учетом уровня иерархии
ТабДок.Вывести(ОбластьГруппа, ВыборкаИерархия.Уровень());
КонецЦикла;
ТабДок.ЗакончитьАвтогруппировкуСтрок();
Использование ОбходРезультатаЗапроса.ПоГруппировкамСИерархией гарантирует, что каждая группа и элемент будут обработаны строго один раз на своем уровне вложенности.
Частой ошибкой начинающих разработчиков является попытка рассчитать остаток для группы номенклатуры отдельным запросом внутри цикла обхода. Проанализируем следующий неоптимальный код:
Функция ОстатокНомНаСкладе(вхНоменклатура, вхСклад)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТоварыНаСкладахОстатки.КоличествоОстаток КАК КоличествоОстаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(
| &ДатаОст,
| Склад = &вхСклад И Номенклатура = &Номенклатура) КАК ТоварыНаСкладахОстатки";
Запрос.УстановитьПараметр("ДатаОст", КонецДня(ЭтотОбъект.Дата));
Запрос.УстановитьПараметр("вхСклад", вхСклад);
Запрос.УстановитьПараметр("Номенклатура", вхНоменклатура);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Возврат Выборка.КоличествоОстаток;
Иначе
Возврат 0;
КонецЕсли;
КонецФункции
Данный подход содержит две критические проблемы:
ЭтоГруппа = Ложь). Условие Номенклатура = &Номенклатура, где в параметр передана группа, всегда вернет пустой результат. Чтобы получить суммарный остаток по всем вложенным элементам папки, необходимо использовать оператор В ИЕРАРХИИ: Номенклатура В ИЕРАРХИИ (&Номенклатура).Разберем грамотную архитектонику получения остатков по складам и ценам в одном запросе без использования циклов. Для этого данные предварительно подготавливаются во временных таблицах, а расчет итогов доверяется секции ИТОГИ.
ВЫБРАТЬ
ТоварыНаСкладахОстатки.Склад КАК Склад,
ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
ТоварыНаСкладахОстатки.КоличествоОстаток КАК КоличествоОстаток
ПОМЕСТИТЬ ВТ_Остатки
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, ) КАК ТоварыНаСкладахОстатки
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_Остатки.Склад КАК Склад,
ВТ_Остатки.Номенклатура КАК Номенклатура,
ВТ_Остатки.КоличествоОстаток КАК Остаток,
ЕСТЬНУЛЛ(Цены.Цена, 0) КАК Цена
ИЗ
ВТ_Остатки КАК ВТ_Остатки
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ТипЦены = &ТипЦены) КАК Цены
ПО ВТ_Остатки.Номенклатура = Цены.Номенклатура
ИТОГИ
СУММА(Остаток)
ПО
Номенклатура ИЕРАРХИЯ,
Склад
Преимущества такого подхода:
ТипЦены = &ТипЦены) исключает размножение строк при левом соединении.Для еще более простой и гибкой настройки иерархических отчетов с разворачиванием по складам и номенклатуре рекомендуется использовать Систему компоновки данных (СКД), которая берет на себя автоматический расчет иерархических итогов и корректную группировку строк.