Задача: дебиторская задолженность

Разбираем классический вопрос с собеседования: как одним запросом получить задолженность контрагентов на нужную дату и оставить только должников.

Дебиторская задолженность — это деньги, которые контрагенты должны нашей компании: отгрузили товар или оказали услугу, а оплату ещё не получили. В 1С такие суммы обычно хранятся в регистре накопления «Взаиморасчёты с контрагентами».

Почти на каждом собеседовании 1С-разработчика звучит просьба: «Напишите запрос, который покажет, сколько нам должны контрагенты на первое число месяца». Задача выглядит простой, но проверяет сразу несколько важных навыков: понимаете ли вы регистры накопления, умеете ли работать с виртуальными таблицами и знаете ли, чем отбор через ИМЕЮЩИЕ отличается от отбора через ГДЕ. Разберём её по шагам.

Что вообще такое «долг на дату»

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

У регистра накопления «Остатки» такая таблица называется Остатки. Обращаемся к ней так: РегистрНакопления.ВзаиморасчётыСКонтрагентами.Остатки(&ДатаОтчёта). Первый параметр в скобках — Период: на какой момент нам нужен остаток. Именно этот параметр отличает «сколько должны прямо сейчас» от «сколько были должны на 1 января».

Шаг 1. Простой запрос остатков

Начнём с минимального рабочего запроса — просто вытащим остатки по всем контрагентам на дату:

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

Запрос.УстановитьПараметр("ДатаОтчёта", КонецДня(ДатаОтчёта));

Результат = Запрос.Выполнить().Выбрать();
Пока Результат.Следующий() Цикл
    Сообщить(Результат.Контрагент.Наименование + ": " + Результат.Долг);
КонецЦикла;

Обратите внимание на три вещи. Во-первых, поле в виртуальной таблице «Остатки» называется не Сумма, а СуммаОстаток — платформа добавляет суффикс Остаток к каждому ресурсу регистра. Во-вторых, дату мы передаём параметром &ДатаОтчёта, а не вставляем в текст запроса — так безопаснее и быстрее. В-третьих, мы обернули дату в КонецДня(): без этого «01.02.2026» означает начало суток (00:00:00), и документы, проведённые в течение самого дня, в остаток не попадут.

Шаг 2. Оставляем только должников

Запрос выше вернёт всех контрагентов, у кого есть хоть какое-то движение. Но нас просили показать должников — тех, у кого долг больше нуля. Первая мысль новичка — дописать ГДЕ Взаиморасчёты.СуммаОстаток > 0. И для этого запроса такой вариант действительно сработает. Но на собеседовании любят усложнять: «А если по одному контрагенту несколько договоров, и нужно суммировать долг по всем договорам, а потом отобрать?» Вот тут появляется группировка — и вместе с ней ИМЕЮЩИЕ.

ВЫБРАТЬ
    Взаиморасчёты.Контрагент КАК Контрагент,
    СУММА(Взаиморасчёты.СуммаОстаток) КАК Долг
ИЗ
    РегистрНакопления.ВзаиморасчётыСКонтрагентами.Остатки(&ДатаОтчёта) КАК Взаиморасчёты
СГРУППИРОВАТЬ ПО
    Взаиморасчёты.Контрагент
ИМЕЮЩИЕ
    СУММА(Взаиморасчёты.СуммаОстаток) > 0
УПОРЯДОЧИТЬ ПО
    Долг УБЫВ

Здесь мы свернули строки по контрагенту через СГРУППИРОВАТЬ ПО, сложили долги по всем его договорам через СУММА(...), а затем оставили только тех, у кого итоговая сумма положительна. Отбор по агрегату — это и есть работа для ИМЕЮЩИЕ. В конце отсортировали по убыванию, чтобы самые крупные должники были сверху — так отчёт читается удобнее.

Результат:

ООО «Ромашка»    148 500,00
ИП Петров        52 300,00
ООО «Вектор»     9 800,00

