При построении отчетов в платформе 1С:Предприятие 7.7 разработчики часто сталкиваются с задачей отбора позиций номенклатуры, по которым образовался отрицательный конечный остаток. Попытка применить стандартную конструкцию вида |Условие(КолКонОст < 0); приводит к ошибке или некорректным результатам.
Разберем причину такого поведения платформы. Секция Условие() в черных запросах 1С 7.7 работает как фильтр отдельных движений (аналог инструкции WHERE в SQL). В языке запросов 1С 7.7 отсутствует прямой аналог конструкции HAVING, позволяющий фильтровать уже сгруппированные и посчитанные агрегатные функции, такие как КонОст() или Сумма(). Если написать условие на ресурс регистра, система проверит значение этого ресурса в каждой конкретной записи движения, а не итоговый конечный остаток по позиции.
Рассмотри эффективные и проверенные способы решения этой проблемы.
Наиболее надежный и правильный способ на встроенном языке 1С 7.7 — разделение задачи на два этапа. Сначала первым запросом мы собираем список позиций с отрицательным остатком, а вторым запросом формируем итоговый отчет с правильным расчетом всех иерархических группировок.
Рассмотрим пошагово реализацию этого алгоритма:
СписокЗначений.Условие(Номенклатура в СписокОтрицательных).Посмотрим на пример кода:
Перем Запрос1, Запрос2, ТекстЗапроса1, ТекстЗапроса2, СписокОтрицательных;
СписокОтрицательных = СоздатьОбъект("СписокЗначений");
// Этап 1: Отобрать номенклатуру с отрицательным остатком
Запрос1 = СоздатьОбъект("Запрос");
ТекстЗапроса1 = "
|Период с НачДата по КонДата;
|Номенклатура = Регистр.ОстаткиТМЦ.Номенклатура;
|Количество = Регистр.ОстаткиТМЦ.Количество;
|Функция КолКонОст = КонОст(Количество);
|Группировка Номенклатура Без Групп;
|";
Если Запрос1.Выполнить(ТекстЗапроса1) = 1 Тогда
Пока Запрос1.Группировка("Номенклатура") = 1 Цикл
Если Запрос1.КолКонОст < 0 Тогда
СписокОтрицательных.ДобавитьЗначение(Запрос1.Номенклатура);
КонецЕсли;
КонецЦикла;
КонецЕсли;
// Этап 2: Сформировать итоговый запрос с иерархией и итогами
Запрос2 = СоздатьОбъект("Запрос");
ТекстЗапроса2 = "
|Период с НачДата по КонДата;
|Номенклатура = Регистр.ОстаткиТМЦ.Номенклатура;
|Количество = Регистр.ОстаткиТМЦ.Количество;
|Функция КолКонОст = КонОст(Количество);
|Группировка Номенклатура;
|Условие(Номенклатура в СписокОтрицательных);
|";
Если Запрос2.Выполнить(ТекстЗапроса2) = 1 Тогда
// Вывод результатов запроса в печатную форму
КонецЕсли;
Другой интересный вариант — использование пользовательской функции модуля внутри конструкции Функция ... = Сумма(...). Язык запросов 1С 7.7 позволяет вызывать функции глобального модуля или модуля формы при вычислении агрегатов.
Проанализируем пример реализации данного метода:
Функция ПолучитьОтрицОстаток(Товар, Склад)
Ост = Регистр.ОстаткиТоваров.СводныйОстаток(Товар, Склад, "ОстатокТовара");
Возврат ?(Ост < 0, Ост, 0);
КонецФункции
Процедура Сформировать()
Перем Запрос, ТекстЗапроса;
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса = "
|Товар = Регистр.ОстаткиТоваров.Товар;
|Склад = Регистр.ОстаткиТоваров.Склад;
|ОстатокТовара = Регистр.ОстаткиТоваров.ОстатокТовара;
|Функция ОстатокТовараКонОст = КонОст(ОстатокТовара);
|Функция ТолькоОтрицательные = Сумма(ПолучитьОтрицОстаток(Товар, Склад));
|Группировка Склад Без Групп;
|Группировка Товар Без Групп;
|";
Если Запрос.Выполнить(ТекстЗапроса) = 1 Тогда
// Обработка результатов
КонецЕсли;
КонецПроцедуры
Важно: этот способ вычисляет сводный остаток на текущий момент (или точку актуальности) с помощью метода СводныйОстаток(), поэтому его удобно применять для оперативности, но нужно учитывать логику работы с временными периодами.
Если структура отчета не требует сложных общих итогов по вышестоящим группировкам или итоги выводятся динамически в процессе печати, фильтрацию можно выполнить непосредственно в цикле обхода результата запроса.
Рассмотрим пример кода фильтрации при выводе:
Пока Запрос.Группировка("Склад") = 1 Цикл
Пока Запрос.Группировка("Номенклатура") = 1 Цикл
// Проверяем конечное значение агрегатной функции
Если Запрос.КолКонОст < 0 Тогда
Таб.ВывестиСекцию("Строка");
КонецЕсли;
КонецЦикла;
КонецЦикла;
В случаях, когда обработка выполняется на больших объемах данных и критична скорость работы, эффективным решением становится использование внешних компонентов (например, 1С++) для выполнения прямых SQL-запросов к базам данных.
При использовании прямых запросов отбор отрицательных остатков выполняется на стороне СУБД с помощью стандартной инструкции HAVING SUM(...) < 0. Это позволяет мгновенно отсечь лишние данные из виртуальных таблиц остатков (таблиц RG*) еще до передачи данных на клиентский ПК.
Подводя итог: для типовых отчетов на 1С 7.7 оптимальным выбором является двухэтапный запрос через СписокЗначений, так как он гарантирует корректность иерархических итогов и не требует внешних компонент.