К статье
Практический материал

Разработка и Конфигурация · 11 мин

Как в 1С проверить, есть ли реквизит у документа

Как в 1С проверить наличие реквизита у документа: метаданные, НайтиПоИмени, проверка табличных частей и типовые ошибки с готовыми примерами кода.

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

В 1С:Предприятии 8 нет встроенной функции вида «ЕстьРеквизит», поэтому проверку выполняют через объект метаданных документа. Правильный подход зависит от того, известно ли имя документа на этапе разработки, нужно ли проверять реквизиты табличных частей, и где выполняется код — на сервере или на клиенте.

Ниже разобраны рабочие способы проверки: через коллекцию реквизитов метаданных, через тип значения, через попытку обращения, а также частые ошибки, которые приводят к ложным результатам.

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

Основной и рекомендуемый способ — обращение к коллекции реквизитов объекта метаданных. У каждого документа в дереве метаданных есть свойство Реквизиты, у которого метод Найти() ищет реквизит по имени и возвращает либо объект описания, либо Неопределено.

Функция ЕстьРеквизитДокумента(ИмяДокумента, ИмяРеквизита) Экспорт

МетаданныеДокумента = Метаданные.Документы.Найти(ИмяДокумента);

Если МетаданныеДокумента = Неопределено Тогда

Возврат Ложь; // самого документа нет в конфигурации

КонецЕсли;

Возврат МетаданныеДокумента.Реквизиты.Найти(ИмяРеквизита) <> Неопределено;

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

Пример вызова: ЕстьРеквизитДокумента("РеализацияТоваровУслуг", "ЗаказКлиента"). Если в текущей конфигурации у документа такой реквизит есть, функция вернёт Истина, иначе — Ложь.

Обратите внимание: проверка через метаданные отвечает на вопрос «описан ли реквизит в конфигурации», а не «заполнен ли он у конкретного документа». Это разные задачи, и путать их — распространённая ошибка.

Способ 2: получение метаданных от объекта документа

Если в коде уже есть ссылка или объект документа, не обязательно знать его имя заранее — метаданные можно получить от самого значения. Это удобно в универсальных обработчиках, куда документы разных видов приходят параметром.

Функция ЕстьРеквизитУОбъекта(ДокументСсылка, ИмяРеквизита) Экспорт

МетаданныеДокумента = ДокументСсылка.Метаданные();

Возврат МетаданныеДокумента.Реквизиты.Найти(ИмяРеквизита) <> Неопределено;

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

Метод Метаданные() доступен и у ссылки, и у объекта документа, и у данных формы. Вызов у ссылки не обращается к базе данных за самим документом — берётся только описание конфигурации, поэтому это быстрая операция.

Такой вариант особенно полезен в подписках на события и процедурах обмена, где тип документа заранее неизвестен: код одинаково проверяет реквизит у любого переданного документа.

Проверка реквизитов табличных частей

Реквизиты табличных частей не входят в коллекцию Реквизиты документа — их нужно искать отдельно. Сначала проверяется наличие самой табличной части, затем наличие реквизита внутри неё.

Функция ЕстьРеквизитТабличнойЧасти(ИмяДокумента, ИмяТЧ, ИмяРеквизита) Экспорт

МетаданныеДокумента = Метаданные.Документы.Найти(ИмяДокумента);

Если МетаданныеДокумента = Неопределено Тогда

Возврат Ложь;

КонецЕсли;

МетаданныеТЧ = МетаданныеДокумента.ТабличныеЧасти.Найти(ИмяТЧ);

Если МетаданныеТЧ = Неопределено Тогда

Возврат Ложь; // нет такой табличной части

КонецЕсли;

Возврат МетаданныеТЧ.Реквизиты.Найти(ИмяРеквизита) <> Неопределено;

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

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

Порядок действий
  1. 1
    Определите, где искать реквизит: в шапке документа или в табличной части
  2. 2
    Получите объект метаданных документа по имени или от ссылки через Метаданные()
  3. 3
    Вызовите Реквизиты.Найти(ИмяРеквизита) и сравните результат с Неопределено
  4. 4
    Для табличной части сначала найдите её в ТабличныеЧасти, затем ищите реквизит внутри
  5. 5
    Только после успешной проверки обращайтесь к реквизиту у объекта документа

Проверка стандартных реквизитов и реквизитов адресации

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

РеквизитНайден = (МетаданныеДокумента.Реквизиты.Найти(ИмяРеквизита) <> Неопределено)

Или (МетаданныеДокумента.СтандартныеРеквизиты.Найти(ИмяРеквизита) <> Неопределено);

Отдельный случай — реквизиты, которые видны в форме, но не являются реквизитами объекта: например, поля, вычисляемые в запросе динамического списка, или реквизиты формы, добавленные разработчиком. Проверка через метаданные их не покажет, потому что они существуют только в контексте конкретной формы.