Как это работает

Ключевая идея — понять порядок, в котором платформа выполняет части запроса. Сначала берётся источник данных (виртуальная таблица Остатки — платформа рассчитывает остатки на переданную дату). Потом строки группируются по контрагенту. Затем для каждой группы вычисляются агрегаты — СУММА. И только после этого применяется ИМЕЮЩИЕ: оно фильтрует уже готовые сгруппированные строки. Поэтому в ИМЕЮЩИЕ можно писать СУММА(...) > 0, а в ГДЕ — нельзя: на момент ГДЕ суммы ещё не посчитаны.

Правило простое: ГДЕ фильтрует отдельные строки до группировки, ИМЕЮЩИЕ — группы после неё. Если условие можно проверить на исходной строке (например, «только по конкретной организации»), ставьте его в параметры виртуальной таблицы или в ГДЕ — это быстрее. Если условие про итог группы («сумма долга больше нуля») — только ИМЕЮЩИЕ.

// Условие по организации — до группировки, в параметры ВТ (быстро):
РегистрНакопления.ВзаиморасчётыСКонтрагентами.Остатки(
    &ДатаОтчёта,
    Организация = &Организация) КАК Взаиморасчёты
// ...
ИМЕЮЩИЕ
    СУММА(Взаиморасчёты.СуммаОстаток) > 0   // условие по итогу — после группировки

Частые ошибки

Забыли КонецДня() у даты. Передали в параметр ТекущаяДата() или дату без времени — и «потеряли» все документы за сегодняшний день. Долг оказывается меньше реального. Всегда явно решайте, включён ли последний день в отчёт, и оборачивайте границу в КонецДня().

Пишут СуммаОстаток как Сумма. Поле в виртуальной таблице «Остатки» — с суффиксом. Если написать просто Сумма, платформа выдаст ошибку «поле не найдено». В таблице «Обороты» суффикс другой — СуммаОборот. Всегда сверяйтесь с составом виртуальной таблицы в конструкторе запроса.

Тянут отбор должников в ГДЕ при группировке. Условие ГДЕ СУММА(...) > 0 просто не скомпилируется — агрегатов в ГДЕ быть не может. А отбор по сырому СуммаОстаток в ГДЕ при наличии нескольких договоров даст неверный результат: отфильтруются отдельные строки договоров, а не итоговый долг контрагента.

Считают долг перебором документов в цикле. Медленно, тяжело для базы и почти всегда с ошибками в логике «приход минус расход». Регистр остатков и виртуальная таблица Остатки существуют именно для того, чтобы этого не делать.

Итоги

  • Долг на дату берём из виртуальной таблицы Остатки регистра накопления, дату передаём параметром &ДатаОтчёта в её первый аргумент (Период).
  • Ресурс в «Остатки» называется с суффиксом — СуммаОстаток, а не Сумма.
  • Границу периода оборачиваем в КонецДня(), чтобы не потерять документы за последний день.
  • Отбор «только должники» по сумме нескольких договоров делаем через СГРУППИРОВАТЬ ПО + ИМЕЮЩИЕ СУММА(...) > 0.
  • Помним порядок: ГДЕ — до группировки (по строкам), ИМЕЮЩИЕ — после (по группам).
Проверьте себя
1. Как в виртуальной таблице Остатки регистра накопления называется ресурс Сумма?
AСумма
BСуммаОстаток
CСуммаОборот
DОстатокСуммы
2. Нужно оставить только контрагентов, у которых суммарный долг по всем договорам больше нуля, при этом строки сгруппированы по контрагенту. Куда поставить условие?
AВ ГДЕ: ГДЕ СУММА(СуммаОстаток) > 0
BВ параметры виртуальной таблицы Остатки
CВ ИМЕЮЩИЕ: ИМЕЮЩИЕ СУММА(СуммаОстаток) > 0
DВ УПОРЯДОЧИТЬ ПО