Родитель элемента в 1С 8.3: свойство Родитель, запросы, дерево значений и формы

· 9 мин

Содержание

Родителя элемента иерархического справочника в 1С возвращает свойство Родитель: Ссылка.Родитель на сервере или поле Родитель в запросе. У строки дерева значений это тоже Родитель, а у строки дерева на управляемой форме — метод ПолучитьРодителя(). Если элемент лежит на верхнем уровне, вместо родителя вы получите пустую ссылку или Неопределено.

Дальше всё зависит от того, где вы ищете: в объекте базы, в коллекции в памяти, в данных формы или в метаданных. Для каждого случая у платформы 1С:Предприятие 8.3 свой способ, и путать их — главная причина ошибок.

Где в 1С вообще бывает «родитель»

Слово одно, а объектов несколько. Перед тем как писать код, определите, с чем работаете.

ГдеКак получить родителяЧто вернётся на верхнем уровне
Иерархический справочникСсылка.РодительПустая ссылка этого справочника
План счетовСчет.РодительПустая ссылка плана счетов
Иерархический план видов характеристикВид.РодительПустая ссылка
Строка ДеревоЗначенийСтрока.РодительНеопределено
Строка дерева на формеСтрока.ПолучитьРодителя()Неопределено
Элемент формыЭлементы.Поле.РодительСама форма
Объект метаданныхОбъектМетаданных.Родитель()Объект Конфигурация

Отдельно стоит владелец. У подчинённого справочника (например, «Договоры контрагентов» подчинены «Контрагентам») связь с верхним объектом идёт через свойство Владелец, а не Родитель. Это разные механизмы:

  • 📁 Родитель — группа или элемент того же справочника, внутри иерархии;
  • 🔗 Владелец — элемент другого справочника, к которому привязан текущий.

У справочника может быть и то и другое сразу. Договор лежит в группе «Архив» (родитель) и принадлежит контрагенту «Ромашка» (владелец).

Родитель элемента в иерархическом справочнике

Прямое обращение через ссылку

На сервере достаточно точки:

Группа = Номенклатура.Родитель;
Если ЗначениеЗаполнено(Группа) Тогда
    Сообщить("Группа: " + Группа.Наименование);
Иначе
    Сообщить("Элемент лежит в корне справочника");
КонецЕсли;

Проверяйте через ЗначениеЗаполнено(), а не через сравнение с Неопределено. Пустой родитель — это пустая ссылка нужного типа, и Номенклатура.Родитель = Неопределено даст Ложь.

Если нужна именно пустая ссылка для сравнения или отбора, берите её у менеджера: Справочники.Номенклатура.ПустаяСсылка().

Корневая группа: подъём по цепочке

Частая задача — найти самую верхнюю группу. Например, отчёт группирует товары по разделам первого уровня, а элемент лежит на четвёртом.

Функция КорневаяГруппа(Ссылка) Экспорт
    Текущий = Ссылка;
    Пока ЗначениеЗаполнено(Текущий.Родитель) Цикл
        Текущий = Текущий.Родитель;
    КонецЦикла;
    Возврат Текущий;
КонецФункции

Если элемент сам в корне, функция вернёт его же. Если нужен не корень, а весь путь, собирайте родителей в массив:

Функция ЦепочкаРодителей(Ссылка) Экспорт
    Результат = Новый Массив;
    Текущий = Ссылка.Родитель;
    Пока ЗначениеЗаполнено(Текущий) Цикл
        Результат.Вставить(0, Текущий);
        Текущий = Текущий.Родитель;
    КонецЦикла;
    Возврат Результат; // от корня к ближайшей группе
КонецФункции

Каждое .Родитель у ссылки — это чтение из базы (с кэшем на время вызова). Для одного элемента это незаметно. Для тысячи строк в цикле поднимайте всю цепочку одним запросом — способ ниже.

Проверка «входит ли элемент в группу»

Чтобы не ходить по цепочке руками, у ссылки справочника есть метод ПринадлежитЭлементу():

Если Номенклатура.ПринадлежитЭлементу(ГруппаУслуги) Тогда
    // элемент лежит в группе «Услуги» на любом уровне вложенности
КонецЕсли;

Метод проверяет всю иерархию вверх, а не только прямого родителя.

Обход потомков от родителя

Обратная задача — от группы получить всё содержимое. Менеджер справочника умеет выбирать иерархически:

Выборка = Справочники.Номенклатура.ВыбратьИерархически(ГруппаУслуги);
Пока Выборка.Следующий() Цикл
    Отступ = "";
    Для Сч = 1 По Выборка.Уровень() Цикл
        Отступ = Отступ + "  ";
    КонецЦикла;
    Сообщить(Отступ + Выборка.Наименование);
