При разработке отчетов на системе компоновки данных (СКД) мы часто сталкиваемся с задачей детализации движений по документам-регистраторам. Типичная ситуация: необходимо построить отчет по заказам (на производство или покупателя) и показать хронологию изменения остатка по каждому документу. Казалось бы, задача тривиальная — взять виртуальную таблицу ОстаткиИОбороты и вывести поля.
Однако на практике мы сталкиваемся с проблемой, которую описал автор темы: остатки начинают «скакать». Конечный остаток одной строки не совпадает с начальным остатком следующей. Например, первый документ закрыл остаток 7, а следующий документ показывает начальный остаток 0 или 3. Давайте разберемся, почему так происходит, и проанализируем методы решения этой задачи, чтобы получить корректный, непрерывный поток остатков.
Прежде чем переходить к написанию формул, давайте проанализируем природу хранения данных в регистрах накопления. Виртуальная таблица ОстаткиИОбороты рассчитывает ресурсы в разрезе всех измерений регистра.
Предположим, у нас есть регистр с измерениями: Заказ, Номенклатура.
Если мы строим отчет с группировкой по Заказу, а затем детальные записи по Регистратору, система видит это так:
Для СКД это два разных потока данных. Конечный остаток Товара А никак не перетекает в начальный остаток Товара Б. Именно поэтому мы видим нули или неожиданные цифры в колонке «Начальный остаток» следующей строки — система показывает остаток именно по тому набору измерений, который участвовал в движении этого документа.
Если нам необходимо видеть общую картину по Заказу, игнорируя, какая именно номенклатура двигалась, самым простым решением будет исключение лишних измерений из выборки.
В конструкторе запроса схемы компоновки данных:
ОстаткиИОбороты.Регистратор.Номенклатура. Мы группируем данные по Заказу и Регистратору, суммируя ресурсы.Однако этот метод работает не всегда. Часто нам все же нужно видеть номенклатуру в других группировках или использовать её для расшифровки. Если мы просто уберем измерение, мы потеряем аналитику. Если оставим — получим разрыв остатков.
Это наиболее универсальный и профессиональный метод. Идея заключается в том, чтобы отказаться от стандартных полей НачальныйОстаток и КонечныйОстаток, предоставляемых виртуальной таблицей, и рассчитать их математически прямо в отчете, используя механизм выражений ресурсов.
Мы будем имитировать поведение ведомости: текущий остаток = остаток на начало периода + все обороты с начала периода до текущего момента.
В наборе данных запроса нам понадобятся:
НачальныйОстаток (на начало всего периода отчета, а не на момент документа).Оборот (приход минус расход).Регистратор.Перейдем на вкладку Ресурсы в схеме компоновки данных. Здесь мы можем использовать мощную функцию языка выражений СКД — ВычислитьВыражение. Она позволяет обращаться к другим строкам группировки.
Чтобы получить Конечный остаток по строке (нарастающим итогом внутри Заказа), используем следующую логику:
Конечный остаток = Остаток на начало отчета + Сумма всех оборотов от первой записи до текущей.
Формула для ресурса «КонечныйОстаток» (на уровне группировки по Регистратору):
Сумма(НачальныйОстаток) + ВычислитьВыражение("Сумма(Оборот)", , , "Первая", "Текущая")
Разберем параметры функции:
"Сумма(Оборот)" — что суммируем."Первая" — начало диапазона суммирования (первая строка в группировке Заказа)."Текущая" — конец диапазона (текущая строка документа).Чтобы получить Начальный остаток для текущей строки (чтобы он совпадал с конечным предыдущей), нам нужно просуммировать обороты до предыдущей строки:
Формула для ресурса «НачальныйОстаток»:
Сумма(НачальныйОстаток) + ВычислитьВыражение("Сумма(Оборот)", , , "Первая", "Предыдущая")
Важно: Функция с параметром "Предыдущая" для самой первой строки вернет NULL (или 0), поэтому к результату нужно добавить глобальный начальный остаток по группировке.
Чтобы описанный выше метод работал корректно и строки не перемешивались, нарушая хронологию (что приведет к неверному нарастающему итогу), необходимо соблюсти несколько правил.
Система должна понимать хронологию документов. Зайдем на вкладку Наборы данных, выделим поле Регистратор (или ПериодСекунда, если вы его используете) и проверим колонку «Роль».
Период.В настройках отчета структура должна быть следующей:
Заказ.Регистратор (или Детальные записи, если они упорядочены).Вычисление выражения с параметрами "Первая", "Текущая" корректно работает именно в рамках родительской группировки (Заказа).
Если в вашей выборке могут быть пустые значения оборотов, рекомендуется обернуть поля в функцию ЕстьNULL на этапе запроса или внутри ВычислитьВыражение, чтобы арифметика не ломалась.
ЕстьNULL(Сумма(НачальныйОстаток), 0) +
ЕстьNULL(ВычислитьВыражение("Сумма(Оборот)", , , "Первая", "Текущая"), 0)
Применив этот подход, мы получим следующую картину в отчете:
Заказ №1 (Начальный остаток: 0)
Как видите, несмотря на то, что во втором документе участвовал Товар Б, начальный остаток для него был взят как общий итог по Заказу (10), а не 0. Цепочка остатков стала непрерывной.
Проблема «скачущих» остатков в СКД при детализации до регистратора решается переходом от использования готовых полей виртуальной таблицы к самостоятельному расчету нарастающего итога. Использование функции ВычислитьВыражение позволяет игнорировать разрывы измерений (например, разную номенклатуру) и строить непрерывный баланс в рамках выбранной аналитики (Заказа).