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