КонецЦикла;

Выборка.Уровень() возвращает глубину строки, его удобно использовать для отступов. Для массовой обработки всё равно лучше запрос.

План счетов

У плана счетов иерархия встроена всегда. Субсчёт 60.01 имеет родителем счёт 60, у самого счёта 60 родитель пустой. Код тот же: Счет.Родитель. Для отбора «счёт и все его субсчета» в запросе используйте В ИЕРАРХИИ — об этом в следующем разделе.

Как найти родителя через запрос

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

Родитель одного или многих элементов

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

Обращение через точку (Т.Родитель.Наименование) платформа превращает в соединение, отдельного запроса на каждую строку нет.

Несколько уровней вверх одним запросом

Язык запросов не умеет «подниматься до корня» рекурсивно. Но если глубина справочника ограничена (в большинстве учётных справочников это 3–5 уровней), берите уровни явно:

"ВЫБРАТЬ
|    Т.Ссылка КАК Элемент,
|    Т.Родитель КАК Уровень1,
|    Т.Родитель.Родитель КАК Уровень2,
|    Т.Родитель.Родитель.Родитель КАК Уровень3,
|    Т.Родитель.Родитель.Родитель.Родитель КАК Уровень4
|ИЗ
|    Справочник.Номенклатура КАК Т
|ГДЕ
|    Т.Ссылка В (&Список)"

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

Все элементы внутри группы

Для отбора по родителю на любой глубине есть оператор В ИЕРАРХИИ:

"ВЫБРАТЬ
|    Т.Ссылка
|ИЗ
|    Справочник.Номенклатура КАК Т
|ГДЕ
|    Т.Ссылка В ИЕРАРХИИ (&Группа)"

В результат попадёт и сама группа. Если она не нужна, добавьте И Т.Ссылка <> &Группа. Если нужны только прямые потомки, а не вся ветка, пишите Т.Родитель = &Группа.

Тот же оператор работает по счетам: Счет В ИЕРАРХИИ (&Счет60) в регистре бухгалтерии отберёт обороты по 60 и всем его субсчетам.

Итоги по иерархии

Если результат нужно разложить по группам с подытогами, используйте ИТОГИ ... ПО Номенклатура ИЕРАРХИЯ и обход:

Выборка = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией);

Так платформа сама добавит строки групп-родителей над элементами.

Родитель строки в дереве значений

ДеревоЗначений живёт в памяти, базы тут нет. У каждой строки есть свойство Родитель. Для строки верхнего уровня оно равно Неопределено.

Путь от корня до строки

Функция ПутьСтроки(СтрокаДерева, Колонка = "Наименование")
    Части = Новый Массив;
    Текущая = СтрокаДерева;
    Пока Текущая <> Неопределено Цикл
        Части.Вставить(0, Строка(Текущая[Колонка]));
        Текущая = Текущая.Родитель;
    КонецЦикла;
    Возврат СтрСоединить(Части, " / ");
КонецФункции

Для строки «Болты М8» внутри «Крепёж» внутри «Метизы» функция вернёт Метизы / Крепёж / Болты М8.

Поиск строки и её родителя

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

Найденная = Дерево.Строки.Найти(ИскомыйТовар, "Номенклатура", Истина);
Если Найденная <> Неопределено Тогда
    РодительСтроки = Найденная.Родитель;
КонецЕсли;

Без третьего параметра поиск идёт только среди строк верхнего уровня — и строка на втором уровне «не находится», хотя она есть.

Как построить дерево из справочника

Дерево с правильными родителями проще всего получить из запроса: Результат.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией) с итогами ПО ... ИЕРАРХИЯ. Строки групп станут родителями строк элементов, и Строка.Родитель будет указывать на строку группы.

Анализ метаданных: есть ли у справочника иерархия

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

МД = Метаданные.НайтиПоТипу(ТипЗнч(Ссылка));
Если Метаданные.Справочники.Содержит(МД) И МД.Иерархический Тогда
    Если МД.ВидИерархии = Метаданные.СвойстваОбъектов.ВидИерархии.ИерархияГруппИЭлементов Тогда
        // родителем может быть только группа
    Иначе
        // ИерархияЭлементов: родителем может быть обычный элемент
    КонецЕсли;
КонецЕсли;

Что полезно знать о свойствах:

  • ✅ Иерархический — Истина, если у справочника есть поле Родитель;
  • 🗂 ВидИерархии — группы и элементы или элементы без групп;
  • 📏 ОграничиватьКоличествоУровней и КоличествоУровней — предел вложенности, если он задан в конфигураторе;
  • 📌 Владельцы — список справочников-владельцев (для подчинённых справочников).

