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