Ссылка есть, реквизита нет: чтение данных справочника из клиентского кода 1С 8.3

· 9 мин

Содержание

На клиенте 1С 8.3 значение справочника получают тремя способами. Предопределённый элемент берут функцией ПредопределенноеЗначение("Справочник.Валюты.Рубль") прямо в клиентском коде. Реквизиты обычного элемента читают через серверную функцию с директивой &НаСервереБезКонтекста или через общий модуль с флагом «Вызов сервера». Если данные уже выведены на форму, их берут из реквизитов формы или из Элементы.Список.ТекущиеДанные вообще без обращения к серверу.

Типичная ситуация: в обработчике ПриИзменении поля «Контрагент» нужно проверить, юрлицо это или физлицо, и показать или скрыть поле КПП. Разработчик пишет Объект.Контрагент.ЮридическоеФизическоеЛицо и получает ошибку в тонком клиенте. Ссылка на клиенте есть, а доступа к её полям нет. Разберём, как получить значение справочника на клиенте правильно и без лишних серверных вызовов.

Что доступно на клиенте: ссылка, представление и больше ничего

В управляемом приложении клиент и сервер — разные процессы, часто на разных компьютерах. Клиент не обращается к базе данных напрямую. Любые данные справочника попадают на клиент только как результат серверного вызова или вместе с данными формы.

Ссылка на элемент справочника (СправочникСсылка.Контрагенты) передаётся на клиент свободно. Но это лишь идентификатор плюс тип. Что можно сделать со ссылкой в клиентском коде:

  • ✅ сравнить с другой ссылкой: Если Объект.Валюта = ВалютаРубль Тогда;
  • ✅ проверить на пустоту: Объект.Контрагент.Пустая() или сравнением с пустой ссылкой;
  • ✅ проверить тип: ТипЗнч(Значение) = Тип("СправочникСсылка.Контрагенты");
  • ✅ получить представление для вывода: Строка(Ссылка) — если ссылка пришла с формы, представление уже есть;
  • ❌ обратиться к реквизиту через точку: Ссылка.Наименование, Ссылка.ИНН, Ссылка.Родитель;
  • ❌ вызвать ПолучитьОбъект(), методы менеджера Справочники.Контрагенты.НайтиПоКоду() или выполнить запрос.

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

Что нужно получитьГде взять на клиентеСерверный вызов
Предопределённый элементПредопределенноеЗначение()нет (кешируется)
Пустая ссылкаПредопределенноеЗначение("Справочник.X.ПустаяСсылка")нет
Реквизит элемента на форме объектареквизит формы, заполненный на серверенет, данные пришли с формой
Поле текущей строки спискаЭлементы.Список.ТекущиеДанныенет
Реквизит произвольной ссылкифункция &НаСервереБезКонтекстада, один
Константа-настройка, которая не меняетсямодуль с повторным использованиемодин раз за сеанс

Предопределённые элементы и пустые ссылки без обращения к серверу

Предопределённые элементы (валюта «Рубль», вид контактной информации, статья по умолчанию) на клиенте получают глобальной функцией ПредопределенноеЗначение. Ей передают полное имя строкой:

&НаКлиенте
Процедура ВалютаПриИзменении(Элемент)

    ВалютаРегл = ПредопределенноеЗначение("Справочник.Валюты.Рубль");
    Элементы.Курс.Доступность = (Объект.Валюта <> ВалютаРегл);

КонецПроцедуры

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

Тем же способом получают пустую ссылку нужного типа:

ПустойКонтрагент = ПредопределенноеЗначение("Справочник.Контрагенты.ПустаяСсылка");

Если предопределённого элемента с таким именем нет в конфигурации, ПредопределенноеЗначение вызывает исключение. Код упадёт у пользователя, а не при проверке модуля: имя записано строкой, и синтаксический контроль его не проверяет.

В типовых конфигурациях на БСП для этого есть безопасная обёртка ОбщегоНазначенияКлиентСервер.ПредопределенныйЭлемент("Справочник.Валюты.Рубль"). Она возвращает Неопределено, если элемента нет. Это полезно в расширениях и общем коде, который работает в нескольких конфигурациях.

Не путайте имя предопределённого элемента с наименованием. Имя видно в конфигураторе в свойствах элемента и пишется без пробелов. Наименование («Российский рубль») пользователь может переименовать, имя — нет.

Получение значения справочника через серверный вызов

Для обычного элемента справочника реквизит читают на сервере и возвращают на клиент примитивное значение или структуру. Главное правило — директива &НаСервереБезКонтекста, а не &НаСервере.

Почему без контекста

При вызове процедуры &НаСервере платформа отправляет на сервер данные формы и возвращает их обратно. На форме документа с табличной частью в 500 строк это сотни килобайт трафика в обе стороны. Процедура &НаСервереБезКонтекста получает только параметры. Внутри неё нет доступа к Объект, Элементы и реквизитам формы, зато вызов в разы легче.

Пример: один реквизит

