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