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