Как программно определить наличие у документа определенного реквизита в 1С 7.7 и 1С 8.x?

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

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

В этой статье мы подробно разберем, как элегантно, быстро и безопасно решить эту задачу. Мы детально проанализируем решения как для исторической платформы 1С:Предприятие 7.7 («клюшки»), так и для современной платформы 1С:Предприятие 8.x.

Решение для платформы 1С:Предприятие 7.7 («Клюшки») через объект Метаданные

В платформе 1С 7.7 структура метаданных доступна через предопределенный объект Метаданные. Для проверки наличия реквизита у конкретного вида документа разработчики часто используют свойство Выбран.

Рассмотрим важнейшую особенность работы движка 1С 7.7: если мы запрашиваем несуществующий реквизит у объекта метаданных, система не завершает работу аварийно! Вместо этого она возвращает пустой (неинициализированный) объект метаданных. Чтобы проверить, действительно ли реквизит существует в конфигурации, нам достаточно обратиться к свойству Выбран этого объекта. Если реквизит есть, свойство вернет 1, если его нет — вернет 0.

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

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


Функция глЕстьРеквизитШапки(ИмяРеквизита, ВидДокумента) Экспорт
    Если ПустоеЗначение(ИмяРеквизита) = 1 Тогда
        Возврат 0;
    ИначеЕсли Метаданные.ОбщийРеквизитДокумента(ИмяРеквизита).Выбран = 1 Тогда
        Возврат 1;
    ИначеЕсли ПустоеЗначение(ВидДокумента) = 1 Тогда
        Возврат 0;
    ИначеЕсли Метаданные.Документ(ВидДокумента).РеквизитШапки(ИмяРеквизита).Выбран = 1 Тогда
        Возврат 1;
    Иначе
        Возврат 0;
    КонецЕсли;
КонецФункции

Посмотрим, как работает этот алгоритм по шагам:

  1. Сначала мы проверяем, не передано ли пустое имя реквизита. Если передано — сразу возвращаем 0.
  2. Затем мы проверяем, не является ли искомый реквизит общим для всех документов с помощью обращения к Метаданные.ОбщийРеквизитДокумента(). Если свойство Выбран возвращает 1, мы сразу возвращаем 1 (истина), так как общий реквизит гарантированно присутствует в любом документе.
  3. Если реквизит не общий, мы проверяем, передан ли вид документа. Если вид документа пустой, поиск локального реквизита не имеет смысла, возвращаем 0.
  4. Наконец, мы обращаемся к локальной структуре конкретного документа через Метаданные.Документ(ВидДокумента).РеквизитШапки(ИмяРеквизита). Если свойство Выбран равно 1, реквизит найден.

Аналогично мы можем проверить наличие реквизита в многострочной (табличной) части документа. Для этого общие реквизиты проверять не нужно, так как в 1С 7.7 общие реквизиты могут принадлежать только шапке. Код функции выглядит следующим образом:


Функция глЕстьРеквизитМнЧ(ИмяРеквизита, ВидДокумента) Экспорт
    Перем Реквизит;
    Если (ПустоеЗначение(ВидДокумента) = 1) ИЛИ (ПустоеЗначение(ИмяРеквизита) = 1) Тогда
        Возврат 0;
    КонецЕсли;
    Реквизит = Метаданные.Документ(ВидДокумента).РеквизитТабличнойЧасти(ИмяРеквизита);
    Возврат Реквизит.Выбран;
КонецФункции

Альтернативный динамический подход в 1С 7.7: Попытка — Исключение

Что делать, если в коде мы оперируем конкретным объектом (ссылкой на документ), а не его строковым видом, и при этом хотим написать максимально универсальный код, не привязанный к метаданным? В таком случае мы можем использовать динамическое обращение к атрибутам объекта с перехватом возможных ошибок.

В платформе 1С 7.7 для этого применяется метод ПолучитьАтрибут(), обернутый в блок обработки исключений:


Функция ЕстьРеквизитУОбъекта(КонтекстОбъекта, ИмяРеквизита) Экспорт
    Попытка
        ВремЗначение = КонтекстОбъекта.ПолучитьАтрибут(ИмяРеквизита);
        Возврат 1; // Реквизит успешно прочитан, значит он существует
    Исключение
        Возврат 0; // Возникла ошибка чтения, реквизита нет
    КонецПопытки;
КонецФункции

Проанализируем плюсы и минусы данного подхода:

Решение для платформы 1С:Предприятие 8.x (Современные методы)

Переходя к платформе 1С 8.x, мы сталкиваемся с гораздо более строгой структурой метаданных. Реквизиты объектов базы данных здесь разделены на три независимые коллекции:

  1. Реквизиты (пользовательские реквизиты шапки);
  2. СтандартныеРеквизиты (предопределенные платформой реквизиты: Дата, Номер, Проведен, ПометкаУдаления и другие);
  3. ОбщиеРеквизиты (вынесенные на уровень всей конфигурации).