&НаКлиенте
Процедура КонтрагентПриИзменении(Элемент)

    ЭтоЮрЛицо = КонтрагентЮрЛицо(Объект.Контрагент);
    Элементы.КПП.Видимость = ЭтоЮрЛицо;

КонецПроцедуры

&НаСервереБезКонтекста
Функция КонтрагентЮрЛицо(Контрагент)

    Если Не ЗначениеЗаполнено(Контрагент) Тогда
        Возврат Ложь;
    КонецЕсли;

    Возврат Контрагент.ЮридическоеФизическоеЛицо
        = Перечисления.ЮридическоеФизическоеЛицо.ЮридическоеЛицо;

КонецФункции

Обращение Контрагент.ЮридическоеФизическоеЛицо на сервере допустимо, но платформа при этом читает весь элемент справочника целиком, со всеми табличными частями. Для одного-двух полей лучше запрос или функция БСП.

Функции БСП для чтения реквизитов

В конфигурациях на БСП (БП 3.0, ЗУП 3.1, УТ 11 и другие) в серверном модуле ОбщегоНазначения есть готовые функции:

  • 📌 ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Ссылка, "ИНН") — одно значение;
  • 📌 ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Ссылка, "Код, Наименование, ИНН") — структура с ключами по именам реквизитов;
  • 📌 ОбщегоНазначения.ЗначениеРеквизитаОбъектов(МассивСсылок, "ИНН") — соответствие «ссылка → значение» для нескольких элементов.

Внутри они строят запрос только по нужным полям. Модуль ОбщегоНазначения серверный, напрямую с клиента его не вызвать. Оборачиваем в свою функцию без контекста:

&НаСервереБезКонтекста
Функция ДанныеКонтрагента(Контрагент)

    Возврат ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Контрагент,
        "Наименование, ИНН, КПП, ЮридическоеФизическоеЛицо");

КонецФункции

На клиенте результат — обычная Структура: Данные.ИНН, Данные.КПП.

Общий модуль «Вызов сервера»

Если одно и то же значение справочника нужно в десятке форм, вынесите функцию в общий модуль. В конфигураторе создайте общий модуль, установите флаги «Сервер» и «Вызов сервера», снимите «Клиент (управляемое приложение)». Экспортные функции такого модуля вызываются из клиентского кода как МойМодульВызовСервера.ДанныеКонтрагента(Ссылка).

Каждая экспортная функция модуля с флагом «Вызов сервера» доступна с клиента любому пользователю. Проверяйте в ней параметры и не возвращайте лишнего: такой модуль — открытая дверь к данным.

Реквизиты и дополнительные поля без лишних вызовов

Самый быстрый серверный вызов — тот, которого нет. Если значение справочника нужно постоянно, пока открыта форма, получите его один раз вместе с формой.

Реквизит формы, заполненный при создании

Добавьте на форму реквизит, например ВалютаКонтрагента, и заполните его на сервере в ПриСозданииНаСервере и при смене контрагента. Клиентский код читает реквизит формы как обычную переменную:

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    ОбновитьДанныеКонтрагента();
КонецПроцедуры

&НаСервере
Процедура ОбновитьДанныеКонтрагента()
    Если ЗначениеЗаполнено(Объект.Контрагент) Тогда
        ИННКонтрагента = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Объект.Контрагент, "ИНН");
    Иначе
        ИННКонтрагента = "";
    КонецЕсли;
КонецПроцедуры

Здесь &НаСервере оправдан: процедуру вызывают из серверного обработчика, где контекст формы уже есть.

Текущие данные динамического списка

В форме списка поля текущей строки доступны на клиенте через Элементы.Список.ТекущиеДанные. Туда попадают колонки, выбранные в запросе динамического списка, даже если они не выведены на форму:

&НаКлиенте
Процедура СписокПриАктивизацииСтроки(Элемент)

    ТекущиеДанные = Элементы.Список.ТекущиеДанные;
    Если ТекущиеДанные = Неопределено Тогда
        Возврат;
    КонецЕсли;

    Элементы.КомандаОтправить.Доступность = ЗначениеЗаполнено(ТекущиеДанные.ИНН);

КонецПроцедуры

Чтобы поле появилось в ТекущиеДанные, его нужно добавить в запрос динамического списка (произвольный запрос) или выбрать в списке полей основной таблицы. Поле без колонки на форме иногда не передаётся на клиент: в этом случае добавьте его в свойство динамического списка «Использовать всегда».

ТекущиеДанные может вернуть Неопределено — например, когда список пуст или строка не выбрана. Проверка в начале обработчика обязательна.

Скорость: кеширование и пакетное чтение

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

ПриёмКогда применятьРиск
&НаСервереБезКонтекста вместо &НаСерверевсегда, если не нужна форманет
Один вызов вместо несколькихнужно 3-5 полей разомнет
Пакетное чтение по массиву ссылокобработка строк табличной частикрупный массив = долгий вызов
Модуль с повторным использованиемнастройки, которые не меняются в сеансеустаревшие данные
Реквизит формызначение нужно всё время работы формынадо обновлять при изменении

