Как правильно получить остатки на конец дня в 1С и СКД?

Программист 1С v8.3 (Обычные формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

Каждый разработчик 1С рано или поздно сталкивается с классической проблемой: при формировании отчета по остаткам на конец определенного дня в него не попадают документы, проведенные в самую последнюю секунду этого дня (в 23:59:59). Простая передача параметра КонецДня(Дата) в виртуальную таблицу остатков часто приводит к тому, что актуальные движения «теряются». Выясним, почему так происходит, подробно разберем внутренние механизмы работы платформы «1С:Предприятие 8» и СУБД, а также рассмотрим три надежных практических способа решения этой задачи для обычных запросов и Системы компоновки данных (СКД) — для этого подойдёт набор инструментов разработчика и консоль запросов.

В чем кроется причина проблемы: анатомия виртуальной таблицы «Остатки»

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

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

Важнейшее правило работы виртуальной таблицы остатков: расчет итогов всегда производится строго на начало указанной секунды (то есть эксклюзивно, не включая движения самой этой секунды). На уровне SQL-запроса это транслируется в условие со знаком «меньше» (<), а не «меньше или равно» (<=).

Рассмотрим конкретный пример. Предположим, у нас есть документ «Реализация товаров и услуг», проведенный 31 декабря в 23:59:59. Если мы передаем в виртуальную таблицу остатков параметр &ДатаКонец со значением 31.12.2023 23:59:59, СУБД выполнит запрос со следующим условием по времени:

Период < '2023-12-31 23:59:59'

Как мы видим, время проведения документа совпадает с границей запроса. Поскольку используется строгое сравнение, наш документ реализации, записанный ровно в последнюю секунду дня, просто отсекается СУБД и не учитывается при расчете остатка. В результате отчет показывает некорректные данные, не отражающие реальное положение дел на конец дня — для этого подойдёт автоматизированный поиск отрицательных остатков в регистрах.

Для решения этой проблемы в платформе 1С предусмотрено несколько механизмов. Разберем их подробно.

Решение 1. Использование объекта «Граница» (для запросов в коде)

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

Объект Граница принимает два параметра при инициализации:

  1. Значение — это дата со временем (тип Дата) или ссылка на документ (тип ДокументСсылка, МоментВремени).
  2. Вид границы — системное перечисление, которое может принимать значения ВидГраницы.Включая или ВидГраницы.Исключая.

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


ГраницаОстатков = Новый Граница(КонецДня(РабочаяДата), ВидГраницы.Включая);

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

Запрос.УстановитьПараметр("ПериодГраница", ГраницаОстатков);
РезультатЗапроса = Запрос.Выполнить();

При использовании объекта Граница с флагом Включая платформа на уровне СУБД преобразует оператор сравнения из строгого «меньше» в «меньше или равно» (<=). Это гарантирует, что абсолютно все документы, проведенные в последнюю секунду дня, гарантированно попадут в итоговую выборку.

Решение 2. Смещение времени на начало следующего дня (+1 секунда)

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

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

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

Рассмотрим, как прописать такое смещение непосредственно в тексте запроса с помощью встроенных функций языка запросов 1С:


ВЫБРАТЬ
	Взаиморасчеты.Контрагент КАК Контрагент,
	Взаиморасчеты.СуммаОстаток КАК СуммаОстаток
ИЗ
	РегистрНакопления.ВзаиморасчетыСКонтрагентами.Остатки(
		НАЧАЛОПЕРИОДА(ДОБАВИТЬКДАТЕ(&КонечнаяДата, ДЕНЬ, 1), ДЕНЬ), 
		) КАК Взаиморасчеты

Давайте разберем эту конструкцию по шагам:

  1. Функция ДОБАВИТЬКДАТЕ(&КонечнаяДата, ДЕНЬ, 1) прибавляет к выбранной пользователем дате ровно один день. Например, если пользователь выбрал 31.12.2023, мы получаем дату 01.01.2024 с сохранением времени.
  2. Функция НАЧАЛОПЕРИОДА(..., ДЕНЬ) сбрасывает время полученной даты на начало суток, то есть на 00:00:00. На выходе мы получаем чистую дату начала следующего дня: 01.01.2024 00:00:00.

Этот метод чрезвычайно прост, не требует написания сложного программного кода подготовки параметров и отлично работает в любых СУБД.

Решение 3. Реализация в Системе компоновки данных (СКД)

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

Если мы разрабатываем отчет на СКД, лучшим решением будет разделение параметров на пользовательские и системные (служебные). Рассмотрим этот алгоритм по шагам:

  1. Откроем схему компоновки данных нашего отчета и перейдем на вкладку «Параметры».
  2. Создадим или настроим параметр, который будет видеть и заполнять пользователь. Назовем его, например, ПериодОтчета. Установим для него тип значения СтандартныйПериод или Дата.
  3. Для виртуальной таблицы остатков в окне редактирования запроса укажем использование скрытого параметра &ДатаОстатков:
    
    РегистрНакопления.ВзаиморасчетыСКонтрагентами.Остатки(&ДатаОстатков, ) КАК Взаиморасчеты
    
  4. Вернемся на вкладку «Параметры» в СКД. Для нашего скрытого параметра ДатаОстатков установим флаг «Ограничение доступности» (чтобы пользователь не видел его в настройках отчета и не мог изменить вручную).
  5. В колонке «Выражение» для параметра ДатаОстатков пропишем формулу расчета на основе пользовательского параметра.

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


ДобавитьКДате(КонецПериода(&ПериодОтчета.ДатаОкончания, "День"), "Секунда", 1)

Если же пользователь вводит простую дату в параметр Период, формула расчета примет вид:


ДобавитьКДате(КонецПериода(&Период, "День"), "Секунда", 1)

Важное преимущество этого подхода: пользователь указывает в шапке отчета привычную и понятную ему дату (например, «31.12.2023»), а СКД автоматически и незаметно для него пересчитывает этот параметр в 01.01.2024 00:00:00 для передачи в виртуальную таблицу остатков. При этом в заголовках и шапке отчета будет красиво выводиться именно выбранная пользователем дата без лишних служебных секунд.

В чем разница между таблицами «Остатки» и «ОстаткиИОбороты»

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

Таким образом, если в вашем запросе используется таблица ОстаткиИОбороты, вам не нужно делать смещение на одну секунду для получения корректного конечного остатка. Достаточно передать в качестве параметра конца периода обычный конец дня: КонецДня(Дата) или КонецПериода(&Дата, "День").

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

Резюме

Мы подробно проанализировали особенности работы с датами при получении остатков в системе «1С:Предприятие 8». Для успешного решения задач и избежания типичных ошибок всегда придерживайтесь следующих рекомендаций:

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