Как вывести остатки номенклатуры для всех строк табличной части на определенную дату?

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

При разработке документов или обработок в 1С часто возникает задача: заполнить колонку "Остаток" в табличной части для списка товаров, который пользователь добавил вручную. Начинающие программисты часто сталкиваются с тем, что остаток выводится только для одной (текущей) строки или код работает крайне медленно из-за запросов в цикле.

В этой статье мы подробно разберем, как правильно и оптимально получить остатки для произвольного списка номенклатуры, избежать типичных ошибок производительности и корректно отобразить данные на форме.

Разбор типичных ошибок

Прежде чем перейти к правильному решению, проанализируем, почему код может работать некорректно. Рассмотрим ситуацию, с которой столкнулся автор вопроса.

  1. Обращение к текущим данным: Использование конструкции Элементы.Остатки.ТекущиеДанные возвращает данные только той строки, которая в данный момент выделена курсором. Если мы хотим получить остатки для всей таблицы, нам необходимо перебирать коллекцию Объект.Товары (или другую табличную часть), а не обращаться к элементам формы.
  2. Запрос в цикле: Самая грубая ошибка производительности — это вызов серверной функции с запросом внутри цикла обхода строк таблицы. Если в документе 100 строк, система сделает 100 вызовов сервера и 100 запросов к базе данных.
  3. Неверная обработка результата: Даже если запрос выполнен правильно, новички часто возвращают из функции Выборка.Остаток, забывая, что запрос вернул таблицу значений, и нужно сопоставить конкретный товар с его остатком.

Правильный алгоритм решения

Чтобы задача решалась быстро и корректно, мы будем придерживаться следующего алгоритма:

  1. На клиенте (или на сервере) формируем массив ссылок на всю номенклатуру из табличной части.
  2. Выполняем один запрос к базе данных, передавая туда весь массив сразу.
  3. Результат запроса выгружаем в коллекцию, по которой удобного искать (например, Соответствие).
  4. Обходим табличную часть документа, находим для каждой строки значение в полученном соответствии и заполняем остаток.

Реализация на практике

Разберем код по частям. Предположим, у нас есть табличная часть Товары с колонками Номенклатура и Остаток.

Шаг 1. Сбор данных и вызов функции

Этот код может выполняться на клиенте (например, по нажатию кнопки "Заполнить остатки"). Наша задача — собрать номенклатуру в массив и передать его на сервер.


&НаКлиенте
Процедура ЗаполнитьОстатки(Команда)
    
    // 1. Собираем массив номенклатуры
    МассивНоменклатуры = Новый Массив;
    Для Каждого СтрокаТЧ Из Объект.Товары Цикл
        // Добавляем только заполненную номенклатуру, чтобы не искать пустые ссылки
        Если ЗначениеЗаполнено(СтрокаТЧ.Номенклатура) Тогда
            МассивНоменклатуры.Добавить(СтрокаТЧ.Номенклатура);
        КонецЕсли;
    КонецЦикла;
    
    // Если товаров нет, нет смысла идти на сервер
    Если МассивНоменклатуры.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;
    
    // 2. Получаем данные с сервера (Дата берется из реквизита документа или текущая)
    ДатаОстатков = Объект.Дата; 
    СоответствиеОстатков = ПолучитьОстаткиНаСервере(МассивНоменклатуры, ДатаОстатков);
    
    // 4. Заполняем табличную часть полученными данными
    Для Каждого СтрокаТЧ Из Объект.Товары Цикл
        
        // Пытаемся получить остаток из соответствия
        НайденныйОстаток = СоответствиеОстатков.Получить(СтрокаТЧ.Номенклатура);
        
        Если НайденныйОстаток <> Неопределено Тогда
            СтрокаТЧ.Остаток = НайденныйОстаток;
        Иначе
            // Если товара не было в выборке, значит остатка нет
            СтрокаТЧ.Остаток = 0;
        КонецЕсли;
        
    КонецЦикла;

КонецПроцедуры

Шаг 2. Серверная функция и Запрос

Теперь рассмотрим самое важное — функцию ПолучитьОстаткиНаСервере. Здесь мы используем виртуальную таблицу остатков с отбором по переданному массиву. Также важно использовать объект Граница, если мы хотим получить остатки на конкретный момент времени с учетом движений документов.


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

КонецФункции

Важные нюансы реализации

Давайте подробно разберем ключевые моменты, использованные в коде:

1. Использование объекта Соответствие

Почему мы возвращаем Соответствие, а не массив или таблицу значений? На клиенте нам нужно для каждой строки ТЧ быстро найти её остаток. Метод Соответствие.Получить(Ключ) работает гораздо быстрее, чем поиск строки в таблице значений методом Найти() внутри цикла. Это обеспечивает высокую скорость работы даже при больших табличных частях.

2. Функция ЕСТЬNULL

В запросе мы использовали конструкцию ЕСТЬNULL(..., 0). В случае реальной таблицы остатков это не всегда обязательно (так как виртуальная таблица просто не вернет записи для отсутствующих товаров), но это хорошая привычка. Если бы мы использовали левое соединение с таблицей товаров, отсутствие ЕСТЬNULL привело бы к появлению значения NULL, что вызвало бы ошибку при записи в числовое поле на форме.

3. Параметры виртуальной таблицы

Обратите внимание: отбор Номенклатура В (&МассивНоменклатуры) накладывается внутри параметров виртуальной таблицы .Остатки(...), а не в секции ГДЕ. Это критически важно для производительности. СУБД сначала отфильтрует записи регистра, и только потом рассчитает по ним остатки. Если использовать ГДЕ, система сначала рассчитает остатки по всей базе, а потом отсечет лишнее.

4. Граница времени

Использование Новый Граница(Дата, ВидГраницы.Включая) гарантирует, что мы получим остаток с учетом движений, совершенных именно в эту секунду. Если передать просто дату, остаток будет рассчитан на начало этой секунды.

Оптимизация при вызове с сервера

Если ваш код выполняется полностью на сервере (например, при проведении документа или в серверной процедуре ПриСозданииНаСервере), этап сбора массива можно упростить. Вместо цикла можно использовать метод ВыгрузитьКолонку.


// Пример для серверного контекста
ТаблицаТоваров = Объект.Товары.Выгрузить(); // Выгружаем ТЧ в ТаблицуЗначений
МассивНоменклатуры = ТаблицаТоваров.ВыгрузитьКолонку("Номенклатура");

// Далее выполняем запрос...

Используя этот подход, вы сможете эффективно выводить остатки для любого количества товаров, не перегружая базу данных лишними запросами.

← На главную