Как рассчитать задолженность с индивидуальным сроком оплаты и почему возникает синтаксическая ошибка в параметрах виртуальной таблицы?

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

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

Анализ проблемы и причина синтаксической ошибки

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


ВЫБРАТЬ РАЗРЕШЕННЫЕ
    ВзаиморасчетыОстаткиИОбороты.Договор КАК Договор,
    СУММА(ВзаиморасчетыОстаткиИОбороты.СуммаНачальныйОстаток) КАК СуммаНачальныйОстаток,
    СУММА(ВзаиморасчетыОстаткиИОбороты.СуммаКонечныйОстаток) КАК СуммаКонечныйОстаток
ИЗ
    РегистрНакопления.Взаиморасчеты.ОстаткиИОбороты(
        ДОБАВИТЬКДАТЕ(&КонецПериода, ДЕНЬ, (-1) * ВзаиморасчетыОстаткиИОбороты.Договор.СрокОплаты),
        &КонецПериода,
        Авто,
        ,
    ) КАК ВзаиморасчетыОстаткиИОбороты
СГРУППИРОВАТЬ ПО
    ВзаиморасчетыОстаткиИОбороты.Договор

При выполнении или проверке такой конструкции платформа выдает ошибку:

«Неверные параметры "РегистрНакопления.Взаиморасчеты.ОстаткиИОбороты, 0"» или «Синтаксическая ошибка "ВЫБРАТЬ"».

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

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

Рассмотрим рабочие методы решения этой задачи.

Способ 1. Получение конечного остатка и оборотов за максимальный интервал

Если известна максимальная отсрочка платежа среди всех клиентов (или ее можно ограничить, например, 60, 90 или 180 днями), задачу можно решить в одном классическом запросе без использования построчных циклов. Разберем логику: получаем остаток на конечную дату и обороты за максимально возможный период отсрочки, после чего отсекаем движения индивидуально для каждого договора с помощью конструкции ВЫБОР КОГДА.


ВЫБРАТЬ
    ВзаиморасчетыСКонтрагентамиОстатки.Контрагент КАК Контрагент,
    ВзаиморасчетыСКонтрагентамиОстатки.ДоговорКонтрагента КАК Договор,
    ВзаиморасчетыСКонтрагентамиОстатки.СуммаВзаиморасчетовОстаток КАК КонОст
ПОМЕСТИТЬ тОст
ИЗ
    РегистрНакопления.ВзаиморасчетыСКонтрагентами.Остатки(&КонДата, ) КАК ВзаиморасчетыСКонтрагентамиОстатки
;

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

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ЕСТЬNULL(тОборот.Контрагент, тОст.Контрагент) КАК Контрагент,
    ЕСТЬNULL(тОборот.Договор, тОст.Договор) КАК Договор,
    ЕСТЬNULL(тОст.КонОст, 0) - ЕСТЬNULL(тОборот.Оборот, 0) КАК НачОст,
    ЕСТЬNULL(тОст.КонОст, 0) КАК КонОст,
    ЕСТЬNULL(тОборот.Оборот, 0) КАК Оборот
ИЗ
    тОст КАК тОст
        ПОЛНОЕ СОЕДИНЕНИЕ тОборот КАК тОборот
        ПО тОст.Контрагент = тОборот.Контрагент
            И тОст.Договор = тОборот.Договор

Проанализируем этот алгоритм:

  1. Первый запрос выбирает конечный долг на требуемую дату среза.
  2. Второй запрос берет обороты по дням с использованием параметра &МаксОтсрочка. В секции ВЫБОР КОГДА отсекаются все обороты, дата которых наступила раньше, чем индивидуальный срок оплаты по конкретному договору.
  3. Итоговый запрос соединяет остатки с вычисленными оборотами, позволяя математически восстановить начальный остаток долга на момент начала периода отсрочки.

Способ 2. Связывание двух наборов данных в СКД

Если отчет разрабатывается в Системе компоновки данных (СКД), можно разделить получение данных на два связанных набора:

  1. Первый набор данных (Запрос): обращается к справочнику договоров и выбирает ссылку на договор и реквизит СрокОплаты. В вычисляемых полях или прямо в запросе рассчитывается дата: ДОБАВИТЬКДАТЕ(&КонецПериода, ДЕНЬ, -СрокОплаты) КАК ДатаНачалаСреза.
  2. Второй набор данных (Запрос): обращается к регистру накопления (например, к таблице ОстаткиИОбороты), где в параметрах виртуальной таблицы указан параметр: &НачалоПериода.
  3. Связи наборов данных: на вкладке «Связи наборов данных» устанавливается связь между первым и вторым набором по полю Договор. В колонке «Параметры» указывается, что значение параметра &НачалоПериода берется из вычисленного поля ДатаНачалаСреза первого набора.

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

Способ 3. Расчет задолженности в разрезе документов поставки

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


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

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

← На главную