Как вывести остаток товара при выборе продукции в документе 1С?

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

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

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

Анализ типичных ошибок участников обсуждения

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

1. Ошибка «Колонка не принадлежит коллекции»

Эта ошибка возникает в строке кода:

ИтогПоНоменклатуре = РезультатЗапроса.Итог("Количество");

Причина сбоя заключается в несоответствии имени колонки, возвращаемой запросом, и имени, передаваемого методу Итог(). Если в тексте запроса вы пишете:

ПередачаПродукцииОстатки.КоличествоОстаток КАК КоличествоОстаток

То и в методе свертки или подсчета итогов таблицы значений необходимо использовать именно имя "КоличествоОстаток". Имена колонок в платформе 1С чувствительны к регистру и должны совпадать символ в символ.

2. Использование метода ПолучитьОбъект() для чтения реквизитов

В коде участников форума встречается следующая конструкция заполнения реквизитов строки табличной части:


СтрокаТабличнойЧасти.Фирма = СтрокаТабличнойЧасти.Продукция.ПолучитьОбъект().Фирма;

Это классический антипаттерн разработки 1С. Метод ПолучитьОбъект() считывает из базы данных абсолютно все данные элемента справочника (включая табличные части, если они есть) и инициализирует объект в памяти сервера. Вызов этого метода ради чтения одного-двух реквизитов создает колоссальную избыточную нагрузку на СУБД и сервер приложений, особенно если табличная часть документа содержит десятки и сотни строк. Читать реквизиты ссылочного типа нужно напрямую через точку (в простых случаях на клиенте, если это разрешено конфигурацией) либо получать одним запросом к базе данных.

3. Неверная передача параметров в функцию остатков

В одном из сообщений функция расчета остатков вызывалась следующим образом:

ПолучитьОстаток(СтрокаТабличнойЧасти.Количество);

Вместо ссылки на саму продукцию в функцию передавалось числовое значение колонки количества (которое на момент изменения товара чаще всего равно нулю или не заполнено). В результате серверный запрос искал остатки для пустой ссылки и закономерно возвращал нулевое значение.

4. Отборы в предложении ГДЕ виртуальной таблицы

Использование конструкции:


ИЗ РегистрНакопления.ПередачаПродукции.Остатки КАК ПередачаПродукцииОстатки
ГДЕ ПередачаПродукцииОстатки.Продукция = &Продукт

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

---

Пошаговое построение оптимального решения

Чтобы форма документа работала мгновенно, а сервер базы данных не испытывал излишних нагрузок, мы объединим получение реквизитов номенклатуры (Фирма, Тип продукции, Единица измерения) и получение её текущего остатка в **один серверный запрос**. Таким образом, при изменении продукции в строке будет происходить ровно один быстрый вызов сервера.

Шаг 1. Создание обработчика события на клиенте

Для начала откроем форму нашего документа (например, РеализацияПродукции) в конфигураторе, перейдем на вкладку «Форма», выделим колонку «Продукция» (или «Номенклатура») в табличной части и в панели свойств создадим событие ПриИзменении.

Код клиентской процедуры будет выглядеть следующим образом:


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

Шаг 2. Разработка серверной функции с оптимизированным запросом

Теперь создадим функцию ПолучитьДанныеПродукцииНаСервере с директивой компиляции &НаСервере. Функция должна принимать на вход ссылку на выбранный товар, выполнять пакетный запрос или левое соединение справочника с виртуальной таблицей остатков регистра накопления и возвращать структуру с результатами.


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

Разбор преимуществ предложенного подхода

Рассмотрим подробнее преимущества, которые мы получили благодаря оптимизации кода:

  1. Минимизация серверных вызовов. Вместо последовательного вызова функций получения остатка и отдельного получения реквизитов (что привело бы к многократным обращениям к серверу) мы выполняем ровно один вызов процедуры ПолучитьДанныеПродукцииНаСервере().
  2. Отказ от тяжелых объектов. Мы полностью исключили метод ПолучитьОбъект(). Запрос выбирает только необходимые поля из таблиц базы данных, экономя оперативную память сервера.
  3. Правильная фильтрация остатков. Отбор по конкретной продукции передан в качестве первого параметра виртуальной таблицы остатков: Остатки(, Продукция = &Продукция). Это обеспечивает моментальную выборку из СУБД благодаря задействованию внутренних индексов регистра накопления.
  4. Безопасность типов. Использование функции ЕСТЬNULL() гарантирует, что если по выбранной продукции отсутствуют движения в регистре остатков, система вернет числовое значение 0 вместо значения Null. Это предотвращает возникновение непредвиденных ошибок при дальнейших математических расчетах на форме.
  5. Универсальность заполнения. Использование встроенной функции ЗаполнитьЗначенияСвойств() позволяет автоматически сопоставить поля структуры (Фирма, ТипПродукции, ЕдиницаИзмерения, ОстатокВЗале) с именами реквизитов строки табличной части документа.

Разработанный по данной методике механизм вывода остатков будет работать надежно и быстро даже в сильно нагруженных информационных базах со множеством одновременно работающих пользователей.

← На главную