Не вызывайте сервер в цикле

Типичная ошибка — проверка каждой строки табличной части отдельным вызовом:

// Плохо: 300 строк = 300 обращений к серверу
Для Каждого Строка Из Объект.Товары Цикл
    Если ЭтоУслуга(Строка.Номенклатура) Тогда
        Строка.Количество = 1;
    КонецЕсли;
КонецЦикла;

Соберите ссылки в массив и получите всё за один вызов:

&НаКлиенте
Процедура ПроставитьКоличествоУслуг()

    Ссылки = Новый Массив;
    Для Каждого Строка Из Объект.Товары Цикл
        Ссылки.Добавить(Строка.Номенклатура);
    КонецЦикла;

    Услуги = ПризнакиУслуг(Ссылки);

    Для Каждого Строка Из Объект.Товары Цикл
        Если Услуги.Получить(Строка.Номенклатура) = Истина Тогда
            Строка.Количество = 1;
        КонецЕсли;
    КонецЦикла;

КонецПроцедуры

&НаСервереБезКонтекста
Функция ПризнакиУслуг(Ссылки)

    Запрос = Новый Запрос;
    Запрос.Текст =
    "ВЫБРАТЬ РАЗРЕШЕННЫЕ
    |    Номенклатура.Ссылка КАК Ссылка,
    |    Номенклатура.Услуга КАК Услуга
    |ИЗ
    |    Справочник.Номенклатура КАК Номенклатура
    |ГДЕ
    |    Номенклатура.Ссылка В (&Ссылки)";
    Запрос.УстановитьПараметр("Ссылки", Ссылки);

    Результат = Новый Соответствие;
    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        Результат.Вставить(Выборка.Ссылка, Выборка.Услуга);
    КонецЦикла;

    Возврат Результат;

КонецФункции

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

Кеширование на время сеанса

Для значений, которые за сеанс не меняются (основная организация, валюта учёта, настройки пользователя), создайте серверный общий модуль с флагом «Вызов сервера» и свойством «Повторное использование возвращаемых значений» = «На время сеанса». Первый вызов функции идёт на сервер, повторные с теми же параметрами отдаются из кеша.

Возвращайте из такого модуля неизменяемые значения: ФиксированнаяСтруктура, ФиксированныйМассив, примитивы, ссылки. Обычную структуру вызывающий код может случайно изменить, и испорченное значение получат все следующие вызовы.

Не кешируйте то, что пользователь может изменить в течение дня: цены, остатки, статусы. Кеш сбрасывается только с окончанием сеанса или через ОбновитьПовторноИспользуемыеЗначения(), а эта функция очищает весь кеш, а не одно значение.

Ошибки при чтении справочника с клиента и как их обработать

Диагностика: выберите симптом

Как обрабатывать исключения

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

&НаКлиенте
Процедура ЗаполнитьПоКонтрагенту()

    Попытка
        Данные = ДанныеКонтрагента(Объект.Контрагент);
    Исключение
        ИнфоОшибки = ИнформацияОбОшибке();
        ПоказатьПредупреждение(, КраткоеПредставлениеОшибки(ИнфоОшибки));
        Возврат;
    КонецПопытки;

    Объект.ИНН = Данные.ИНН;

КонецПроцедуры

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

Проверку прав делайте на сервере. В запросах используйте ВЫБРАТЬ РАЗРЕШЕННЫЕ: тогда при ограничении доступа на уровне записей запрос вернёт только доступные строки вместо ошибки. Функция ПравоДоступа("Чтение", Метаданные.Справочники.Контрагенты) подскажет, есть ли у пользователя право на справочник в целом.

Проверка клиентского кода, который читает справочник
Выполнено: 0 / 7

Частые вопросы

Можно ли на клиенте получить ссылку на элемент по коду или наименованию?
Нет, поиск выполняется только на сервере. Передайте код в функцию &НаСервереБезКонтекста, внутри вызовите Справочники.Валюты.НайтиПоКоду("643") и верните ссылку на клиент.
Работает ли это в веб-клиенте и мобильном клиенте?
Да. Правила одинаковы для тонкого, веб- и мобильного клиента: данные справочника клиент получает только через сервер или вместе с формой. В веб-клиенте лишний серверный вызов обходится особенно дорого.
Чем `&НаСервереБезКонтекста` отличается от общего модуля «Вызов сервера»?
По скорости ничем: оба передают только параметры. Функция формы видна только в этой форме, а общий модуль доступен из любого клиентского кода. Повторяющуюся логику выносите в модуль.
Почему в обычном приложении обращение через точку работало, а в управляемом нет?
В толстом клиенте обычного приложения клиентский код сам читал базу данных. В управляемом приложении клиент к базе не подключается, поэтому поля ссылки ему недоступны.
Как получить значение справочника в расширении конфигурации?
Так же: в модуле формы расширения добавьте функцию &НаСервереБезКонтекста или общий модуль расширения с флагом «Вызов сервера». Предопределённые элементы основной конфигурации доступны через ПредопределенноеЗначение.