Проверяем, на что указывает ссылка: код для 1С 8.3

· 9 мин

Содержание

Чтобы определить тип ссылки в 1С, возьмите ТипЗнч(Значение) и проверьте результат: Справочники.ТипВсеСсылки().СодержитТип(ТипЗнч(Значение)) вернёт Истина для любой ссылки на справочник, Документы.ТипВсеСсылки().СодержитТип(...) — для любого документа. Если нужен конкретный объект, сравнивайте с Тип("СправочникСсылка.Номенклатура") или Тип("ДокументСсылка.РеализацияТоваровУслуг").

Способов несколько, и выбирают их по задаче. Сравнение с Тип(...) — когда вы знаете, какой объект ждёте. ТипВсеСсылки() — когда нужно понять вид объекта: справочник это или документ. Метаданные.НайтиПоТипу() — когда надо получить имя, синоним или полное имя объекта. В запросе — оператор ССЫЛКА и функция ТИПЗНАЧЕНИЯ.

Всё ниже относится к платформе 1С:Предприятие 8.3 и не зависит от конфигурации: код одинаково работает в БП 3.0, ЗУП 3.1, УТ 11 и самописных базах.

Зачем проверять тип: где встречаются ссылки неизвестного вида

Тип ссылки приходится выяснять там, где одно поле может хранить значения разных видов:

  • 🔀 реквизит составного типа — например, «Основание» или «Субконто», куда можно записать и контрагента, и документ;
  • 📑 поле «Регистратор» в регистрах — в нём лежит ссылка на один из документов-регистраторов;
  • 📦 параметр общей процедуры, которую вызывают из разных форм;
  • 🧾 значение из табличного документа, отчёта или обработки при расшифровке;
  • 🔁 данные из обмена и загрузки, где тип приходит строкой.

В каждом из этих случаев обращение к реквизиту без проверки типа рано или поздно падает с ошибкой. У справочника есть Наименование и Код, у документа — Номер и Дата. Перепутали — получили исключение.

Функция ТипЗнч и сравнение с Тип(): базовая проверка

ТипЗнч() возвращает значение типа Тип. Само по себе оно мало что говорит, его нужно с чем-то сравнить. Самое простое — с конкретным типом через функцию Тип():

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

Имя типа в Тип() состоит из двух частей: вид ссылки и имя объекта метаданных. Основные виды:

Объект конфигурацииИмя типа ссылки
СправочникСправочникСсылка.<Имя>
ДокументДокументСсылка.<Имя>
ПеречислениеПеречислениеСсылка.<Имя>
План видов характеристикПланВидовХарактеристикСсылка.<Имя>
План счетовПланСчетовСсылка.<Имя>
Бизнес-процессБизнесПроцессСсылка.<Имя>
ЗадачаЗадачаСсылка.<Имя>
План обменаПланОбменаСсылка.<Имя>

Имя объекта берётся из свойства «Имя» в конфигураторе, а не из синонима. Синоним «Реализация (акты, накладные, УПД)» в Тип() не подойдёт.

Если в имени опечатка или такого объекта нет в конфигурации, Тип() выбросит исключение Тип не определен (СправочникСсылка.Номенклатурка). Код, который должен работать в разных конфигурациях, оборачивайте в Попытка ... Исключение или проверяйте наличие объекта через Метаданные.Справочники.Найти("Номенклатура").

Для нескольких допустимых типов удобнее не городить цепочку ИЛИ, а собрать ОписаниеТипов:

Допустимые = Новый ОписаниеТипов("СправочникСсылка.Контрагенты, СправочникСсылка.ФизическиеЛица");
Если Допустимые.СодержитТип(ТипЗнч(Значение)) Тогда
	// работаем как с контрагентом или физлицом
КонецЕсли;

Справочник или документ: метод ТипВсеСсылки()

Когда неважно, какой именно справочник, а важно сам факт «это справочник», сравнение с конкретными типами не годится. Здесь работает метод менеджера коллекции ТипВсеСсылки(). Он возвращает ОписаниеТипов, в котором перечислены ссылочные типы всех объектов этого вида.

