Почему виртуальная таблица Остатки не включает данные на 23:59:59 и как получить правильные остатки на конец дня?

Программист 1С v8.3 (Управляемые формы)
← На главную

При работе с запросами в 1С многие разработчики сталкиваются с ситуацией, которая на первый взгляд кажется нелогичной. При попытке получить остатки на конец дня с помощью функции КОНЕЦПЕРИОДА(&Дата, "ДЕНЬ"), которая возвращает время 23:59:59, движения, сделанные ровно в эту секунду, не попадают в результат. При этом та же самая конструкция в виртуальной таблице оборотов работает корректно, включая все движения до конца дня. Это несоответствие порождает вопросы и может стать настоящей ловушкой.

Давайте подробно разберемся, почему так происходит, и какие существуют правильные способы получения остатков на конец периода.

В чем корень проблемы: Срез на момент времени vs. Данные за интервал

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

Это поведение подтверждается и официальной документацией. В синтакс-помощнике для параметра <МоментВремени> таблицы Остатки сказано:

"...если МоментВремени - то в остатках не будут учтены движения регистра, произошедшие в тот же момент времени."

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

Как правильно получить остатки на конец дня: 3 способа

Зная причину проблемы, мы можем легко ее обойти, используя инструменты, которые предоставляет платформа. Рассмотрим три наиболее распространенных и правильных подхода.

Способ 1. Использование объекта `Граница` (рекомендуемый)

Это самый правильный, явный и "идеологически верный" способ. В платформе 1С существует специальный объект Граница, который предназначен именно для таких случаев — когда нужно точно указать, включать или не включать пограничное значение в интервал.

Объект Граница содержит два параметра: само значение (наша дата) и вид границы (Включая или Исключая).

Чтобы получить остатки с учетом всех движений до конца дня, нужно передать в параметр виртуальной таблицы не просто дату, а объект Граница с видом Включая.

Посмотрим на пример:

Пусть у нас есть параметр запроса &КонПериода, в котором хранится дата, на конец которой мы хотим получить остатки.


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

В коде на 1С перед выполнением запроса мы должны правильно установить значение параметра &ГраницаПериода:


// Получаем конец дня (например, 31.12.2023 23:59:59)
КонецДня = КОНЕЦПЕРИОДА(КонПериода, "ДЕНЬ");

// Устанавливаем параметр запроса с использованием объекта Граница
Запрос.УстановитьПараметр("ГраницаПериода", Новый Граница(КонецДня, ВидГраницы.Включая));

Такой код явно указывает платформе: "Рассчитай остатки на момент времени КонецДня и, пожалуйста, включи в расчет все движения, которые произошли ровно в этот момент". Это наиболее читаемый и надежный подход.

Способ 2. Добавление одной секунды

Это старый и проверенный прием, который также дает корректный результат. Логика его проста: остатки на конец дня 31.12.2023 — это то же самое, что остатки на самое начало следующего дня, то есть на 01.01.2024 00:00:00.

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

Посмотрим на пример:

В запросе параметр остается тем же:


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

А вот установка параметра будет выглядеть иначе:


// Получаем конец дня для нашей даты
КонецДня = КОНЕЦПЕРИОДА(КонПериода, "ДЕНЬ");

// Прибавляем одну секунду, чтобы получить начало следующего дня
НачалоСледующегоДня = ДОБАВИТЬКДАТЕ(КонецДня, "СЕКУНДА", 1);
// Альтернативный и более надежный вариант:
// НачалоСледующегоДня = НАЧАЛОПЕРИОДА(ДОБАВИТЬКДАТЕ(КонПериода, "ДЕНЬ", 1), "ДЕНЬ");

// Устанавливаем параметр запроса
Запрос.УстановитьПараметр("МоментВремени", НачалоСледующегоДня);

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

Способ 3. Использование стандартного объекта `Период`

Если вы работаете с отчетами, особенно на СКД (Система Компоновки Данных), или используете стандартный объект Период, то платформа часто берет работу с границами на себя. У стандартного периода свойство ДатаОкончания уже является границей, включающей конечный момент времени. Для реализации более сложных вариантов вычислений в отчетах можно применить методику расчета нарастающего итога и дельты в СКД.

При настройке параметров виртуальной таблицы в конструкторе запроса на СКД, если вы используете стандартный период, система автоматически подставит правильные значения границ. В качестве примера построения подобных отчетов по регистру «Товары на складах» можно использовать разработку для вывода движений товаров с расчетом нарастающего итога.

В коде это может выглядеть так:


// Где-то в отчете у нас есть стандартный параметр Период
// Его свойство ДатаОкончания уже содержит нужную нам Границу

// Мы просто передаем его в запрос
Запрос.УстановитьПараметр("КонецПериода", Период.ДатаОкончания);

Этот способ удобен при работе со стандартными механизмами, но при написании произвольных запросов лучше контролировать границы явно через первые два способа.

Заключение

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

← На главную