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