В отличие от версии 7.7, коллекции метаданных в 1С 8.x представляют собой полноценные индексированные коллекции. Это значит, что для поиска реквизита нам больше не нужно обходить метаданные в цикле — мы можем использовать быстрый метод Найти().

Рассмотрим пример функции, которая проверяет все три коллекции метаданных для поиска реквизита шапки документа в 1С 8.x:


Функция ЕстьРеквизитДокумента8(ИмяДокумента, ИмяРеквизита) Экспорт
    
    МетаданныеДокумента = Метаданные.Документы[ИмяДокумента];
    
    // 1. Проверяем стандартные реквизиты (Дата, Номер, Проведен и т.д.)
    Если МетаданныеДокумента.СтандартныеРеквизиты.Найти(ИмяРеквизита) <> Неопределено Тогда
        Возврат Истина;
    КонецЕсли;
    
    // 2. Проверяем обычные реквизиты шапки
    Если МетаданныеДокумента.Реквизиты.Найти(ИмяРеквизита) <> Неопределено Тогда
        Возврат Истина;
    КонецЕсли;
    
    // 3. Проверяем общие реквизиты конфигурации
    Если Метаданные.ОбщиеРеквизиты.Найти(ИмяРеквизита) <> Неопределено Тогда
        // Проверяем, входит ли наш документ в состав этого общего реквизита
        ОбщийРеквизит = Метаданные.ОбщиеРеквизиты[ИмяРеквизита];
        СоставДокумента = ОбщийРеквизит.Состав.Найти(МетаданныеДокумента);
        Если СоставДокумента <> Неопределено Тогда
            // Проверяем режим использования общего реквизита для нашего документа
            Если СоставДокумента.Использование = Метаданные.СвойстваОбъектов.ИспользованиеОбщегоРеквизита.Использовать Тогда
                Возврат Истина;
            КонецЕсли;
        КонецЕсли;
    КонецЕсли;
    
    Возврат Ложь;
КонецФункции

Золотой стандарт БСП: Функция ЕстьРеквизитИлиСвойствоОбъекта

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


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

Выясним причину, почему этот метод считается самым надежным и производительным в экосистеме 1С 8.x. Разработчики БСП применили оригинальный алгоритм, который проверяет наличие реквизита без прямого обращения к тяжелому дереву метаданных. Он основан на работе со структурами и платформенным методом ЗаполнитьЗначенияСвойств().

Давайте посмотрим на упрощенный аналог этого алгоритма, чтобы понять, как устроена данная магия «под капотом»:


Функция ЕстьРеквизитОбъектаЛокальный(Объект, ИмяРеквизита) Экспорт
    // Создаем уникальный маркер-идентификатор
    Маркер = Новый УникальныйИдентификатор;
    
    // Создаем проверочную структуру, где ключ - имя искомого реквизита, а значение - наш маркер
    ПроверочнаяСтруктура = Новый Структура(ИмяРеквизита, Маркер);
    
    // Пытаемся заполнить структуру свойствами проверяемого объекта
    ЗаполнитьЗначенияСвойств(ПроверочнаяСтруктура, Объект);
    
    // Если реквизит у объекта существовал, значение маркера перетерлось (даже на Неопределено или NULL)
    Возврат ПроверочнаяСтруктура[ИмяРеквизита] <> Маркер;
КонецФункции

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

Проверка динамических реквизитов управляемой формы

Иногда перед программистом встает задача проверить наличие реквизита не у физического документа в базе данных, а у открытой на экране управляемой формы (например, динамически добавленный программный реквизит формы или реквизит, созданный конфигуратором на уровне интерфейса).

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


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

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

Резюме и рекомендации

Подводя итог нашему анализу, сформулируем краткие рекомендации для разработчиков:

  1. Если вы работаете в 1С 7.7, используйте проверенную функцию проверки метаданных, обязательно включающую в себя анализ Метаданные.ОбщийРеквизитДокумента(), чтобы не пропустить глобальные реквизиты шапки. Для быстрой динамической проверки единичных объектов без привязки к типу метаданных используйте конструкцию Попытка ... Исключение с методом ПолучитьАтрибут().
  2. Если вы работаете в 1С 8.x, забудьте про ручной перебор коллекций метаданных. Используйте метод Найти() для коллекций метаданных, если у вас есть только имя типа документа. Если же у вас есть сам объект или ссылка на него — используйте стандартную функцию БСП ОбщегоНазначенияКлиентСервер.ЕстьРеквизитИлиСвойствоОбъекта(), которая является быстрой, безопасной и работает как на клиенте, так и на сервере.
← На главную