При разработке универсальных механизмов в 1С (таких как групповое изменение реквизитов, поиск ссылок или динамическое построение запросов) мы часто сталкиваемся с необходимостью анализировать метаданные конфигурации. Одна из самых нетривиальных задач в этой области — определить, является ли конкретный реквизит объекта (справочника, документа или табличной части) характеристикой, привязанной к определенному Плану видов характеристик (ПВХ).
Сложность заключается в том, что если в конфигураторе реквизиту явно задан тип Характеристика.ИмяПВХ, то при программном обращении к метаданным система возвращает обычный объект ОписаниеТипов, содержащий массив всех разрешенных в этом ПВХ типов. Напрямую узнать имя самого ПВХ-контейнера через свойство Реквизит.Тип не удается. В этой статье мы подробно разберем, почему так происходит, и рассмотрим три надежных способа решения этой проблемы, а также исправим распространенное заблуждение по работе с характеристиками в запросах.
Когда мы настраиваем реквизит в конфигураторе и выбираем тип из ветки «Характеристика», платформа 1С на самом деле выполняет автоматическое развертывание этого типа. В режиме «Предприятие» свойство метаданных реквизита Тип хранит составной тип данных, включающий все типы, которые отмечены в самом Плане видов характеристик. Для платформы это выглядит как обычное ОписаниеТипов, и она «забывает», что этот тип был унаследован от конкретного ПВХ.
Попытка вызвать метод Типы() у описания типов реквизита вернет массив конкретных типов (например, Число, Строка, СправочникСсылка.Номенклатура), но не ссылку на сам объект метаданных ПВХ. Давайте разберем по шагам, какими способами мы можем обойти это ограничение и получить точную информацию о связи реквизита с Планом видов характеристик.
В реальных конфигурациях тип «Характеристика» практически никогда не используется изолированно. Характеристика всегда логически связана с другим реквизитом, который определяет конкретное свойство или вид субконто (то есть хранит ссылку на элемент ПВХ). Например, в табличной части дополнительных реквизитов всегда есть реквизит Свойство (ссылка на ПВХ) и реквизит Значение (тип Характеристика).
Связь между этими реквизитами жестко настраивается в конфигураторе с помощью свойства Связь по типу. Мы можем использовать эту связь для точного определения ПВХ. Давайте проанализируем, как это сделать программно:
Рассмотрим алгоритм решения:
СвязьПоТипу.Реализуем этот алгоритм на практике с помощью следующего кода:
Функция ОпределитьПВХПоСвязиТипов(ОбъектМетаданныхКонфигурации, ИмяТабличнойЧасти = "", ИмяРеквизита) Экспорт
// Получаем метаданные нужного реквизита
Если ЗначениеЗаполнено(ИмяТабличнойЧасти) Тогда
РеквизитМетаданные = ОбъектМетаданныхКонфигурации.ТабличныеЧасти[ИмяТабличнойЧасти].Реквизиты[ИмяРеквизита];
Иначе
РеквизитМетаданные = ОбъектМетаданныхКонфигурации.Реквизиты[ИмяРеквизита];
КонецЕсли;
// Проверяем свойство "Связь по типу"
Связь = РеквизитМетаданные.СвязьПоТипу;
Если Связь = Неопределено Или Связь.ЭлементыСвязи.Количество() = 0 Тогда
Возврат Неопределено; // Связи нет, это не характеристика или связь не настроена
КонецЕсли;
// Получаем имя связанного реквизита (владельца типа)
ИмяСвязанногоПоля = Связь.ЭлементыСвязи[0].ПутьКДанным;
// Из пути к данным получаем чистое имя реквизита
ЧастиПути = СтрРазделить(ИмяСвязанногоПоля, ".");
ЧистоеИмяСвязанногоРеквизита = ЧастиПути[ЧастиПути.ВГраница()];
// Находим метаданные связанного реквизита
Если ЗначениеЗаполнено(ИмяТабличнойЧасти) Тогда
СвязанныйРеквизитМетаданные = ОбъектМетаданныхКонфигурации.ТабличныеЧасти[ИмяТабличнойЧасти].Реквизиты[ЧистоеИмяСвязанногоРеквизита];
Иначе
СвязанныйРеквизитМетаданные = ОбъектМетаданныхКонфигурации.Реквизиты[ЧистоеИмяСвязанногоРеквизита];
КонецЕсли;
// Проверяем, указывает ли тип связанного реквизита на ПВХ
Для Каждого ТекущийТип Из СвязанныйРеквизитМетаданные.Тип.Типы() Цикл
МетаданныеТипа = Метаданные.НайтиПоТипу(ТекущийТип);
Если МетаданныеТипа <> Неопределено И Метаданные.ПланыВидовХарактеристик.Содержит(МетаданныеТипа) Тогда
// Успех! Мы нашли План видов характеристик, к которому относится наш реквизит
Возврат МетаданныеТипа;
КонецЕсли;
КонецЦикла;
Возврат Неопределено;
КонецФункции
Этот способ является наиболее надежным, поскольку он исключает ложные срабатывания. Он опирается на строгую логическую связь, заложенную разработчиком при проектировании конфигурации.
Если по какой-то причине свойство «Связь по типу» не заполнено (например, реквизиту просто вручную назначили составной тип, совпадающий с типами ПВХ), мы можем пойти путем сравнения описаний типов. Мы сравним ОписаниеТипов нашего реквизита и ОписаниеТипов самого Плана видов характеристик.
Вместо медленного перебора массивов типов в циклах, мы применим эффективную возможность конструктора ОписаниеТипов — вычитание типов. Если при вычитании типов ПВХ из типов реквизита у нас остается пустой набор типов, это означает, что типы нашего реквизита полностью входят в состав ПВХ (являются его подмножеством).
Давайте посмотрим на пример реализации такой проверки:
Функция ПроверитьВхождениеТипаВПВХ(ТипРеквизита, ИмяПВХ) Экспорт
// Получаем описание типов из самого Плана видов характеристик
ТипПВХ = Метаданные.ПланыВидовХарактеристик[ИмяПВХ].Тип;
// Создаем новое описание типов, исключая из проверяемого типа все типы, разрешенные в ПВХ
ОстатокТипов = Новый ОписаниеТипов(ТипРеквизита, , ТипПВХ);
// Если в остатке не осталось типов, значит наш реквизит полностью совместим со структурой ПВХ
Если ОстатокТипов.Типы().Количество() = 0 Тогда
Возврат Истина;
КонецЕсли;
Возврат Ложь;
КонецФункции
Важное примечание: Этот метод дает высокую степень вероятности, но не стопроцентную гарантию. Если в конфигурации есть два разных ПВХ с абсолютно одинаковым набором типов, этот алгоритм может сопоставить реквизит не с тем ПВХ. Тем не менее, для большинства практических задач этого решения более чем достаточно.
Начиная с версии платформы 8.2.11, разработчики 1С получили мощный встроенный инструмент — коллекцию Характеристики у прикладных объектов метаданных (справочников, документов и т.д.). Этот механизм изначально предназначен для интеграции с Системой компоновки данных (СКД), но мы можем использовать его в своих целях.
Если мы разрабатываем универсальный поиск или обработку, нам не нужно гадать и перебирать реквизиты вручную. Мы можем напрямую спросить у метаданных объекта, какие характеристики для него зарегистрированы. Проанализируем пример кода:
Процедура ВывестиХарактеристикиСправочника(ИмяСправочника)
СправочникМетаданные = Метаданные.Справочники[ИмяСправочника];
ОписанияХарактеристик = СправочникМетаданные.Характеристики;
Для Каждого Описание Из ОписанияХарактеристик Цикл
СтрокаСообщения = "Найдена характеристика для объекта " + ИмяСправочника + ":";
СтрокаСообщения = СтрокаСообщения + " ПВХ-источник: " + Описание.Источник.ПолноеИмя();
СтрокаСообщения = СтрокаСообщения + "; Таблица хранения: " + Описание.ТаблицаВыбора.ПолноеИмя();
СтрокаСообщения = СтрокаСообщения + "; Поле ключа: " + Описание.ВидХарактеристики;
СтрокаСообщения = СтрокаСообщения + "; Поле значения: " + Описание.ЗначениеХарактеристики;
Сообщить(СтрокаСообщения);
КонецЦикла;
КонецПроцедуры
Этот подход позволяет автоматически определять, в каких таблицах хранятся значения характеристик для конкретного справочника или документа (например, в табличной части или в регистре сведений), какой ПВХ за них отвечает и по каким полям делать соединение. Это самый передовой и архитектурно правильный способ написания универсального кода.
В процессе обсуждения подобных задач на форумах программисты часто совершают серьезную техническую ошибку при динамическом формировании текста запроса. Они считают, что если реквизит имеет тип характеристики ПВХ, то в запросе к нему нужно обращаться через специальное свойство, дописывая к имени поля суффикс .Value (или .Значение), например:
// ОШИБОЧНЫЙ ВАРИАНТ ФОРМИРОВАНИЯ ЗАПРОСА ДЛЯ 1С
ТекстЗапроса = "ВЫБРАТЬ Таблица.Ссылка ИЗ Документ.РасходныйОрдер КАК Таблица ГДЕ Таблица.ДополнительныйРеквизит.Value = &Значение";
Выясним причину этого заблуждения и разберем, как обстоят дела на самом деле:
Запрос) суффиксов .Value или .Значение не существует. Платформа 1С самостоятельно и полностью прозрачно для разработчика преобразует обращения к составным типам на уровне СУБД. Запрос должен выглядеть стандартно, без каких-либо дописок.
ТекстЗапроса = "ВЫБРАТЬ Таблица.Ссылка ИЗ Документ.РасходныйОрдер КАК Таблица ГДЕ Таблица.ДополнительныйРеквизит = &Значение";
Откуда же берется суффикс Value? Эта конструкция используется исключительно при работе с внешними интерфейсами платформы, например, при обращении к базе данных через протокол 1С:OData (REST API). В OData составные типы действительно расширяются свойствами вида Реквизит_Type и Реквизит_Value. Если вы пишете код внутри самой платформы 1С, забудьте про эти суффиксы — они гарантированно приведут к синтаксической ошибке выполнения запроса.
Мы подробно рассмотрели особенности работы с типами Планов видов характеристик в метаданных 1С. Для простых и надежных проверок всегда используйте анализ свойства СвязьПоТипу. Для создания гибких и универсальных отчетов или обработок используйте коллекцию Характеристики. Эти методы позволят вашему коду оставаться стабильным и не зависеть от изменений в структуре конфигурации.