У самих объектов метаданных тоже есть «родитель» — метод Родитель(). Для реквизита справочника он вернёт сам справочник, для справочника — объект Конфигурация. Это помогает в обработках, которые получают объект метаданных реквизита и должны понять, чей он:

МДРеквизита = Метаданные.Справочники.Номенклатура.Реквизиты.Артикул;
Сообщить(МДРеквизита.Родитель().ПолноеИмя()); // Справочник.Номенклатура

Родитель в управляемых формах

В формах «родитель» встречается в трёх видах, и у каждого своё ограничение клиент-сервер.

Родитель текущего элемента в форме объекта

В форме элемента справочника реквизит Объект.Родитель доступен на клиенте — это ссылка. А вот её реквизиты на клиенте не прочитать: тонкий клиент не разыменовывает ссылки через точку. Объект.Родитель.Наименование в процедуре &НаКлиенте — ошибка.

Решение — короткий серверный вызов без контекста:

&НаКлиенте
Процедура ПоказатьГруппу(Команда)
    Сообщить(ИмяРодителя(Объект.Родитель));
КонецПроцедуры

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

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

Текущая группа в динамическом списке

В форме списка иерархического справочника у таблицы формы есть свойство ТекущийРодитель. Оно показывает группу, внутри которой сейчас стоит пользователь (в режиме просмотра «Иерархический список»):

&НаКлиенте
Процедура СписокПриАктивизацииСтроки(Элемент)
    ТекущаяГруппа = Элементы.Список.ТекущийРодитель;
КонецПроцедуры

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

Строка дерева на форме

Если реквизит формы имеет тип ДеревоЗначений, на клиенте он превращается в ДанныеФормыДерево. У его строк нет свойства Родитель — вместо него метод ПолучитьРодителя():

&НаКлиенте
Процедура ДеревоПриАктивизацииСтроки(Элемент)
    ТекущиеДанные = Элементы.Дерево.ТекущиеДанные;
    Если ТекущиеДанные = Неопределено Тогда
        Возврат;
    КонецЕсли;
    РодительСтроки = ТекущиеДанные.ПолучитьРодителя();
    Если РодительСтроки = Неопределено Тогда
        // строка верхнего уровня
    КонецЕсли;
КонецПроцедуры

Если у вас есть только идентификатор строки (Элементы.Дерево.ТекущаяСтрока), сначала получите строку: Дерево.НайтиПоИдентификатору(Идентификатор), затем вызовите ПолучитьРодителя().

Родитель элемента формы

Последний вариант — родитель самого элемента интерфейса. Элементы.Артикул.Родитель вернёт группу, в которой лежит поле, а для элемента верхнего уровня — саму форму. Так в коде переносят поле в другую группу (Элементы.Переместить()) или проверяют, видима ли группа, где находится поле.

Ошибки при поиске родителя и как их отлаживать

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

Как отлаживать:

  1. Поставьте точку останова на строке с .Родитель и посмотрите тип значения в окне «Вычислить выражение» (Shift+F9 в конфигураторе). Пустая ссылка, Неопределено и строка дерева выглядят по-разному.
  2. Выведите ТипЗнч(Значение). Если там ДанныеФормыЭлементДерева, нужен ПолучитьРодителя(), а не свойство.
  3. Для запроса откройте его в консоли запросов и проверьте результат на одном конкретном элементе с известной глубиной.
  4. Если цепочка подъёма идёт неожиданно долго, выведите Строка(Текущий) на каждом шаге: часто элемент лежит глубже, чем кажется по интерфейсу.

Если вы не только читаете, но и меняете Родитель у объекта (перенос в другую группу), делайте это на тестовой копии базы. Массовый перенос меняет группировки в отчётах и отборы, настроенные по группам. Перед записью в рабочей базе — резервная копия и согласование с ответственным за справочник.

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

Можно ли сделать родителем обычный элемент, а не группу?
Да, если у справочника вид иерархии «Иерархия элементов». При виде «Иерархия групп и элементов» родителем может быть только группа.
Как найти всех родителей одним запросом без ограничения глубины?
Рекурсии в языке запросов нет. Либо берите уровни явно через Родитель.Родитель, либо поднимайтесь по цепочке в коде, либо используйте ИТОГИ ПО … ИЕРАРХИЯ.
Почему у счёта плана счетов родитель пустой, хотя он в списке не первый?
Счёт первого порядка (например, 60) всегда в корне иерархии. Родитель заполнен только у субсчетов вроде 60.01.
Как программно перенести элемент в другую группу?
Получите объект через ПолучитьОбъект(), присвойте новое значение свойству Родитель и вызовите Записать(). Делайте это на сервере.
В ИЕРАРХИИ включает саму группу в результат?
Да. Чтобы исключить её, добавьте условие, что ссылка не равна параметру группы.