&НаСервереБезКонтекста
Функция ВидСсылки(Значение)

	ТипЗначения = ТипЗнч(Значение);

	Если Справочники.ТипВсеСсылки().СодержитТип(ТипЗначения) Тогда
		Возврат "Справочник";
	ИначеЕсли Документы.ТипВсеСсылки().СодержитТип(ТипЗначения) Тогда
		Возврат "Документ";
	ИначеЕсли Перечисления.ТипВсеСсылки().СодержитТип(ТипЗначения) Тогда
		Возврат "Перечисление";
	ИначеЕсли ПланыВидовХарактеристик.ТипВсеСсылки().СодержитТип(ТипЗначения) Тогда
		Возврат "ПланВидовХарактеристик";
	ИначеЕсли ПланыСчетов.ТипВсеСсылки().СодержитТип(ТипЗначения) Тогда
		Возврат "ПланСчетов";
	КонецЕсли;

	Возврат "";

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

Плюсы способа:

  • ✅ не нужно знать имена объектов, функция переживёт добавление новых справочников;
  • ✅ различает ссылку и объект: СправочникОбъект.Номенклатура в ТипВсеСсылки() не входит;
  • ✅ работает быстро, в базу за данными не ходит.

Тот же метод есть у менеджеров БизнесПроцессы, Задачи, ПланыОбмена, ПланыВидовРасчета. Если нужно ответить на вопрос «это вообще ссылка?», проверьте значение по всем нужным коллекциям.

В конфигурациях на БСП уже есть готовые функции общего модуля ОбщегоНазначения: ЭтоСсылка(Тип) проверяет, ссылочный ли тип, а ВидОбъектаПоСсылке(Ссылка) возвращает вид объекта строкой. В типовых БП, ЗУП и УТ лучше вызывать их, а не писать свою функцию.

Типы реквизита составного типа: ОписаниеТипов.Типы()

Иногда проверять нужно не значение, а сам реквизит: какие типы в него вообще можно записать. Описание типов реквизита хранится в метаданных, а метод Типы() отдаёт их массивом.

РеквизитМД = Метаданные.Документы.ПоступлениеТоваровУслуг.Реквизиты.Контрагент;

Для Каждого ТипРеквизита Из РеквизитМД.Тип.Типы() Цикл
	Если Справочники.ТипВсеСсылки().СодержитТип(ТипРеквизита) Тогда
		Сообщить("Справочник: " + Метаданные.НайтиПоТипу(ТипРеквизита).Имя);
	ИначеЕсли Документы.ТипВсеСсылки().СодержитТип(ТипРеквизита) Тогда
		Сообщить("Документ: " + Метаданные.НайтиПоТипу(ТипРеквизита).Имя);
	Иначе
		Сообщить("Другой тип: " + Строка(ТипРеквизита));
	КонецЕсли;
КонецЦикла;

Здесь имена документа и реквизита условные: подставьте свои из конфигуратора. Если реквизит не составной, массив будет из одного элемента.

Это пригодится, когда вы пишете обработку, которая сама строит отбор или форму выбора по реквизиту. Узнали, что в поле могут быть только справочники, — открываете форму выбора справочника. Есть документы — даёте пользователю сначала выбрать тип.

У ОписаниеТипов есть ещё метод ПривестиЗначение(). Он возвращает значение, если его тип входит в описание, или пустое значение первого типа, если не входит. Удобно для нормализации входных данных, но для вопроса «справочник или документ» он не нужен.

Имя, синоним и вид объекта через Метаданные.НайтиПоТипу

Метаданные.НайтиПоТипу(Тип) возвращает объект метаданных, которому принадлежит тип. Через него можно узнать всё: имя, синоним, полное имя, а заодно и вид.

ОбъектМД = Метаданные.НайтиПоТипу(ТипЗнч(Значение));

Если ОбъектМД = Неопределено Тогда
	// примитивный тип: Строка, Число, Дата, Булево, Неопределено и т. п.
ИначеЕсли Метаданные.Справочники.Содержит(ОбъектМД) Тогда
	Сообщить("Справочник «" + ОбъектМД.Синоним + "»");
