Как правильно получить остатки товаров по массиву номенклатуры в 1С и вывести позиции с нулевым остатком

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

В процессе разработки в среде 1С:Предприятие 8.3, особенно в таких конфигурациях, как Управление торговлей 11.4, часто возникает задача получения остатков сразу по нескольким позициям номенклатуры. Начинающие разработчики нередко сталкиваются с ситуацией, когда запрос возвращает данные только по одному товару или вовсе игнорирует позиции, которых нет на складе. В этой статье мы подробно разберем, как избежать этих ошибок и написать оптимальный код.

Разбор первой ошибки: преждевременный выход из цикла

Рассмотрим ситуацию, когда при обходе результата запроса в цикле Пока Выборка.Следующий() код обрабатывает только первую строку. Проанализируем причину. Основная ошибка заключается в использовании оператора Возврат внутри тела цикла. Как только программа доходит до первой итерации и выполняет Возврат, выполнение всей функции прекращается, и остальные данные из выборки просто игнорируются.

Чтобы исправить это, нам необходимо сначала собрать все данные в коллекцию (например, в Массив или ТаблицуЗначений) и только после завершения цикла передать этот результат вызывающей процедуре. Посмотрим на правильный пример структуры кода:


&НаСервере
Функция ПолучитьОстаткиНаСервере(МассивНоменклатуры)
    РезультатМассив = Новый Массив;
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
    "ВЫБРАТЬ
    |	СвободныеОстаткиОстатки.Номенклатура КАК Номенклатура,
    |	СвободныеОстаткиОстатки.ВНаличииОстаток КАК Остаток
    |ИЗ
    |	РегистрНакопления.СвободныеОстатки.Остатки(, Номенклатура В (&МассивНоменклатуры)) КАК СвободныеОстаткиОстатки";
    
    Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
    Выборка = Запрос.Выполнить().Выбрать();
    
    Пока Выборка.Следующий() Цикл
        // Создаем структуру для хранения данных по каждой строке
        ДанныеСтроки = Новый Структура;
        ДанныеСтроки.Вставить("Номенклатура", Выборка.Номенклатура);
        ДанныеСтроки.Вставить("Остаток", Выборка.Остаток);
        
        РезультатМассив.Добавить(ДанныеСтроки);
    КонецЦикла;
    
    // Возвращаем собранный массив только после завершения цикла!
    Возврат РезультатМассив;
КонецФункции

Оптимизация запроса: использование параметров виртуальной таблицы

Проанализируем текст запроса. Одной из критических ошибок производительности является наложение фильтра через предложение ГДЕ после выбора данных из виртуальной таблицы остатков. В 1С это работает следующим образом: если условие стоит в ГДЕ, система сначала может рассчитать остатки по ВСЕМ товарам регистра, а уже потом отфильтровать их по вашему списку.

Правильный подход — передавать массив номенклатуры непосредственно в параметры виртуальной таблицы. Это позволяет SQL-серверу на самом низком уровне выбрать данные только для нужных нам записей. Рассмотрим конструкцию подробнее: РегистрНакопления.СвободныеОстатки.Остатки(&Период, Номенклатура В (&МассивНоменклатуры)). Здесь первым параметром идет момент времени (если не указан — текущий), а вторым — условие отбора.

Решение проблемы с «нулевыми» остатками

Выясним причину, по которой товары с нулевым остатком не попадают в результат запроса даже при использовании функции ЕСТЬNULL. Дело в том, что виртуальная таблица Остатки физически не содержит записей для товаров, у которых по регистру нет движений или итог равен нулю. Если записи нет в таблице, то и ЕСТЬNULL применять не к чему.

Чтобы решить эту задачу, нам необходимо выполнить ЛЕВОЕ СОЕДИНЕНИЕ нашего исходного списка товаров с таблицей остатков. Рассмотрим алгоритм решения по шагам:

  1. Загрузим исходный массив номенклатуры во Временную Таблицу.
  2. Выполним ЛЕВОЕ СОЕДИНЕНИЕ этой временной таблицы с виртуальной таблицей остатков.
  3. Используем ЕСТЬNULL(ТаблицаОстатков.ВНаличииОстаток, 0), чтобы заменить пустые значения (NULL) для отсутствующих на складе товаров реальным нулем.

Пример реализации запроса с использованием временной таблицы:


МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;

// Шаг 1: Помещаем массив во временную таблицу
Запрос.Текст = 
"ВЫБРАТЬ
|	ТЗ_Номенклатура.Ссылка КАК Товар
|ПОМЕСТИТЬ ВТ_СписокТоваров
|ИЗ
|	&ТаблицаНоменклатуры КАК ТЗ_Номенклатура";

// Для этого нам понадобится ТаблицаЗначений
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Ссылка", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
Для Каждого ТекТовар Из МассивНоменклатуры Цикл
    ТЗ.Добавить().Ссылка = ТекТовар;
КонецЦикла;

Запрос.УстановитьПараметр("ТаблицаНоменклатуры", ТЗ);
Запрос.Выполнить();

// Шаг 2 и 3: Левое соединение для получения всех позиций, включая нулевые
Запрос.Текст = 
"ВЫБРАТЬ
|	ВТ_СписокТоваров.Товар КАК Номенклатура,
|	ЕСТЬNULL(СвободныеОстатки.ВНаличииОстаток, 0) КАК Остаток
|ИЗ
|	ВТ_СписокТоваров КАК ВТ_СписокТоваров
|		ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.СвободныеОстатки.Остатки(, Номенклатура В (ВЫБРАТЬ Товар ИЗ ВТ_СписокТоваров)) КАК СвободныеОстатки
|		ПО ВТ_СписокТоваров.Товар = СвободныеОстатки.Номенклатура";

РезультатЗапроса = Запрос.Выполнить();

Особенности работы с регистром «Свободные остатки» в УТ 11.4

Разберем специфику конфигурации. Регистр СвободныеОстатки является производным. В УТ 11.4 «свободный остаток» — это не просто наличие на складе, а разница между текущим остатком и зарезервированным товаром. Если в вашей системе включен расширенный контроль остатков или используются механизмы обеспечения потребностей, данные в этом регистре могут не обновляться мгновенно при записи документов, а пересчитываться фоновыми заданиями.

Для получения максимально оперативной информации «здесь и сейчас» профессионалы рекомендуют использовать комбинацию регистров ТоварыНаСкладах (физическое наличие) и ТоварыКОтгрузке (резервы). Однако для большинства пользовательских задач СвободныеОстатки подходят идеально, так как они уже содержат готовую агрегированную информацию.

Передача данных между сервером и клиентом

Важно помнить, что передача больших объемов данных (таких как тяжелые ТаблицыЗначений) между сервером и клиентом может существенно замедлить работу интерфейса. Рассмотрим наиболее эффективную схему:

1. Сбор данных на сервере: Складываем результаты запроса в массив структур. 2. Передача на клиент: Возвращаем этот массив в процедуру на клиенте. 3. Обработка на клиенте: Используем метод НайтиСтроки() для табличной части объекта, чтобы быстро заполнить соответствующие колонки Остаток в форме документа.

Проанализируем, как это выглядит на практике:


&НаКлиенте
Процедура ОбновитьОстаткиВФорме(Команда)
    МассивТоваров = Новый Массив;
    Для Каждого Стр Из Объект.Товары Цикл
        МассивТоваров.Добавить(Стр.Номенклатура);
    КонецЦикла;
    
    ДанныеОбОстатках = ПолучитьОстаткиНаСервере(МассивТоваров);
    
    // Обходим полученные данные и заполняем форму
    Для Каждого Запись Из ДанныеОбОстатках Цикл
        Отбор = Новый Структура("Номенклатура", Запись.Номенклатура);
        НайденныеСтроки = Объект.Товары.НайтиСтроки(Отбор);
        Для Каждого ТекСтрока Из НайденныеСтроки Цикл
            ТекСтрока.ОстатокНаСкладе = Запись.Остаток;
        КонецЦикла;
    КонецЦикла;
КонецПроцедуры

Подведение итогов

Мы проанализировали основные сложности при работе с остатками в запросах 1С. Помните три главных правила: никогда не делайте Возврат внутри цикла перебора данных, всегда используйте параметры виртуальных таблиц для фильтрации и применяйте ЛЕВОЕ СОЕДИНЕНИЕ с временной таблицей, если вам нужно видеть товары с нулевыми остатками. Соблюдение этих принципов сделает ваш код быстрым, надежным и понятным для коллег.

← На главную