Каждый практикующий разработчик и аналитик в среде «1С:Предприятие 8» рано или поздно сталкивается с классической, но от этого не менее коварной проблемой: документы, проведенные ровно в последнюю секунду периода (например, 31 числа в 23:59:59), упорно отказываются попадать в конечные остатки отчетов. При этом в оборотах за месяц эти суммы отображаются корректно. Стоит перенести документ хотя бы на одну секунду назад (на 23:59:58) — и баланс чудесным образом сходится.
В этой статье мы подробно разберем, почему возникает такое поведение платформы, как устроены механизмы виртуальных таблиц остатков, и рассмотрим все возможные способы решения этой проблемы: от написания правильного кода запроса до использования расширений и административных методов.
Для начала проанализируем ситуацию и поймем, почему обычная передача даты конца периода в параметры виртуальной таблицы остатков приводит к ошибке. Источник проблемы кроется в логике работы платформы «1С:Предприятие» с временными точками.
Когда мы обращаемся к виртуальной таблице остатков регистра накопления:
РегистрНакопления.РасчетыСКлиентамиПоСрокам.Остатки(&КонецПериода)
и передаем в параметр &КонецПериода обычное значение типа Дата (например, 31.12.2023 23:59:59), платформа по умолчанию рассчитывает остатки исключая эту граничную секунду. То есть расчет производится на самый первый миг (начало) указанной секунды. Все движения, которые были записаны в базу данных ровно в 23:59:59, фактически оказываются за пределами этой временной границы и не попадают в итоговую выборку.
При этом виртуальная таблица ОстаткиИОбороты работает по совершенно иной логике — она включает граничную секунду по умолчанию. Именно из-за этого несоответствия возникает неприятный диссонанс: обороты в отчете верны, а конечный остаток «плывет» на сумму документов, проведенных в последнюю секунду.
Ниже мы подробно рассмотрим четыре практических способа решения этой проблемы в зависимости от того, имеете ли вы доступ к изменению кода конфигурации.
Если вы самостоятельно разрабатываете отчет или логику получения остатков в коде, самым правильным и стандартным решением является использование специального объекта платформы — Граница.
Этот объект позволяет явно указать системе, нужно ли включать документы, записанные на граничную секунду, в расчет остатков. Для этого используется перечисление ВидГраницы.Включая.
Рассмотрим пример правильной передачи параметра в код запроса:
// Получаем конечную дату периода (например, конец дня)
КонечнаяДата = КонецДня(Параметры.Период);
// Формируем объект Граница, включающий все движения на эту секунду
ГраницаОстатков = Новый Граница(КонечнаяДата, ВидГраницы.Включая);
// Устанавливаем параметр в запрос
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| РасчетыПоСрокам.ПредоплатаРеглОстаток КАК НашДолгКонечныйОстаток
|ИЗ
| РегистрНакопления.РасчетыСКлиентамиПоСрокам.Остатки(&КонецПериода, ) КАК РасчетыПоСрокам";
Запрос.УстановитьПараметр("КонецПериода", ГраницаОстатков);
РезультатЗапроса = Запрос.Выполнить();
Использование объекта Граница гарантирует, что любые документы, проведенные в последнюю секунду указанной даты, будут учтены при расчете остатков.
Очень часто отчеты строятся на СКД без использования ручного написания кода в модулях объектов. В самом конструкторе запросов СКД мы не можем напрямую объявить объект Граница. Однако мы можем элегантно обойти это ограничение на уровне настроек параметров схемы.
Для этого мы можем прибавить к дате конца периода одну секунду, тем самым перенося расчет остатков на начало следующего дня (на 00:00:00 следующего числа). Поскольку на первую секунду нового дня документов в базе обычно еще нет, этот метод абсолютно безопасен и точен.
Разберем по шагам, как настроить это в СКД:
КонецПериода).
ДОБАВИТЬКДАТЕ(КОНЕЦПЕРИОДА(&КонецПериода, "ДЕНЬ"), "СЕКУНДА", 1)
В результате этого несложного преобразования платформа рассчитает остатки на первую секунду следующего месяца, автоматически захватив все документы предыдущего дня, включая проведенные в 23:59:59.
Что делать, если конфигурация находится на жесткой поддержке, полностью закрыта для изменений, а отчет является типовым? В этом случае на помощь приходит современный механизм платформы — Расширения конфигурации.
Мы можем создать расширение и перехватить процедуру компоновки или формирования отчета, принудительно преобразовав параметр даты в объект Граница перед его передачей в СКД.
Пример программного переопределения параметров в модуле отчета через расширение:
&Вместо("ПриКомпоновкеРезультата")
Процедура Расш_ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
// Получаем значение параметра КонецПериода из настроек компоновщика
ПараметрКонецПериода = КомпоновщикНастроек.Настройки.ПараметрыДанных.НайтиЗначениеПараметра(Новый ПараметрКомпоновкиДанных("КонецПериода"));
Если ПараметрКонецПериода <> Неопределено И ЗначениеЗаполнено(ПараметрКонецПериода.Значение) Тогда
// Преобразуем обычную дату в объект Граница Включая
ГраницаПериода = Новый Граница(КонецДня(ПараметрКонецПериода.Значение), ВидГраницы.Включая);
ПараметрКонецПериода.Значение = ГраницаПериода;
КонецЕсли;
// Вызываем оригинальный метод
ПродолжитьВызов(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка);
КонецПроцедуры
Этот способ позволяет решить проблему раз и навсегда для любого типового отчета без снятия конфигурации с поддержки и без проблем с последующими обновлениями.
Если коллизии с остатками возникают регулярно из-за документов, приходящих из внешних систем (например, при обмене из «1С:Бухгалтерия» или сторонних CRM/ERP по API), хорошим решением будет предотвратить появление документов со временем 23:59:59 в самой базе данных.
Для этого можно использовать подписку на событие ПередЗаписью или обработчик ПриЗаписи в расширении для документов поступления денежных средств, реализации товаров и услуг и т.д. В обработчике проверяется время документа: если оно равно 23:59:59, то время принудительно смещается на минуту или секунду назад.
Проанализируем пример кода для модуля документа:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Если документ проводится концом дня, смещаем его время
Если Час(ЭтотОбъект.Дата) = 23 И Минута(ЭтотОбъект.Дата) = 59 И Секунда(ЭтотОбъект.Дата) = 59 Тогда
ЭтотОбъект.Дата = ЭтотОбъект.Дата - 1; // Смещаем ровно на 1 секунду до 23:59:58
КонецЕсли;
КонецПроцедуры
Это административно-техническое решение полностью исключает попадание документов на «проблемную» секунду, гарантируя корректное отображение данных в любых, даже не доработанных программистами отчетах.
Описанная выше проблема с отчетом «Ведомость расчетов с клиентами» в конфигурациях УТ 11.4, КА 2.4 и ERP 2.4 была официально признана разработчиками фирмы «1С» как баг. Были зарегистрированы следующие коды ошибок:
В актуальных релизах конфигураций этот косяк был исправлен разработчиками «1С» на уровне общего модуля формирования отчетов СКД. Параметры компоновки теперь автоматически приводятся к типу Граница перед выполнением запросов к виртуальным таблицам. Если вы столкнулись с данной ошибкой, наиболее простым и надежным решением будет обновление вашей конфигурации до актуального релиза.