Как рассчитать дни до оплаты и дни просрочки в СКД 1С

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

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

Постановка задачи и анализ исходных данных

Представим типичную бизнес-ситуацию. Нам нужно построить отчет, который анализирует документы отгрузки (например, РеализацияТоваровУслуг) и сопоставляет их с условиями договоров. В договорах контрагентов обычно присутствуют реквизиты: ДопустимаяСуммаЗадолженности и ДопустимоеЧислоДнейЗадолженности (срок отсрочки).

Для корректного вычисления нам понадобятся следующие показатели:

  1. Дата платежа (Крайний срок): рассчитывается как дата документа отгрузки плюс допустимое число дней отсрочки из договора.
  2. Дни до оплаты: количество дней, оставшихся от текущей даты до крайнего срока платежа. Если срок еще не подошел, мы должны видеть положительное число дней. Если срок прошел — значение должно быть равно нулю (или отсутствовать), чтобы не путать пользователя.
  3. Просрочено дней: количество дней, на которое превышен допустимый срок оплаты относительно текущей даты. Этот показатель должен рассчитываться только тогда, когда текущая дата больше крайнего срока оплаты.

Давайте проанализируем базовый запрос, с которого часто начинают начинающие разработчики, и разберем его ключевые недостатки.


ВЫБОР
    КОГДА ДОБАВИТЬКДАТЕ(ВзаиморасчетыСКонтрагентами.Регистратор.Дата, ДЕНЬ, ДоговорыКонтрагентов.ДопустимоеЧислоДнейЗадолженности) > &ТекущаяДата
        ТОГДА РАЗНОСТЬДАТ(&ТекущаяДата, ДОБАВИТЬКДАТЕ(ВзаиморасчетыСКонтрагентами.Регистратор.Дата, ДЕНЬ, ДоговорыКонтрагентов.ДопустимоеЧислоДнейЗадолженности), ДЕНЬ)
    ИНАЧЕ 0
КОНЕЦ КАК ДниДоОплаты

На первый взгляд, логика верна. Однако, если мы используем этот запрос «в лоб» на реальной базе данных, мы быстро столкнемся с критическими проблемами производительности и логическими ошибками. Рассмотрим их подробнее.

Критический анализ производительности: почему физические таблицы — это табу

Обратите внимание на конструкцию запроса, где выбираются данные из физической таблицы РегистрНакопления.ВзаиморасчетыСКонтрагентами с последующим левым соединением справочников и фильтрацией через ГДЕ Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг. Это классическая архитектурная ошибка.

При таком подходе СУБД вынуждена выполнять полное сканирование таблицы движений регистра. Если в базе миллионы записей взаиморасчетов, отчет будет выполняться крайне медленно, перегружая сервер. Кроме того, физическая таблица содержит абсолютно все движения — и оплаты, и отгрузки. Если документ реализации уже полностью оплачен год назад, он все равно попадет в этот запрос, и система будет рассчитывать для него «просрочку», которой по факту давно нет.

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

Шаг 1. Проектирование оптимального запроса на виртуальных таблицах

Давайте перестроим запрос так, чтобы он работал быстро и корректно. Мы будем использовать виртуальную таблицу остатков, настроив её параметры. В качестве разреза учета (аналитики) выберем объект расчетов (документ реализации).

Рассмотрим структуру правильного запроса:


