Часто разработчики 1С сталкиваются с ситуацией, когда простой, на первый взгляд, запрос к виртуальной таблице остатков регистра накопления не возвращает данные на конец периода (например, на конец месяца). При этом стандартные отчеты показывают, что остатки есть, а запрос на начало следующего дня внезапно начинает работать корректно. Разберем, почему так происходит и как правильно получать срез данных на определенный момент времени.
Проанализируем исходную проблему. Есть запрос, который должен получить остатки из регистра НезавершенноеПроизводство на конец месяца:
Запрос = Новый Запрос(
"ВЫБРАТЬ
| НезавершенноеПроизводствоОстатки.Подразделение,
| НезавершенноеПроизводствоОстатки.СтатьяЗатрат,
| // ... другие поля
| НезавершенноеПроизводствоОстатки.КоличествоОстаток,
| НезавершенноеПроизводствоОстатки.СтоимостьОстаток
|ИЗ
| РегистрНакопления.НезавершенноеПроизводство.Остатки(
| &ДатаКон,
| // ... условия
| ) КАК НезавершенноеПроизводствоОстатки
|ГДЕ
| // ... условия отбора
|");
Запрос.УстановитьПараметр("ДатаКон", Новый Граница(КонецМесяца(докРСВ.Дата), ВидГраницы.Включая));
Такой код не возвращает результат. Однако, если изменить установку параметра на следующую, данные появляются:
Запрос.УстановитьПараметр("ДатаКон", КонецМесяца(докРСВ.Дата) + 1);
Выясним, в чем кроется причина такого поведения платформы.
Ключ к пониманию проблемы лежит в том, как платформа 1С обрабатывает дату и время. Дело не просто в секундах, а в более точном понятии — МоментВремени.
МоментВремени. Он состоит из двух частей: самой даты (с точностью до секунды) и уникальной ссылки на документ-регистратор. Это позволяет точно определить позицию каждого движения на временной оси.Остатки. Когда мы запрашиваем данные у виртуальной таблицы ИмяРегистра.Остатки(&Период), она возвращает срез данных строго на начало указанного в параметре &Период момента времени. Если мы передаем дату 31.01.2024 23:59:59, запрос вернет остатки, которые были на самое начало этой секунды, но не будет включать движения документов, проведенных ровно в эту секунду.Граница не помогает? Использование объекта Новый Граница(Дата, ВидГраницы.Включая) в данном случае не решает проблему. Когда граница создается только на основе даты (без указания конкретного документа), она все равно позиционируется перед всеми движениями, которые произошли в ту же секунду. Чтобы включить движения конкретного документа, в границу нужно передавать его МоментВремени, а не просто дату.Таким образом, первоначальный запрос не работал, потому что он запрашивал остатки на 23:59:59, но до того, как были учтены документы, проведенные в эту последнюю секунду месяца.
Исходя из механики работы платформы, самый надежный и общепринятый способ получить остатки на конец любого периода (дня, месяца, квартала) — это запросить их на начало следующего периода.
Для нашей задачи это означает, что вместо конца месяца нужно использовать начало следующего дня. Это гарантирует, что абсолютно все движения, включая те, что были совершены в 23:59:59, попадут в расчет.
Посмотрим на правильную реализацию:
// Получаем дату конца месяца
ДатаКонцаМесяца = КонецМесяца(докРСВ.Дата);
// Устанавливаем параметр запроса на начало следующего дня
// Вариант 1: прибавление секунды
Запрос.УстановитьПараметр("ДатаКон", ДатаКонцаМесяца + 1);
// Вариант 2: использование функции НачалоДня (более наглядно)
// Запрос.УстановитьПараметр("ДатаКон", НачалоДня(ДатаКонцаМесяца + 86400)); // 86400 секунд в сутках
// Вариант 3 (самый читаемый):
КонецПериода = КонецМесяца(докРСВ.Дата);
НачалоСледующегоПериода = НачалоДня(КонецПериода) + 86400;
Запрос.УстановитьПараметр("ДатаКон", НачалоСледующегоПериода);
Этот подход является стандартной практикой при работе с виртуальной таблицей Остатки и обеспечивает получение корректных данных.
Это справедливый вопрос, который часто возникает. Если наш запрос требует "танцев с бубном", почему стандартные отчеты, вроде "Ведомости по товарам", всегда показывают правильный остаток на конец дня?
Ответ кроется в использовании другой виртуальной таблицы — ОстаткиИОбороты.
Рассмотрим их ключевые различия:
РегистрНакопления.ИмяРегистра.Остатки(&Период)
Предназначена для получения среза данных на один конкретный момент времени. Она имеет один основной параметр Период. Как мы выяснили, она возвращает остатки на начало указанного момента.
РегистрНакопления.ИмяРегистра.ОстаткиИОбороты(&НачалоПериода, &КонецПериода, ...)
Предназначена для анализа данных за интервал. Она принимает как минимум два параметра: НачалоПериода и КонецПериода. Платформа при работе с этой таблицей обрабатывает параметр КонецПериода как включающую границу. Она корректно рассчитывает поле КонечныйОстаток с учетом всех движений внутри заданного интервала, включая документы, проведенные в последнюю секунду.
Именно поэтому в стандартных отчетах, которые практически всегда используют таблицу ОстаткиИОбороты, нет необходимости прибавлять секунду к концу периода. Система делает все сама.
Чтобы избежать ошибок и путаницы в будущем, следуйте этим простым правилам:
ИмяРегистра.Остатки, всегда используйте в качестве параметра начало следующего за этим периодом дня. Например, КонецМесяца(Дата) + 1.ИмяРегистра.ОстаткиИОбороты. В ней можно смело использовать НачалоПериода и КонецПериода без каких-либо добавлений.Границу с осторожностью. Объект Граница с видом Включая действительно полезен, но в основном тогда, когда нужно получить остатки строго после движений конкретного документа. В этом случае в конструктор передается не просто дата, а МоментВремени этого документа. Для простых срезов на конец дня этот объект может привести к путанице.