Типичные ошибки
01
Искать стандартный реквизит (Номер, Дата) в коллекции Реквизиты — он находится в СтандартныеРеквизиты, поэтому проверка даёт ложный отрицательный результат
02
Путать наличие реквизита в конфигурации с его заполненностью у конкретного документа — это разные проверки
03
Искать реквизит табличной части в общей коллекции Реквизиты документа — его там нет
04
Выполнять проверку через Метаданные на клиенте в тонком клиенте — объект Метаданные недоступен вне сервера
05
Перебирать реквизиты циклом вместо метода Найти() — код длиннее и медленнее

Альтернативный подход: проверка через Попытка/Исключение

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

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

Допустимое применение — проверка реквизитов у значений, полученных через ЗначениеИзСтрокиВнутр или COM-соединение, где объект метаданных недоступен напрямую. Но и там лучше по возможности работать через метаданные соответствующей базы.

Сравнение вариантов

Метаданные + Найти()

Основной способ: быстро, надёжно, явно показывает причину отсутствия. Требует выполнения на сервере.

Попытка/Исключение

Работает без метаданных, но медленно, маскирует другие ошибки и затрудняет отладку. Только как запасной вариант.

Перебор коллекции циклом

Даёт тот же результат, что Найти(), но код длиннее. Оправдан, если нужно сравнение по своим правилам.

Ограничения: клиент, права и расширения

Объект Метаданные доступен только в серверном контексте: в серверных процедурах модулей, внешних соединениях, фоновых заданиях. В клиентских процедурах тонкого и веб-клиента прямой вызов Метаданные.Документы вызовет ошибку. Решение — вынести проверку в серверную функцию общего модуля с флагом «Сервер» и вызывать её с клиента.

Права доступа на проверку через метаданные не влияют: коллекция реквизитов видна независимо от того, есть ли у пользователя право просмотра конкретного реквизита. Но последующее чтение значения реквизита у объекта уже контролируется правами — учитывайте это в коде, который выполняется под ограниченными ролями.

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

Проверка заполненности реквизита у конкретного документа

После того как установлено, что реквизит существует, часто требуется вторая проверка — заполнен ли он у конкретного документа. Для этого используется функция ЗначениеЗаполнено() или сравнение с пустым значением типа реквизита.

Если ЕстьРеквизитУОбъекта(ДокументСсылка, "ЗаказКлиента") Тогда

Заказ = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(ДокументСсылка, "ЗаказКлиента");

Если ЗначениеЗаполнено(Заказ) Тогда

// реквизит есть и заполнен

КонецЕсли;

КонецЕсли;

Для чтения реквизита по ссылке без получения всего объекта в типовых конфигурациях используют функции из общего модуля ОбщегоНазначения — это быстрее, чем ПолучитьОбъект(), и не блокирует запись документа другими пользователями.

Контрольные точки

Как проверить результат

Функция проверки возвращает Истина для заведомо существующего реквизита
Норма

Возвращает Истина

Тревожный признак

Возвращает Ложь — проверьте точность имени и коллекцию (шапка/табличная часть)

Поведение при несуществующем имени документа
Норма

Возвращает Ложь без исключения

Тревожный признак

Возникает ошибка — добавьте проверку Документы.Найти(ИмяДокумента)

Работа в клиентском контексте
Норма

Проверка выполняется в серверной функции

Тревожный признак

Ошибка доступности объекта Метаданные на клиенте

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

Можно ли проверить реквизит без обращения к метаданным?

Технически — через Попытка/Исключение при обращении к свойству объекта, но это медленнее и маскирует другие ошибки. Для обычного кода на сервере метаданные доступны всегда, поэтому обходных путей не требуется.

Почему Найти() не находит реквизит «Номер» или «Дата»?

Это стандартные реквизиты, они хранятся в коллекции СтандартныеРеквизиты объекта метаданных, а не в Реквизиты. Проверяйте обе коллекции или учитывайте, что стандартный набор есть у любого документа по умолчанию.

Как проверить реквизит в тонком клиенте?

Напрямую — никак: объект Метаданные недоступен в клиентском контексте. Создайте функцию в общем модуле с признаком «Сервер» и вызывайте её из клиентского кода, передавая имя документа и имя реквизита.

Видит ли проверка реквизиты, добавленные расширением?

Да, если расширение подключено в текущем сеансе — его реквизиты присутствуют в коллекции метаданных наравне со штатными. Если расширение отключено, Найти() корректно вернёт Неопределено.

Чем отличается проверка существования реквизита от проверки его заполненности?

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

Есть ли готовая функция проверки в типовых конфигурациях?

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

Итог

Что важно запомнить

Наличие реквизита у документа проверяется через метаданные: Реквизиты.Найти(ИмяРеквизита) возвращает объект или Неопределено. Табличные части и стандартные реквизиты проверяются в своих коллекциях, проверка выполняется только на сервере, а обращение к значению реквизита делается только после успешной проверки его существования.