Как рассчитать период возникновения и суммы долга по месяцам в отчете СКД?

Программист 1С v8.3 (Управляемые формы) 1C:Бухгалтерия Бухгалтерский учет IT и автоматизация бизнеса
← На главную

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

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

Почему возникает ошибка при расчете через функцию «ВычислитьВыражение»

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


ВЫБОР 
    КОГДА ЕстьNULL(ВычислитьВыражение("Остаток", "Период", , "Предыдущая", "Предыдущая"), 0) = 0 
        ТОГДА ВсегоПоДт - ОборотКт 
    ИНАЧЕ ВычислитьВыражение("Остаток", "Период", , "Предыдущая", "Предыдущая") - ОборотКт 
КОНЕЦ

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

Рассмотрим, как правильно решить эту задачу несколькими надежными способами.

Способ 1. Расчет нарастающего итога через границы диапазона в СКД

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

Разберем алгоритм по шагам:

  1. В запросе набора данных подготавливаем два пакета. Первый пакет рассчитывает общую сумму дебета по контрагенту за весь период отчета с учетом начального сальдо: СуммаНачальныйОстатокДт + СуммаОборотДт КАК ВсегоПоДТ.
  2. Второй пакет выбирает помесячные обороты по кредиту: СуммаОборотКт КАК ОборотКт в разрезе периодов и соединяется с первым пакетом.
  3. В СКД на вкладке Настройки в выбранных полях или в ресурсах нарастающий кредитовый оборот рассчитывается выражением: ВычислитьВыражение("Сумма(ОборотКт)", , , "Первая", "Текущая").
  4. Для вычисления остаточного покрытия дебета в текущем месяце используется разность: ВсегоПоДт - ВычислитьВыражение("Сумма(ОборотКт)", , , "Первая", "Текущая"). Если полученное значение уходит в минус, этот минус фиксирует объем образовавшейся задолженности.
  5. Обратим внимание на важную настройку: виртуальная таблица ОстаткиИОбороты регистра бухгалтерии не возвращает строки за месяцы, в которых не было оборотов. Чтобы шкала времени не разрывалась, в настройках группировки СКД для поля Период необходимо включить Тип дополнения: Месяц и указать начальную и конечную даты отчета.

Способ 2. Расчет на уровне запроса через соединение нарастающих итогов

Если требуется получить готовые цифры задолженности непосредственно в результатах запроса без использования сложных формул ресурсов СКД, воспользуемся алгоритмом соединения таблицы самой с собой.

Посмотрим на пример запроса, реализующего расчет нарастающего итога оборотов:


// Помещаем помесячные обороты во временную таблицу
ВЫБРАТЬ
    Хозрасчетный.Субконто1 КАК Контрагент,
    Хозрасчетный.Период КАК Период,
    Хозрасчетный.СуммаОборотКт КАК ОборотКт
ПОМЕСТИТЬ ВТОборотыПоМесяцам
ИЗ
    РегистрБухгалтерии.Хозрасчетный.ОстаткиИОбороты(&НачалоПериода, &КонецПериода, Месяц, , Счет В ИЕРАРХИИ (&Счет), , Организация = &Организация) КАК Хозрасчетный;

////////////////////////////////////////////////////////////////////////////////
// Считаем нарастающий оборот Кт соединением по условию Период <= Период
ВЫБРАТЬ
    Т1.Контрагент КАК Контрагент,
    Т1.Период КАК Период,
    Т1.ОборотКт КАК ОборотКтТекущий,
    СУММА(Т2.ОборотКт) КАК ОборотКтНарастающий,
    СУММА(Т2.ОборотКт) - Т1.ОборотКт КАК ОборотКтПредыдущий
ПОМЕСТИТЬ ВТНарастающиеИтоги
ИЗ
    ВТОборотыПоМесяцам КАК Т1
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТОборотыПоМесяцам КАК Т2 
        ПО Т1.Контрагент = Т2.Контрагент 
        И Т2.Период <= Т1.Период
СГРУППИРОВАТЬ ПО
    Т1.Контрагент,
    Т1.Период,
    Т1.ОборотКт;

Объединив полученные данные с общим дебетом за период (ВсегоПоДТ), рассчитываем долг, возникший именно в текущем периоде. Логика отсечения перекрытия выглядит следующим образом:


ВЫБОР
    // Если нарастающий оборот Кт превысил общий Дт только в этом месяце
    КОГДА ВТНарастающиеИтоги.ОборотКтНарастающий > ИтогиДт.ВсегоПоДТ 
          И ВТНарастающиеИтоги.ОборотКтПредыдущий < ИтогиДт.ВсегоПоДТ
        ТОГДА ВТНарастающиеИтоги.ОборотКтНарастающий - ИтогиДт.ВсегоПоДТ
    // Если общий Дт был полностью исчерпан еще в прошлых месяцах
    КОГДА ВТНарастающиеИтоги.ОборотКтПредыдущий >= ИтогиДт.ВсегоПоДТ
        ТОГДА ВТНарастающиеИтоги.ОборотКтТекущий
    ИНАЧЕ 0
КОНЕЦ КАК СуммаВозникшегоДолга

Важное ограничение: соединение таблицы по неравенству дат имеет вычислительную сложность порядка O(N²). При обработке больших массивов контрагентов за продолжительные периоды времени этот метод может существенно замедлить выполнение запроса на СУБД.

Способ 3. Постобработка результата во встроенном языке 1С

Когда условия распределения долга по периодам требуют сложной бизнес-логики (например, распределение оплат по правилам ФИФО с учетом договоров, видов задолженности и статей платежей), наиболее прозрачным и быстрым решением является последовательный обход выборки в коде модуля отчета.

Проанализируем пример кода с линейной сложностью O(N), где текущий остаток хранится в переменной и уменьшается на каждом шаге цикла:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   Контрагент,
    |   Период,
    |   ОборотКт,
    |   ВсегоПоДт
    |ИЗ
    |   ВТДанныеДляРасчета
    |УПОРЯДОЧИТЬ ПО
    |   Контрагент,
    |   Период";

Результат = Запрос.Выполнить().Выгрузить();

ТекущийКонтрагент = Неопределено;
ОстатокПокрытия = 0;

Для Каждого Стр Из Результат Цикл
    Если Стр.Контрагент <> ТекущийКонтрагент Тогда
        ТекущийКонтрагент = Стр.Контрагент;
        ОстатокПокрытия = Стр.ВсегоПоДт;
    КонецЕсли;
    
    Если ОстатокПокрытия >= Стр.ОборотКт Тогда
        // Оборот текущего месяца полностью покрывается дебетом
        ОстатокПокрытия = ОстатокПокрытия - Стр.ОборотКт;
        Стр.СуммаДолга = 0;
    ИначеЕсли ОстатокПокрытия > 0 Тогда
        // Дебета хватило только на часть оборота
        Стр.СуммаДолга = Стр.ОборотКт - ОстатокПокрытия;
        ОстатокПокрытия = 0;
    Иначе
        // Дебет исчерпан, весь оборот месяца формирует долг
        Стр.СуммаДолга = Стр.ОборотКт;
    КонецЕсли;
КонецЦикла;

Обработанная таким образом ТаблицаЗначений передается обратно в схему компоновки данных в качестве внешнего набора данных. Это освобождает конфигуратор от громоздких выражений на уровне ресурсов и гарантирует точный математический результат без побочных эффектов вычисления агрегатов.

← На главную