ИначеЕсли Метаданные.Документы.Содержит(ОбъектМД) Тогда
	Сообщить("Документ «" + ОбъектМД.Синоним + "»");
КонецЕсли;

Второй вариант — разобрать полное имя. ОбъектМД.ПолноеИмя() вернёт строку вида Справочник.Номенклатура или Документ.СчетНаОплатуПокупателю. Первая часть до точки и есть вид:

ВидОбъекта = СтрРазделить(ОбъектМД.ПолноеИмя(), ".")[0]; // "Справочник", "Документ"...

Ловушка: НайтиПоТипу вернёт тот же объект метаданных и для ссылки, и для объекта. Тип СправочникОбъект.Номенклатура и тип СправочникСсылка.Номенклатура дадут один и тот же справочник. Если в параметр может прийти и ссылка, и объект (например, из ПередЗаписью), сначала отсеките объект через ТипВсеСсылки().

Сравнение способов:

СпособКогда выбиратьОграничения
ТипЗнч() = Тип("...")ждёте конкретный объектошибка, если объекта нет в конфигурации
ОписаниеТипов.СодержитТип()несколько допустимых объектовсписок надо поддерживать вручную
<Коллекция>.ТипВсеСсылки()нужен вид: справочник, документменеджеры недоступны в тонком клиенте
Метаданные.НайтиПоТипу()нужно имя, синоним, полное имяне отличает ссылку от объекта
ССЫЛКА и ТИПЗНАЧЕНИЯ в запросеотбор и группировка по типу на стороне СУБДтолько в тексте запроса

Проверка типа ссылки в запросе

Если нужно отобрать записи по виду значения составного поля, не перебирайте результат в цикле. Язык запросов умеет это сам.

Оператор ССЫЛКА проверяет, что значение — ссылка на указанную таблицу:

ВЫБРАТЬ
	Продажи.Регистратор КАК Документ,
	ТИПЗНАЧЕНИЯ(Продажи.Регистратор) КАК ТипДокумента
ИЗ
	РегистрНакопления.Продажи КАК Продажи
ГДЕ
	Продажи.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг

Функция ТИПЗНАЧЕНИЯ возвращает тип, а сравнивать его можно с ТИП(...):

ГДЕ
	ТИПЗНАЧЕНИЯ(Задание.Основание) = ТИП(Справочник.Контрагенты)

Обратите внимание на синтаксис: в запросе пишут ТИП(Справочник.Контрагенты) — имя таблицы без кавычек, а не Тип("СправочникСсылка.Контрагенты"), как во встроенном языке. Имя регистра и документа в примере условные.

Если дальше нужно читать реквизиты из составного поля, используйте ВЫРАЗИТЬ(Задание.Основание КАК Документ.РеализацияТоваровУслуг).СуммаДокумента. Без ВЫРАЗИТЬ платформа соединяет поле со всеми таблицами из составного типа, и запрос заметно тяжелеет.

Пустые, битые и неопределённые значения

Тип и наличие данных — разные вещи. Код, который проверил тип и сразу полез в реквизиты, ломается на трёх пограничных случаях.

1. Неопределено и Null. У незаполненного реквизита составного типа значение часто Неопределено. В результате запроса с левым соединением — Null. Их типы — Тип("Неопределено") и Тип("Null"), они не входят ни в один ТипВсеСсылки(), а НайтиПоТипу для них вернёт Неопределено. При обращении к реквизиту такого значения вы получите ошибку Значение не является значением объектного типа (Наименование).

2. Пустая ссылка. У Справочники.Номенклатура.ПустаяСсылка() тип вполне определённый — СправочникСсылка.Номенклатура. Проверка типа пройдёт, а реквизиты вернут пустые значения. Отсекайте такие значения через ЗначениеЗаполнено() или сравнение Значение.Пустая().

3. Битая ссылка. Тип правильный, ссылка не пустая, а объекта в базе нет — его удалили напрямую или не догрузили при обмене. В интерфейсе такая ссылка выглядит как <Объект не найден> (…). Проверка: Значение.ПолучитьОбъект() = Неопределено, но этот способ читает объект целиком. Дешевле запрос по таблице с отбором по ссылке или функция БСП ОбщегоНазначения.СсылкаСуществует().

