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