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