ВЫБРАТЬ
    ВзаиморасчетыОстатки.Контрагент КАК Контрагент,
    ВзаиморасчетыОстатки.ДоговорКонтрагента КАК ДоговорКонтрагента,
    ВзаиморасчетыОстатки.Сделка КАК РегистраторСсылка,
    ВзаиморасчетыОстатки.СуммаВзаиморасчетовОстаток КАК ОстатокДолга,
    ВзаиморасчетыОстатки.Сделка.Дата КАК ДатаРеализации,
    ЕСТЬNULL(ВзаиморасчетыОстатки.ДоговорКонтрагента.ДопустимоеЧислоДнейЗадолженности, 0) КАК ДопустимоеЧислоДнейЗадолженности,
    
    // Расчет дней до оплаты
    ВЫБОР
        КОГДА ДОБАВИТЬКДАТЕ(ВзаиморасчетыОстатки.Сделка.Дата, ДЕНЬ, ЕСТЬNULL(ВзаиморасчетыОстатки.ДоговорКонтрагента.ДопустимоеЧислоДнейЗадолженности, 0)) > &ТекущаяДата
            ТОГДА РАЗНОСТЬДАТ(&ТекущаяДата, ДОБАВИТЬКДАТЕ(ВзаиморасчетыОстатки.Сделка.Дата, ДЕНЬ, ЕСТЬNULL(ВзаиморасчетыОстатки.ДоговорКонтрагента.ДопустимоеЧислоДнейЗадолженности, 0)), ДЕНЬ)
        ИНАЧЕ 0
    КОНЕЦ КАК ДниДоОплаты,
    
    // Расчет просроченных дней
    ВЫБОР
        КОГДА ДОБАВИТЬКДАТЕ(ВзаиморасчетыОстатки.Сделка.Дата, ДЕНЬ, ЕСТЬNULL(ВзаиморасчетыОстатки.ДоговорКонтрагента.ДопустимоеЧислоДнейЗадолженности, 0)) < &ТекущаяДата
            ТОГДА РАЗНОСТЬДАТ(ДОБАВИТЬКДАТЕ(ВзаиморасчетыОстатки.Сделка.Дата, ДЕНЬ, ЕСТЬNULL(ВзаиморасчетыОстатки.ДоговорКонтрагента.ДопустимоеЧислоДнейЗадолженности, 0)), &ТекущаяДата, ДЕНЬ)
        ИНАЧЕ 0
    КОНЕЦ КАК ПросроченоДней
ИЗ
    РегистрНакопления.ВзаиморасчетыСКонтрагентами.Остатки(&ПериодГраница, Сделка ССЫЛКА Документ.РеализацияТоваровУслуг) КАК ВзаиморасчетыОстатки

Проанализируем преимущества этого решения:

  1. Мы выбираем данные только по тем сделкам (реализациям), по которым остаток долга на дату отчета больше нуля. Если клиент полностью оплатил накладную, запись по ней исчезнет из таблицы остатков, и мы не будем тратить ресурсы на бессмысленные расчеты дат.
  2. Использование функции ЕСТЬNULL защищает нас от ошибок, если в договоре не заполнено допустимое число дней задолженности. В этом случае отсрочка будет считаться равной нулю.
  3. Мы обращаемся к реквизитам через точку (например, ВзаиморасчетыОстатки.Сделка.Дата). В СКД это допустимо и автоматически преобразуется платформой в неявные левые соединения на уровне СУБД.

Шаг 2. Автоматизация параметра «ТекущаяДата» в настройках СКД

При использовании параметра &ТекущаяДата в запросе начинающие разработчики часто совершают ошибку, заставляя пользователя вручную заполнять этот параметр перед каждым формированием отчета. Мы можем полностью автоматизировать этот процесс встроенными средствами СКД.

Выполним следующие шаги в конструкторе схемы компоновки данных:

  1. Перейдем на вкладку Параметры.
  2. Найдем в списке наш параметр с именем ТекущаяДата.
  3. В колонке Выражение укажем стандартную функцию платформы: ТекущаяДата(). Однако, чтобы исключить влияние времени (часов, минут, секунд) на расчеты разности дней, лучше использовать функцию приведения к началу дня: НачалоПериода(ТекущаяДата(), "День").
  4. Установим флажок Ограничение доступности для этого параметра. Это скроет его из формы настроек отчета у пользователя, исключая случайные ошибки ручного ввода. Система сама подставит актуальное время сервера при каждом формировании отчета.

Шаг 3. Типичные ошибки при работе со временем в датах

Рассмотрим важный нюанс, связанный со временем проведения документов. Например, документ реализации был выписан 11 августа в 18:00:00. Отсрочка по договору составляет 2 дня. Крайний срок оплаты, рассчитанный через ДОБАВИТЬКДАТЕ, будет равен 13 августа 18:00:00.