Надёжный порядок проверок:

Если Не ЗначениеЗаполнено(Значение) Тогда
	Возврат; // Неопределено, Null или пустая ссылка
КонецЕсли;

Если Не Документы.ТипВсеСсылки().СодержитТип(ТипЗнч(Значение)) Тогда
	Возврат; // не документ
КонецЕсли;

// дальше можно читать Номер, Дату, Проведен

ЗначениеЗаполнено() возвращает Ложь для Неопределено, Null и пустой ссылки, поэтому одна строка закрывает сразу три случая. Битые ссылки она не ловит: для них проверка нужна отдельно.

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

Где выполнять проверку: тонкий, толстый и веб-клиент

Здесь чаще всего ошибаются при переносе кода из старых обработок в управляемые формы.

В тонком и веб-клиенте менеджеры объектов недоступны: в клиентском коде нет Справочники и Документы. Строка Справочники.ТипВсеСсылки() в процедуре с &НаКлиенте вызовет ошибку. Реквизиты ссылки через точку на клиенте тоже не прочитать: Основание.СуммаДокумента — это обращение к базе, а оно возможно только на сервере.

Что доступно на клиенте:

  • ✅ ТипЗнч() и Тип("ДокументСсылка.…") — сравнение с конкретным типом работает;
  • ✅ Новый ОписаниеТипов(...) и СодержитТип();
  • ✅ ЗначениеЗаполнено().

Всё остальное — вид объекта, синоним, реквизиты — выносите в серверную функцию. Лучше без контекста формы, чтобы не гонять её данные на сервер:

&НаКлиенте
Процедура ОснованиеПриИзменении(Элемент)
	ВидОснования = ВидСсылкиНаСервере(Объект.Основание);
КонецПроцедуры

&НаСервереБезКонтекста
Функция ВидСсылкиНаСервере(Значение)
	Если Справочники.ТипВсеСсылки().СодержитТип(ТипЗнч(Значение)) Тогда
		Возврат "Справочник";
	ИначеЕсли Документы.ТипВсеСсылки().СодержитТип(ТипЗнч(Значение)) Тогда
		Возврат "Документ";
	КонецЕсли;
	Возврат "";
КонецФункции

В толстом клиенте обычного приложения менеджеры объектов доступны прямо на клиенте, поэтому старые обработки обходятся без серверных вызовов. При переводе такой обработки на управляемые формы этот код придётся разделить.

Если проверка нужна в нескольких формах, положите функцию в общий модуль с флагами «Сервер» и «Вызов сервера». Тогда из клиента её можно вызвать одной строкой, а логика будет в одном месте.

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

Чем ТипЗнч отличается от Тип?
ТипЗнч(Значение) определяет тип уже существующего значения. Тип("СправочникСсылка.Номенклатура") создаёт тип по имени, чтобы было с чем сравнить. Обычно они работают в паре.
Как получить имя типа строкой?
Строка(ТипЗнч(Значение)) вернёт представление типа, обычно синоним объекта. Для технического имени используйте Метаданные.НайтиПоТипу(ТипЗнч(Значение)).ПолноеИмя().
Почему проверка через Метаданные срабатывает на объекте, а не только на ссылке?
НайтиПоТипу ищет объект метаданных, а он общий для ссылки, объекта, менеджера и выборки. Чтобы отличить ссылку, используйте ТипВсеСсылки().
Можно ли проверить тип ссылки без обращения к базе?
Да. ТипЗнч, ТипВсеСсылки и НайтиПоТипу работают с типом, а не с данными, и в базу не ходят. Обращение к базе нужно только для проверки, существует ли объект.
Как проверить, что значение — любая ссылка, а не число или строка?
В конфигурациях на БСП — ОбщегоНазначения.ЭтоСсылка(ТипЗнч(Значение)). Без БСП — проверьте тип по ТипВсеСсылки() всех нужных коллекций.