При работе с регистрами накопления и бухгалтерии в системе 1С:Предприятие программисты часто сталкиваются с так называемой проблемой последней секунды. Суть её заключается в том, что при передаче обычной даты в качестве параметра виртуальной таблицы остатков, движения, совершенные в самую последнюю секунду этого периода, могут не попасть в результат. В данной статье мы подробно разберем, почему так происходит на уровне SQL, как правильно использовать объект Граница и как современные конфигурации (например, БП 3.0) решают задачу отражения операций в «межотчетном периоде».
Рассмотрим стандартную ситуацию. Нам необходимо получить остатки на конец дня 31.12.2023 23:59:59. Мы пишем запрос к виртуальной таблице Остатки и передаем туда эту дату. Однако на уровне СУБД платформа 1С транслирует этот запрос в SQL-код, где условие по периоду выглядит примерно так: WHERE T._Period < @P1. Здесь @P1 — это как раз начало нашей 59-й секунды.
Проанализируем ситуацию: условие «строго меньше» отсекает все движения, которые были записаны ровно в 23:59:59. Таким образом, остатки на 23:59:59 — это фактически остатки на начало этой секунды, а не на её конец. Интересно, что для виртуальных таблиц Обороты платформа использует нестрогое неравенство (<=), поэтому обороты за период всегда полные, а остатки на ту же дату — «недополученные» на одну секунду. Чтобы избежать путаницы и «костылей» в виде добавления одной секунды (что может привести к захвату лишних данных следующего дня), в 1С предусмотрены специальные механизмы.
Для того чтобы включить движения граничного момента в расчет остатков, необходимо использовать объект Граница. Этот объект позволяет явно указать платформе, должна ли точка времени включаться в интервал или нет. Разберем по шагам, как применить этот метод в коде.
Вместо передачи простой даты в параметр Период, мы создаем специальный объект:
ДатаКонца = КонецДня(ТекущаяДата()); // 23:59:59
ПараметрГраница = Новый Граница(ДатаКонца, ВидГраницы.Включая);
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ХозрасчетныйОстатки.СуммаОстаток
|ИЗ
| РегистрБухгалтерии.Хозрасчетный.Остатки(&Период, , , ) КАК ХозрасчетныйОстатки";
Запрос.УстановитьПараметр("Период", ПараметрГраница);
Результат = Запрос.Выполнить();
При использовании ВидГраницы.Включая платформа сформирует SQL-запрос с оператором <=, и все движения последней секунды будут учтены. Это самый правильный и надежный способ получения остатков на любой момент времени.
Если нам нужно получить остатки не просто на конец секунды, а на конкретный документ (чтобы учесть его движения, но не учитывать движения других документов той же секунды, записанных позже), мы используем МоментВремени. Посмотрим на пример:
СсылкаНаДокумент = Документы.РеализацияТоваровУслуг.НайтиПоНомеру("001");
ГраницаПоДокументу = Новый Граница(СсылкаНаДокумент.МоментВремени(), ВидГраницы.Включая);
Запрос.УстановитьПараметр("Период", ГраницаПоДокументу);
Объект МоментВремени содержит в себе и дату, и уникальную ссылку на документ. Это позволяет 1С выстраивать документы внутри одной секунды в строгой последовательности на уровне индексов СУБД. При включении момента времени в границу, мы получаем остатки после проведения этого конкретного документа.
В последних релизах «1С:Бухгалтерия 3.0» появилась возможность отражать операции в межотчетном периоде. Это необходимо для выполнения требований ФСБУ (например, переоценка ОС при изменении учетной политики), когда данные должны попасть во входящее сальдо нового года, но не должны менять баланс старого года. Рассмотрим подробнее, как это реализовано внутри системы.
Многие ошибочно полагают, что 1С начала использовать миллисекунды (например, записывать время как 23:59:59:500). На самом деле, архитектура хранения даты в SQL (datetime или datetime2(0)) в 1С по-прежнему ориентирована на целые секунды. Решение оказалось более изящным — использование реквизита Уточнение периода.
_PeriodAdjustment.Проанализируем ситуацию: такая запись не попадает в стандартную ОСВ за прошлый год (так как отчеты по умолчанию запрашивают данные с уточнением 0), но она уже включена в остатки на начало следующего года. Это позволяет реализовать «резиновый» стык между годами без изменения времени документа.
Выясним причину путаницы с миллисекундами. В языке 1С действительно есть функции вроде ТекущаяУниверсальнаяДатаВМиллисекундах(). Однако важно понимать разницу между типом данных Дата в коде и хранением даты в базе данных. В оперативной памяти при вычитании дат можно получить дробную часть, но при записи в стандартный реквизит типа «Дата» (в документ или регистр), платформа принудительно отсекает всё, что меньше секунды.
Разработчики платформы добавили объект МоментВремениСУточнениемПериода именно для поддержки механизма межотчетности. Посмотрим, как это выглядит в программном интерфейсе:
// Получение значения уточнения (обычно это 5 для межотчетных операций)
Уточнение = РегистрацияВМежотчетномПериоде.ПолучитьУточнениеПериода(ДокументСсылка);
МоментСУточнением = Новый МоментВремени(ДокументСсылка.Дата, ДокументСсылка);
// Внутренние механизмы платформы используют эти данные для построения
// правильного порядка в виртуальных таблицах остатков.
Подводя итог нашему исследованию, выделим главные правила для работы со временем в 1С:
Новый Граница(Дата, ВидГраницы.Включая).МоментВремени.Проанализировав внутреннее устройство СУБД и механизмы платформы, мы видим, что «костыли» с добавлением секунд ушли в прошлое. Современная платформа 1С предоставляет все необходимые инструменты для точного управления временем и позиционирования записей в базе данных.