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