Почему запрос к регистру накопления не видит остатки на конец месяца?

Программист 1С v8.3 (Управляемые формы) Управленческий учет Промышленность, строительство и АПК
← На главную

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

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


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

Такой код не возвращает результат. Однако, если изменить установку параметра на следующую, данные появляются:

Запрос.УстановитьПараметр("ДатаКон", КонецМесяца(докРСВ.Дата) + 1);

Выясним, в чем кроется причина такого поведения платформы.

Фундаментальная причина: как 1С работает со временем

Ключ к пониманию проблемы лежит в том, как платформа 1С обрабатывает дату и время. Дело не просто в секундах, а в более точном понятии — МоментВремени.

  1. Точность до секунды и позиция документа. Внутри одной секунды может быть проведено множество документов. Чтобы система могла их упорядочить, используется объект МоментВремени. Он состоит из двух частей: самой даты (с точностью до секунды) и уникальной ссылки на документ-регистратор. Это позволяет точно определить позицию каждого движения на временной оси.
  2. Поведение виртуальной таблицы Остатки. Когда мы запрашиваем данные у виртуальной таблицы ИмяРегистра.Остатки(&Период), она возвращает срез данных строго на начало указанного в параметре &Период момента времени. Если мы передаем дату 31.01.2024 23:59:59, запрос вернет остатки, которые были на самое начало этой секунды, но не будет включать движения документов, проведенных ровно в эту секунду.
  3. Почему Граница не помогает? Использование объекта Новый Граница(Дата, ВидГраницы.Включая) в данном случае не решает проблему. Когда граница создается только на основе даты (без указания конкретного документа), она все равно позиционируется перед всеми движениями, которые произошли в ту же секунду. Чтобы включить движения конкретного документа, в границу нужно передавать его МоментВремени, а не просто дату.

Таким образом, первоначальный запрос не работал, потому что он запрашивал остатки на 23:59:59, но до того, как были учтены документы, проведенные в эту последнюю секунду месяца.

Правильный способ получения остатков на конец периода

Исходя из механики работы платформы, самый надежный и общепринятый способ получить остатки на конец любого периода (дня, месяца, квартала) — это запросить их на начало следующего периода.

Для нашей задачи это означает, что вместо конца месяца нужно использовать начало следующего дня. Это гарантирует, что абсолютно все движения, включая те, что были совершены в 23:59:59, попадут в расчет.

Посмотрим на правильную реализацию:


// Получаем дату конца месяца
ДатаКонцаМесяца = КонецМесяца(докРСВ.Дата);

// Устанавливаем параметр запроса на начало следующего дня
// Вариант 1: прибавление секунды
Запрос.УстановитьПараметр("ДатаКон", ДатаКонцаМесяца + 1);

// Вариант 2: использование функции НачалоДня (более наглядно)
// Запрос.УстановитьПараметр("ДатаКон", НачалоДня(ДатаКонцаМесяца + 86400)); // 86400 секунд в сутках

// Вариант 3 (самый читаемый):
КонецПериода = КонецМесяца(докРСВ.Дата);
НачалоСледующегоПериода = НачалоДня(КонецПериода) + 86400;
Запрос.УстановитьПараметр("ДатаКон", НачалоСледующегоПериода);

Этот подход является стандартной практикой при работе с виртуальной таблицей Остатки и обеспечивает получение корректных данных.

Почему же тогда стандартные отчеты все показывают верно?

Это справедливый вопрос, который часто возникает. Если наш запрос требует "танцев с бубном", почему стандартные отчеты, вроде "Ведомости по товарам", всегда показывают правильный остаток на конец дня?

Ответ кроется в использовании другой виртуальной таблицы — ОстаткиИОбороты.

Рассмотрим их ключевые различия:

Именно поэтому в стандартных отчетах, которые практически всегда используют таблицу ОстаткиИОбороты, нет необходимости прибавлять секунду к концу периода. Система делает все сама.

Итоги и практические рекомендации

Чтобы избежать ошибок и путаницы в будущем, следуйте этим простым правилам:

  1. Для получения остатков на конец периода (дня, месяца и т.д.) через запрос к виртуальной таблице ИмяРегистра.Остатки, всегда используйте в качестве параметра начало следующего за этим периодом дня. Например, КонецМесяца(Дата) + 1.
  2. Для получения полного набора данных за период (начальный остаток, обороты, конечный остаток), используйте виртуальную таблицу ИмяРегистра.ОстаткиИОбороты. В ней можно смело использовать НачалоПериода и КонецПериода без каких-либо добавлений.
  3. Используйте Границу с осторожностью. Объект Граница с видом Включая действительно полезен, но в основном тогда, когда нужно получить остатки строго после движений конкретного документа. В этом случае в конструктор передается не просто дата, а МоментВремени этого документа. Для простых срезов на конец дня этот объект может привести к путанице.
← На главную