Если мы формируем отчет 13 августа в 10:00:00, то с точки зрения сравнения дат (13.08.2023 18:00:00 > 13.08.2023 10:00:00) долг еще не считается просроченным. Однако для бизнеса день оплаты наступил, и менеджеры хотят видеть этот документ в списке подлежащих оплате уже с утра.

Чтобы избежать подобных разногласий, при сравнении дат в выражениях запроса рекомендуется приводить обе даты к началу дня с помощью функции НАЧАЛОПЕРИОДА. Измененное выражение для сравнения дат будет выглядеть следующим образом:


НАЧАЛОПЕРИОДА(ДОБАВИТЬКДАТЕ(ДатаРеализации, ДЕНЬ, ЧислоДней), ДЕНЬ) < НАЧАЛОПЕРИОДА(&ТекущаяДата, ДЕНЬ)

Такой подход гарантирует, что расчет просрочки будет вестись строго по календарным дням, без учета конкретного часа и минуты выписки документов.

Шаг 4. Реализация логики через «Вычисляемые поля» СКД

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

Давайте разберем, как перенести расчеты в вычисляемые поля:

  1. В запросе мы оставляем только исходные данные: ДатаРеализации и ДопустимоеЧислоДнейЗадолженности.
  2. Переходим на вкладку Вычисляемые поля в конструкторе СКД.
  3. Добавляем новое поле с именем ДниДоОплаты и в колонке Выражение пишем формулу:

    Выбор Когда ДобавитьКДате(ДатаРеализации, "ДЕНЬ", ДопустимоеЧислоДнейЗадолженности) > &ТекущаяДата Тогда РазностьДат(&ТекущаяДата, ДобавитьКДате(ДатаРеализации, "ДЕНЬ", ДопустимоеЧислоДнейЗадолженности), "ДЕНЬ") Иначе 0 Конец

  4. Добавляем второе поле ПросроченоДней со следующим выражением:

    Выбор Когда ДобавитьКДате(ДатаРеализации, "ДЕНЬ", ДопустимоеЧислоДнейЗадолженности) < &ТекущаяДата Тогда РазностьДат(ДобавитьКДате(ДатаРеализации, "ДЕНЬ", ДопустимоеЧислоДнейЗадолженности), &ТекущаяДата, "ДЕНЬ") Иначе 0 Конец

Важное различие в синтаксисе: обратите внимание, что при написании функций РАЗНОСТЬДАТ и ДОБАВИТЬКДАТЕ в вычисляемых полях СКД тип периода (в нашем случае "ДЕНЬ") обязательно должен указываться в двойных кавычках. В обычном запросе 1С тип периода пишется без кавычек (ДЕНЬ). Игнорирование этого правила — одна из самых частых причин возникновения синтаксических ошибок при компиляции схем компоновки.

Шаг 5. Расчет по рабочим дням производственного календаря

В реальном бизнесе отсрочка платежа часто предоставляется не в календарных, а в рабочих днях. Использование стандартной функции ДОБАВИТЬКДАТЕ в запросе здесь не поможет, так как она считает исключительно календарные дни без учета государственных праздников и выходных.

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

  1. Создадим в конфигураторе общий модуль (или используем существующий, например, КалендарныеГрафики) с установленными свойствами «Сервер», «Вызов сервера».
  2. Напишем экспортную функцию, которая рассчитывает дату окончания срока по производственному календарю:
    
    Функция ПолучитьДатуОкончанияСрока(НачальнаяДата, ЧислоРабочихДней) Экспорт
        // Здесь располагается логика запроса к регистру сведений "ДанныеПроизводственногоКалендаря"
        // Возвращаем рассчитанную дату окончания срока с учетом выходных и праздников
        Возврат КалендарныеГрафики.ПолучитьДатуЗавершенияСрока(НачальнаяДата, ЧислоРабочихДней);
    КонецФункции
    
  3. В вычисляемом поле СКД мы можем напрямую вызвать эту функцию в выражении:

    КалендарныеГрафики.ПолучитьДатуОкончанияСрока(ДатаРеализации, ДопустимоеЧислоДнейЗадолженности)

  4. Полученное значение даты мы затем передаем в функцию РАЗНОСТЬДАТ для определения точного количества дней просрочки.

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

← На главную