Задача: остатки товаров на складе на дату

Вторая классическая задача с собеседования: показать остатки номенклатуры по складам на конкретную дату — и не запутаться с параметром Период и отбором положительных остатков.

Остаток на дату — сколько единиц товара числилось на складе на определённый момент времени. Это накопленный итог всех приходов и расходов до этой даты, который платформа отдаёт готовым из виртуальной таблицы Остатки.

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

Минимальный запрос остатков

Сначала — базовый вариант: остатки всей номенклатуры по всем складам на дату отчёта.

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

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

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

Первый параметр таблицы Остатки — это Период (момент, на который считаем остаток). Второй параметр — условие отбора; мы оставили его пустым (после запятой ничего), значит берём остатки по всей номенклатуре и всем складам. Ресурс называется КоличествоОстаток — снова суффикс Остаток, как и в задаче про долги.

Параметр Период: в чём подвох

Главная ловушка этой задачи — время внутри даты. В 1С дата — это на самом деле дата и время. Если написать УстановитьПараметр("ДатаОстатка", Дата(2026, 2, 1)), платформа поймёт это как «01.02.2026 00:00:00» — самое начало суток. Значит, все документы, проведённые 1 февраля в течение дня, в остаток не войдут. Обычно нужно наоборот — «на конец 1 февраля, со всеми документами этого дня». Для этого дату оборачивают в КонецДня(): она превращается в «01.02.2026 23:59:59».

// Остаток на НАЧАЛО дня (документы за 1 февраля НЕ учтены):
Запрос.УстановитьПараметр("ДатаОстатка", Дата(2026, 2, 1));

// Остаток на КОНЕЦ дня (документы за 1 февраля учтены) — обычно нужно это:
Запрос.УстановитьПараметр("ДатаОстатка", КонецДня(Дата(2026, 2, 1)));

// Актуальный остаток «прямо сейчас» — пустой параметр Период:
// РегистрНакопления.ТоварыНаСкладах.Остатки( , ) КАК Остатки

Отдельно запомните: если совсем не передавать первый параметр (оставить пустым), платформа вернёт остаток на текущий момент — самые свежие данные. Это удобно для отчёта «что на складе сейчас», но для отчёта «на 1-е число» так делать нельзя.

Оставляем только положительные остатки

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

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

Здесь ГДЕ Остатки.КоличествоОстаток > 0 — правильное место для условия: мы фильтруем отдельные строки, группировки нет, агрегатов нет. Никакого ИМЕЮЩИЕ тут не нужно. А сортировка по номенклатуре и складу делает вывод предсказуемым и удобным для чтения.

Результат:

Гвозди 3мм    Основной склад    1 200
Доска 40х150  Основной склад    75
Доска 40х150  Склад №2          18
Саморез 4х50  Склад №2          9 400

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

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

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

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

Путают Период с отбором. Пишут дату во второй параметр вместо первого — и запрос либо падает, либо считает не то. Порядок жёсткий: первый параметр — Период, второй — условие отбора.

Забывают про минусовые остатки. Думают, что Остатки всегда положительны, и не ставят ГДЕ ... > 0. Если в базе есть пересортица или ошибки проведения, в отчёт попадут отрицательные строки, и итог по количеству будет неверным.

Берут остаток из основной таблицы регистра. Иногда новички пишут ИЗ РегистрНакопления.ТоварыНаСкладах без .Остатки(...) и пытаются суммировать движения сами. Это не остаток на дату, а список всех приходов/расходов — совсем другой смысл. Остаток даёт только виртуальная таблица.

Итоги

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