Как рассчитать начальные и конечные остатки будущих периодов в СКД (Cash Flow)?

Программист 1С v8.3 (Управляемые формы) Управленческий учет Финансы и профессиональные услуги
← На главную

При разработке отчетов по бюджетированию и управлению денежными потоками (Cash Flow) мы часто сталкиваемся с задачей построения прогноза остатков. Обычные виртуальные таблицы остатков и оборотов прекрасно работают с фактическими данными прошлого. Однако, когда мы смотрим в будущее (где есть только плановые обороты, но нет записей в регистрах остатков), стандартные механизмы перестают выдавать непрерывную цепочку: Конечный остаток прошлого месяца = Начальный остаток текущего.

В этой статье мы подробно разберем, как решить проблему "пустых" остатков будущих периодов в Системе Компоновки Данных (СКД). Мы проанализируем ситуацию, когда необходимо к фактическому остатку на текущую дату прибавить плановые поступления и вычесть плановые выплаты нарастающим итогом на год вперед.

Суть проблемы: почему не работает простой запрос?

Представим, что мы строим отчет План/Факт. У нас есть регистр накопления с фактическими движениями и регистр с планами. Для прошлых периодов мы легко получаем остатки из виртуальной таблицы ОстаткиИОбороты. Но для будущего месяца (например, январь следующего года) в регистре нет записей, формирующих остаток. Есть только обороты (план).

Если мы попытаемся вывести остатки стандартным способом, то для будущих месяцев увидим пустые поля. Нам же нужно реализовать логику:

  1. Взять реальный остаток на сегодня.
  2. Для следующего месяца: НачальныйОстаток = КонечныйОстаток(Предыдущего).
  3. КонечныйОстаток = НачальныйОстаток + ПланПриход - ПланРасход.

Попытка реализовать это "в лоб" через вычисляемые поля с обращением к предыдущей записи часто приводит к ошибкам или работает только на один шаг вперед (не работает рекурсивно). Разберем два надежных способа решения.

Способ 1: Расчет нарастающего итога в запросе ("Треугольное соединение")

Этот метод, который успешно применил автор темы, заключается в вычислении остатка прямо в тексте запроса через самосоединение таблицы оборотов. Этот подход позволяет получить кумулятивную сумму (нарастающий итог) для каждой записи.

Алгоритм действий:

  1. Мы формируем таблицу оборотов (объединение факта и плана) по месяцам.
  2. Соединяем эту таблицу саму с собой.
  3. Условие соединения: Период из первой копии таблицы должен быть больше или равен периоду из второй копии таблицы.
  4. Группируем данные и суммируем обороты из второй таблицы.

Давайте посмотрим, как это выглядит на языке запросов 1С. Допустим, у нас есть временная таблица ВТ_Обороты с полями Период и СуммаОборота (Приход - Расход).


ВЫБРАТЬ
    ТаблицаИтогов.Период КАК Период,
    СУММА(ТаблицаДанных.СуммаОборота) КАК ОстатокНаКонецПериода
ИЗ
    ВТ_Обороты КАК ТаблицаИтогов
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Обороты КАК ТаблицаДанных
        ПО ТаблицаИтогов.Период >= ТаблицаДанных.Период
СГРУППИРОВАТЬ ПО
    ТаблицаИтогов.Период

Как это работает:

Для января программа найдет все записи, где Период <= Январь (только январь). Сумма будет равна обороту января.
Для февраля программа найдет записи, где Период <= Февраль (январь + февраль). Сумма будет равна нарастающему итогу.
Прибавив к этому нарастающему итогу начальный остаток на самый первый период (полученный отдельным запросом), мы получим корректный конечный остаток для любого будущего месяца.

Плюсы метода: Результат готов сразу в запросе, не требует сложных настроек СКД.
Минусы метода: Если периодов очень много (например, планирование по дням на 5 лет), запрос станет "тяжелым", так как количество соединений растет в геометрической прогрессии.

Способ 2: Использование функций языка выражений СКД

Если мы хотим избежать утяжеления запроса, можно переложить вычисления на процессор компоновки данных. Многие разработчики пытаются использовать функцию ВычислитьВыражение для получения значения предыдущей строки:

ВычислитьВыражение("КонечныйОстаток", "Период", , "Предыдущая")

Это выражение работает некорректно, если КонечныйОстаток сам является вычисляемым полем, зависящим от предыдущего значения. СКД не умеет делать бесконечную рекурсию. Вместо этого нужно использовать логику нарастающего итога, аналогичную первому способу, но средствами СКД.

Формула для ресурса:

Вместо того чтобы искать остаток предыдущего месяца, мы будем каждый раз считать сумму всех оборотов с начала времен ("ОбщийИтог") до текущего момента.

Для поля КонечныйОстаток (ресурс) формула будет выглядеть примерно так:


// Берем зафиксированный начальный остаток на начало отчета
НачальныйОстатокНаСтарт 
+ 
// Прибавляем сумму оборотов с начала группировки до текущей строки
ВычислитьВыражение("Сумма(Оборот)", "Период", "ОбщийИтог", "Текущая")

Разберем параметры функции ВычислитьВыражение:

Для поля НачальныйОстаток формула будет похожей, но суммировать нужно до предыдущей записи:


НачальныйОстатокНаСтарт 
+ 
ВычислитьВыражение("Сумма(Оборот)", "Период", "ОбщийИтог", "Предыдущая")

В случае отсутствия предыдущей записи (первый месяц), функция вернет NULL, поэтому конструкцию желательно обернуть в ЕстьNull(..., 0).

Важный нюанс: Проблема пустых периодов

Оба метода (и запрос, и СКД) требуют, чтобы временная ось была непрерывной. Если в каком-то месяце будущего нет ни планов, ни фактов, этот месяц просто исчезнет из выборки. Это приведет к тому, что:

  1. Визуально в отчете пропадет месяц.
  2. При расчете "предыдущего" значения может взяться данные не за прошлый месяц, а за позапрошлый (последний, где были данные).

Решение: Использовать метод "Полного календаря". В запросе необходимо создать таблицу всех месяцев периода отчета (например, выбрав данные из РегистрСведений.ДанныеПроизводственногоКалендаря или сгенерировав их) и соединить её левым соединением с вашей таблицей данных. Это гарантирует, что даже пустые месяцы попадут в СКД, и расчет остатков пройдет корректно через них.

Резюме

Для построения отчетов Cash Flow с прогнозом остатков:

  1. Не пытайтесь вычислить остаток рекурсивно (текущий = предыдущий + оборот).
  2. Используйте логику нарастающего итога: текущий остаток = начальный остаток на стартовую дату + сумма всех оборотов от старта до текущей даты.
  3. Реализовать это можно либо самосоединением в запросе (надежно, но ресурсоемко), либо функцией ВычислитьВыражение в ресурсах СКД (быстро, но требует внимательности к синтаксису).
  4. Обязательно обеспечьте непрерывность периодов с помощью таблицы календаря.
← На главную