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