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