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