В процессе разработки отчетов по взаиморасчетам с контрагентами одной из самых частых задач является определение актуального состояния задолженности. Нам необходимо не просто вывести сумму долга, но и понять, является ли этот долг просроченным, сколько дней осталось до планового платежа и на сколько дней клиент уже задерживает оплату. В рамках данной статьи мы подробно разберем, как спроектировать такой отчет в Системе компоновки данных (СКД), рассмотрим оптимальные формулы расчета и разберем архитектурные нюансы, которые уберегут вашу базу данных от зависаний.
Представим типичную бизнес-ситуацию. Нам нужно построить отчет, который анализирует документы отгрузки (например, РеализацияТоваровУслуг) и сопоставляет их с условиями договоров. В договорах контрагентов обычно присутствуют реквизиты: ДопустимаяСуммаЗадолженности и ДопустимоеЧислоДнейЗадолженности (срок отсрочки).
Для корректного вычисления нам понадобятся следующие показатели:
Давайте проанализируем базовый запрос, с которого часто начинают начинающие разработчики, и разберем его ключевые недостатки.
ВЫБОР
КОГДА ДОБАВИТЬКДАТЕ(ВзаиморасчетыСКонтрагентами.Регистратор.Дата, ДЕНЬ, ДоговорыКонтрагентов.ДопустимоеЧислоДнейЗадолженности) > &ТекущаяДата
ТОГДА РАЗНОСТЬДАТ(&ТекущаяДата, ДОБАВИТЬКДАТЕ(ВзаиморасчетыСКонтрагентами.Регистратор.Дата, ДЕНЬ, ДоговорыКонтрагентов.ДопустимоеЧислоДнейЗадолженности), ДЕНЬ)
ИНАЧЕ 0
КОНЕЦ КАК ДниДоОплаты
На первый взгляд, логика верна. Однако, если мы используем этот запрос «в лоб» на реальной базе данных, мы быстро столкнемся с критическими проблемами производительности и логическими ошибками. Рассмотрим их подробнее.
Обратите внимание на конструкцию запроса, где выбираются данные из физической таблицы РегистрНакопления.ВзаиморасчетыСКонтрагентами с последующим левым соединением справочников и фильтрацией через ГДЕ Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг. Это классическая архитектурная ошибка.
При таком подходе СУБД вынуждена выполнять полное сканирование таблицы движений регистра. Если в базе миллионы записей взаиморасчетов, отчет будет выполняться крайне медленно, перегружая сервер. Кроме того, физическая таблица содержит абсолютно все движения — и оплаты, и отгрузки. Если документ реализации уже полностью оплачен год назад, он все равно попадет в этот запрос, и система будет рассчитывать для него «просрочку», которой по факту давно нет.
Как сделать правильно: для анализа долгов и просрочки мы должны использовать исключительно виртуальную таблицу остатков РегистрНакопления.ВзаиморасчетыСКонтрагентами.Остатки. Нам нужно анализировать только те документы, по которым на текущий момент есть ненулевой остаток долга.
Давайте перестроим запрос так, чтобы он работал быстро и корректно. Мы будем использовать виртуальную таблицу остатков, настроив её параметры. В качестве разреза учета (аналитики) выберем объект расчетов (документ реализации).
Рассмотрим структуру правильного запроса:
ВЫБРАТЬ
ВзаиморасчетыОстатки.Контрагент КАК Контрагент,
ВзаиморасчетыОстатки.ДоговорКонтрагента КАК ДоговорКонтрагента,
ВзаиморасчетыОстатки.Сделка КАК РегистраторСсылка,
ВзаиморасчетыОстатки.СуммаВзаиморасчетовОстаток КАК ОстатокДолга,
ВзаиморасчетыОстатки.Сделка.Дата КАК ДатаРеализации,
ЕСТЬNULL(ВзаиморасчетыОстатки.ДоговорКонтрагента.ДопустимоеЧислоДнейЗадолженности, 0) КАК ДопустимоеЧислоДнейЗадолженности,
// Расчет дней до оплаты
ВЫБОР
КОГДА ДОБАВИТЬКДАТЕ(ВзаиморасчетыОстатки.Сделка.Дата, ДЕНЬ, ЕСТЬNULL(ВзаиморасчетыОстатки.ДоговорКонтрагента.ДопустимоеЧислоДнейЗадолженности, 0)) > &ТекущаяДата
ТОГДА РАЗНОСТЬДАТ(&ТекущаяДата, ДОБАВИТЬКДАТЕ(ВзаиморасчетыОстатки.Сделка.Дата, ДЕНЬ, ЕСТЬNULL(ВзаиморасчетыОстатки.ДоговорКонтрагента.ДопустимоеЧислоДнейЗадолженности, 0)), ДЕНЬ)
ИНАЧЕ 0
КОНЕЦ КАК ДниДоОплаты,
// Расчет просроченных дней
ВЫБОР
КОГДА ДОБАВИТЬКДАТЕ(ВзаиморасчетыОстатки.Сделка.Дата, ДЕНЬ, ЕСТЬNULL(ВзаиморасчетыОстатки.ДоговорКонтрагента.ДопустимоеЧислоДнейЗадолженности, 0)) < &ТекущаяДата
ТОГДА РАЗНОСТЬДАТ(ДОБАВИТЬКДАТЕ(ВзаиморасчетыОстатки.Сделка.Дата, ДЕНЬ, ЕСТЬNULL(ВзаиморасчетыОстатки.ДоговорКонтрагента.ДопустимоеЧислоДнейЗадолженности, 0)), &ТекущаяДата, ДЕНЬ)
ИНАЧЕ 0
КОНЕЦ КАК ПросроченоДней
ИЗ
РегистрНакопления.ВзаиморасчетыСКонтрагентами.Остатки(&ПериодГраница, Сделка ССЫЛКА Документ.РеализацияТоваровУслуг) КАК ВзаиморасчетыОстатки
Проанализируем преимущества этого решения:
ЕСТЬNULL защищает нас от ошибок, если в договоре не заполнено допустимое число дней задолженности. В этом случае отсрочка будет считаться равной нулю.ВзаиморасчетыОстатки.Сделка.Дата). В СКД это допустимо и автоматически преобразуется платформой в неявные левые соединения на уровне СУБД.При использовании параметра &ТекущаяДата в запросе начинающие разработчики часто совершают ошибку, заставляя пользователя вручную заполнять этот параметр перед каждым формированием отчета. Мы можем полностью автоматизировать этот процесс встроенными средствами СКД.
Выполним следующие шаги в конструкторе схемы компоновки данных:
ТекущаяДата.ТекущаяДата(). Однако, чтобы исключить влияние времени (часов, минут, секунд) на расчеты разности дней, лучше использовать функцию приведения к началу дня: НачалоПериода(ТекущаяДата(), "День").Рассмотрим важный нюанс, связанный со временем проведения документов. Например, документ реализации был выписан 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) долг еще не считается просроченным. Однако для бизнеса день оплаты наступил, и менеджеры хотят видеть этот документ в списке подлежащих оплате уже с утра.
Чтобы избежать подобных разногласий, при сравнении дат в выражениях запроса рекомендуется приводить обе даты к началу дня с помощью функции НАЧАЛОПЕРИОДА. Измененное выражение для сравнения дат будет выглядеть следующим образом:
НАЧАЛОПЕРИОДА(ДОБАВИТЬКДАТЕ(ДатаРеализации, ДЕНЬ, ЧислоДней), ДЕНЬ) < НАЧАЛОПЕРИОДА(&ТекущаяДата, ДЕНЬ)
Такой подход гарантирует, что расчет просрочки будет вестись строго по календарным дням, без учета конкретного часа и минуты выписки документов.
В сообщениях форума справедливо отмечалось, что расчет дат можно перенести из SQL-запроса на уровень процессора компоновки данных, используя вкладку Вычисляемые поля. Этот подход имеет ряд весомых плюсов: он упрощает сам текст запроса, делает его более читаемым и позволяет задействовать мощный функционал выражений СКД.
Давайте разберем, как перенести расчеты в вычисляемые поля:
ДатаРеализации и ДопустимоеЧислоДнейЗадолженности.ДниДоОплаты и в колонке Выражение пишем формулу:
Выбор Когда ДобавитьКДате(ДатаРеализации, "ДЕНЬ", ДопустимоеЧислоДнейЗадолженности) > &ТекущаяДата Тогда РазностьДат(&ТекущаяДата, ДобавитьКДате(ДатаРеализации, "ДЕНЬ", ДопустимоеЧислоДнейЗадолженности), "ДЕНЬ") Иначе 0 Конец
ПросроченоДней со следующим выражением:
Выбор Когда ДобавитьКДате(ДатаРеализации, "ДЕНЬ", ДопустимоеЧислоДнейЗадолженности) < &ТекущаяДата Тогда РазностьДат(ДобавитьКДате(ДатаРеализации, "ДЕНЬ", ДопустимоеЧислоДнейЗадолженности), &ТекущаяДата, "ДЕНЬ") Иначе 0 Конец
Важное различие в синтаксисе: обратите внимание, что при написании функций РАЗНОСТЬДАТ и ДОБАВИТЬКДАТЕ в вычисляемых полях СКД тип периода (в нашем случае "ДЕНЬ") обязательно должен указываться в двойных кавычках. В обычном запросе 1С тип периода пишется без кавычек (ДЕНЬ). Игнорирование этого правила — одна из самых частых причин возникновения синтаксических ошибок при компиляции схем компоновки.
В реальном бизнесе отсрочка платежа часто предоставляется не в календарных, а в рабочих днях. Использование стандартной функции ДОБАВИТЬКДАТЕ в запросе здесь не поможет, так как она считает исключительно календарные дни без учета государственных праздников и выходных.
Для решения этой задачи вычисляемые поля СКД предоставляют уникальную возможность — прямой вызов экспортных функций общих модулей конфигурации. Посмотрим, как это реализовать:
КалендарныеГрафики) с установленными свойствами «Сервер», «Вызов сервера».
Функция ПолучитьДатуОкончанияСрока(НачальнаяДата, ЧислоРабочихДней) Экспорт
// Здесь располагается логика запроса к регистру сведений "ДанныеПроизводственногоКалендаря"
// Возвращаем рассчитанную дату окончания срока с учетом выходных и праздников
Возврат КалендарныеГрафики.ПолучитьДатуЗавершенияСрока(НачальнаяДата, ЧислоРабочихДней);
КонецФункции
КалендарныеГрафики.ПолучитьДатуОкончанияСрока(ДатаРеализации, ДопустимоеЧислоДнейЗадолженности)
РАЗНОСТЬДАТ для определения точного количества дней просрочки.Таким образом, сочетание оптимизированных запросов к виртуальным таблицам остатков, грамотное использование функций работы с датами и механизмов вычисляемых полей СКД позволяет создавать быстрые, надежные и функциональные отчёты для анализа дебиторской задолженности предприятия.