При разработке отчетов по бюджетированию и управлению денежными потоками (Cash Flow) мы часто сталкиваемся с задачей построения прогноза остатков. Обычные виртуальные таблицы остатков и оборотов прекрасно работают с фактическими данными прошлого. Однако, когда мы смотрим в будущее (где есть только плановые обороты, но нет записей в регистрах остатков), стандартные механизмы перестают выдавать непрерывную цепочку: Конечный остаток прошлого месяца = Начальный остаток текущего.
В этой статье мы подробно разберем, как решить проблему "пустых" остатков будущих периодов в Системе Компоновки Данных (СКД). Мы проанализируем ситуацию, когда необходимо к фактическому остатку на текущую дату прибавить плановые поступления и вычесть плановые выплаты нарастающим итогом на год вперед.
Представим, что мы строим отчет План/Факт. У нас есть регистр накопления с фактическими движениями и регистр с планами.
Для прошлых периодов мы легко получаем остатки из виртуальной таблицы ОстаткиИОбороты. Но для будущего месяца (например, январь следующего года) в регистре нет записей, формирующих остаток. Есть только обороты (план).
Если мы попытаемся вывести остатки стандартным способом, то для будущих месяцев увидим пустые поля. Нам же нужно реализовать логику:
НачальныйОстаток = КонечныйОстаток(Предыдущего).КонечныйОстаток = НачальныйОстаток + ПланПриход - ПланРасход.Попытка реализовать это "в лоб" через вычисляемые поля с обращением к предыдущей записи часто приводит к ошибкам или работает только на один шаг вперед (не работает рекурсивно). Разберем два надежных способа решения.
Этот метод, который успешно применил автор темы, заключается в вычислении остатка прямо в тексте запроса через самосоединение таблицы оборотов. Этот подход позволяет получить кумулятивную сумму (нарастающий итог) для каждой записи.
Алгоритм действий:
Давайте посмотрим, как это выглядит на языке запросов 1С. Допустим, у нас есть временная таблица ВТ_Обороты с полями Период и СуммаОборота (Приход - Расход).
ВЫБРАТЬ
ТаблицаИтогов.Период КАК Период,
СУММА(ТаблицаДанных.СуммаОборота) КАК ОстатокНаКонецПериода
ИЗ
ВТ_Обороты КАК ТаблицаИтогов
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Обороты КАК ТаблицаДанных
ПО ТаблицаИтогов.Период >= ТаблицаДанных.Период
СГРУППИРОВАТЬ ПО
ТаблицаИтогов.Период
Как это работает:
Для января программа найдет все записи, где Период <= Январь (только январь). Сумма будет равна обороту января.
Для февраля программа найдет записи, где Период <= Февраль (январь + февраль). Сумма будет равна нарастающему итогу.
Прибавив к этому нарастающему итогу начальный остаток на самый первый период (полученный отдельным запросом), мы получим корректный конечный остаток для любого будущего месяца.
Плюсы метода: Результат готов сразу в запросе, не требует сложных настроек СКД.
Минусы метода: Если периодов очень много (например, планирование по дням на 5 лет), запрос станет "тяжелым", так как количество соединений растет в геометрической прогрессии.
Если мы хотим избежать утяжеления запроса, можно переложить вычисления на процессор компоновки данных. Многие разработчики пытаются использовать функцию ВычислитьВыражение для получения значения предыдущей строки:
ВычислитьВыражение("КонечныйОстаток", "Период", , "Предыдущая")
Это выражение работает некорректно, если КонечныйОстаток сам является вычисляемым полем, зависящим от предыдущего значения. СКД не умеет делать бесконечную рекурсию. Вместо этого нужно использовать логику нарастающего итога, аналогичную первому способу, но средствами СКД.
Формула для ресурса:
Вместо того чтобы искать остаток предыдущего месяца, мы будем каждый раз считать сумму всех оборотов с начала времен ("ОбщийИтог") до текущего момента.
Для поля КонечныйОстаток (ресурс) формула будет выглядеть примерно так:
// Берем зафиксированный начальный остаток на начало отчета
НачальныйОстатокНаСтарт
+
// Прибавляем сумму оборотов с начала группировки до текущей строки
ВычислитьВыражение("Сумма(Оборот)", "Период", "ОбщийИтог", "Текущая")
Разберем параметры функции ВычислитьВыражение:
Для поля НачальныйОстаток формула будет похожей, но суммировать нужно до предыдущей записи:
НачальныйОстатокНаСтарт
+
ВычислитьВыражение("Сумма(Оборот)", "Период", "ОбщийИтог", "Предыдущая")
В случае отсутствия предыдущей записи (первый месяц), функция вернет NULL, поэтому конструкцию желательно обернуть в ЕстьNull(..., 0).
Оба метода (и запрос, и СКД) требуют, чтобы временная ось была непрерывной. Если в каком-то месяце будущего нет ни планов, ни фактов, этот месяц просто исчезнет из выборки. Это приведет к тому, что:
Решение: Использовать метод "Полного календаря". В запросе необходимо создать таблицу всех месяцев периода отчета (например, выбрав данные из РегистрСведений.ДанныеПроизводственногоКалендаря или сгенерировав их) и соединить её левым соединением с вашей таблицей данных. Это гарантирует, что даже пустые месяцы попадут в СКД, и расчет остатков пройдет корректно через них.
Для построения отчетов Cash Flow с прогнозом остатков:
ВычислитьВыражение в ресурсах СКД (быстро, но требует внимательности к синтаксису).