Группы иерархического справочника в 1С 8.3: создание, обход дерева, отбор в запросах и формах

· 9 мин

Содержание

Обратиться к группе справочника 1С можно тремя путями: по ссылке через реквизит Родитель, поиском НайтиПоНаименованию() или НайтиПоКоду() с параметром родителя, и запросом с условием В ИЕРАРХИИ. Отличить группу от элемента помогает стандартный реквизит ЭтоГруппа. Всё это работает только в справочнике, у которого в свойствах метаданных включён флаг «Иерархический».

Приёмы ниже общие для платформы 1С:Предприятие 8.3 и не зависят от конфигурации: одинаково работают в БП 3.0, ЗУП 3.1, УТ 11 и в собственной разработке. Примеры даны на справочнике Номенклатура, но имя легко заменить на любое своё. Отдельный раздел — для аналитика, которому группа нужна в отборе отчёта или списка без строчки кода.

Как устроены группы: виды иерархии и свойства метаданных

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

Вид иерархииЧто это значитЕсть ли ЭтоГруппа
Иерархия групп и элементовГруппы — папки, элементы лежат внутри. Элемент не может быть родителемДа
Иерархия элементовЛюбой элемент может быть родителем другого элемента, отдельных «папок» нетНет

Остальные свойства на той же закладке:

  • 📁 Ограничивать количество уровней иерархии и Количество уровней — сколько этажей дерева разрешено. При превышении платформа не даст записать объект.
  • ⬆️ Размещение групп — показывать группы сверху списка или вместе с элементами по порядку сортировки.
  • 🔒 Выбор групп у реквизитов-ссылок на справочник (свойство реквизита «Выбор групп и элементов») — можно ли выбрать в поле группу, элемент или и то, и другое.

Реквизиты справочника тоже делятся по назначению. У каждого есть свойство «Использование»: Для элемента, Для группы или Для группы и элемента. Реквизит «Ставка НДС», заданный только для элемента, в карточке группы не показывается и в ней не хранится. Код, который пишет такой реквизит в группу, результата не даст.

ЭтоГруппа задаётся один раз при создании. Превратить существующий элемент в группу нельзя: нужно создать новую группу и перенести в неё подчинённые элементы.

Создание, перенос и удаление групп справочника из кода

Новую группу создают менеджером справочника методом СоздатьГруппу(). Элемент — методом СоздатьЭлемент(). Дальше обычная запись объекта.

// Создаём группу «Материалы» в корне и подгруппу «Краски» внутри неё
ГруппаМатериалы = Справочники.Номенклатура.СоздатьГруппу();
ГруппаМатериалы.Наименование = "Материалы";
ГруппаМатериалы.Записать();

ГруппаКраски = Справочники.Номенклатура.СоздатьГруппу();
ГруппаКраски.Наименование = "Краски";
ГруппаКраски.Родитель = ГруппаМатериалы.Ссылка;
ГруппаКраски.Записать();

Корень дерева — это пустая ссылка. Если Родитель не заполнен, объект лежит на верхнем уровне. Проверка выглядит так: Ссылка.Родитель.Пустая() или Ссылка.Родитель = Справочники.Номенклатура.ПустаяСсылка().

Найти группу по наименованию или коду

Сигнатуры методов поиска у менеджера справочника:

  • НайтиПоНаименованию(Наименование, ТочноеСоответствие, Родитель, Владелец);
  • НайтиПоКоду(Код, ПоискПоПолномуКоду, Родитель, Владелец).

Параметр Родитель ограничивает поиск одной группой. Это спасает, когда в разных ветках дерева лежат одноимённые группы «Прочее» или «Архив».

Группа = Справочники.Номенклатура.НайтиПоНаименованию("Краски", Истина, ГруппаМатериалы);
Если Группа.Пустая() Тогда
    Сообщить("Группа не найдена");
ИначеЕсли Не Группа.ЭтоГруппа Тогда
    Сообщить("Найден элемент, а не группа");
КонецЕсли;

Методы поиска возвращают первый подходящий объект — группу или элемент. Поэтому проверка ЭтоГруппа после поиска обязательна, если в одной ветке могут совпасть имена группы и товара.

Перенести объект в другую группу

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

Объект = Ссылка.ПолучитьОбъект();
Объект.Родитель = НоваяГруппа;
Объект.Записать();

Переместить группу внутрь её же потомка платформа не позволит — при записи будет ошибка о зацикливании иерархии.

Пометить группу на удаление

У объекта справочника метод УстановитьПометкуУдаления(Пометка, ВключаяПодчиненные) по умолчанию помечает всё содержимое ветки. Если нужно пометить только саму группу, передайте Ложь вторым параметром.

Пометка группы со всеми подчинёнными в рабочей базе может зацепить сотни позиций. Перед массовой пометкой сделайте резервную копию: удаление помеченных объектов необратимо.

Обход дерева: родители, подчинённые элементы и уровень

Для навигации по дереву в объектной модели есть несколько методов ссылки и объекта справочника.

ЗадачаМетод или свойствоРезультат
Узнать прямого родителяСсылка.РодительСсылка на группу или пустая ссылка
Узнать глубинуСсылка.Уровень()Число, верхний уровень = 0
Проверить вхождение в веткуСсылка.ПринадлежитЭлементу(Группа)Истина, если объект лежит в группе на любом уровне
Перебрать прямых детейСправочники.X.Выбрать(Группа)Выборка только первого уровня под группой
Перебрать всю веткуСправочники.X.ВыбратьИерархически(Группа)Выборка всех потомков

Подняться до верхней группы

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

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

Каждое обращение Текущая.Родитель — это чтение из базы. Для пяти уровней это пять маленьких запросов, для одного товара терпимо. Для тысячи товаров в цикле так делать не стоит — ниже есть вариант через запрос.

Спуститься по ветке

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

Выборка = Справочники.Номенклатура.ВыбратьИерархически(ГруппаМатериалы);
Пока Выборка.Следующий() Цикл
    Если Выборка.ЭтоГруппа Тогда
        Продолжить;
    КонецЕсли;
    Сообщить(Выборка.Наименование);
КонецЦикла;

Выборка через менеджер удобна для коротких служебных обработок. Когда нужны отборы по реквизитам, связанным таблицам или сортировка, запрос работает быстрее и читается проще.

Группы справочника в языке запросов 1С

В запросе к группе обращаются через три конструкции. Их стоит знать на память.

Условие «В ИЕРАРХИИ»

Отбирает саму группу и всё её содержимое на любой глубине. Это главный инструмент.

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

В параметр можно передать и массив групп — тогда в результат попадёт содержимое всех веток сразу. Тот же В ИЕРАРХИИ работает в регистрах: Остатки.Номенклатура В ИЕРАРХИИ (&Группа) даст остатки по всем товарам категории без предварительного сбора их списка.

Отбор только по прямым потомкам

Если нужен один уровень, а не вся ветка, условие пишут через родителя:

"ГДЕ Номенклатура.Родитель = &Группа"

Для верхнего уровня передайте в параметр пустую ссылку справочника.

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

Чтобы получить дерево с промежуточными суммами по группам, используют ИТОГИ ... ПО ... ИЕРАРХИЯ. Выборку обходят с режимом ОбходРезультатаЗапроса.ПоГруппировкамСИерархией.

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

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

Какой способ отбора по группе нужен?

Группы в управляемых формах, списках и отчётах

Форма списка

Форма списка иерархического справочника строится на динамическом списке. Вид отображения задаёт свойство таблицы формы Отображение:

  • 🌳 Дерево — всё дерево с раскрытием веток;
  • 📂 ИерархическийСписок — показывается одна группа, вход в неё двойным щелчком;
  • 📋 Список — плоский список без групп-папок.

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

Отбор списка по группе программно

Чтобы показать в динамическом списке только содержимое ветки, добавьте отбор компоновки с видом сравнения «В иерархии»:

ЭлементОтбора = Список.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементОтбора.ЛевоеЗначение  = Новый ПолеКомпоновкиДанных("Ссылка");
ЭлементОтбора.ВидСравнения   = ВидСравненияКомпоновкиДанных.ВИерархии;
ЭлементОтбора.ПравоеЗначение = ГруппаМатериалы;
ЭлементОтбора.Использование  = Истина;

При таком отборе удобнее переключить список в режим Список: в дереве пользователь всё равно будет видеть путь из групп-родителей.

Форма выбора

Когда реквизит документа ссылается на справочник, что можно выбрать — решает свойство реквизита «Выбор групп и элементов». Значение «Элементы» запрещает подставить группу: кнопка выбора на группе не сработает. Если нужно выбирать именно категорию (например, для правила скидки на всю группу товаров), поставьте «Группы» или «Группы и элементы».

Для аналитика: группа в отборе отчёта

Отчёты на СКД в 1С 8.3 умеют отбирать по группе без программиста. В настройках отчёта при отборе по полю-справочнику выберите вид сравнения:

  • В группе — одна ветка со всем содержимым;
  • В группе из списка — несколько веток сразу;
  • Не в группе — исключить ветку.

Те же виды сравнения доступны в настройке отбора обычных списков — её открывают из меню «Ещё» над списком. Это аналог В ИЕРАРХИИ из языка запросов, только для пользователя. Если нужно видеть итоги по группам, в структуре отчёта у группировки по номенклатуре выберите тип «Иерархия».

Производительность и частые ошибки при работе с группами

Обращение через точку в цикле. Код Товар.Родитель.Родитель.Наименование выглядит безобидно, но каждый шаг читает объект из базы. В цикле по тысяче строк табличной части получается несколько тысяч обращений. Решение — один запрос, который заранее достаёт нужные поля, или В ИЕРАРХИИ вместо ручного обхода.

Рекурсия с запросом на каждом уровне. Собрать все подгруппы вручную, вызывая запрос для каждой группы, — медленно и сложно. Платформа уже умеет это делать: В ИЕРАРХИИ или ВыбратьИерархически решают задачу за один проход.

Забытое НЕ ЭтоГруппа. Условие В ИЕРАРХИИ возвращает и группы. В отчёте по товарам это пустые строки без цен и остатков, а в обработке — попытка записать в группу реквизит, которого у неё нет.

Группа там, где ждут элемент. Документ записан с группой в поле «Номенклатура», потому что реквизит разрешал выбирать группы. Потом отчёты и проводки ведут себя странно. Проверьте свойство «Выбор групп и элементов» у реквизита, а в коде перед записью проверяйте ЭтоГруппа.

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

Попытка сменить тип объекта. ЭтоГруппа недоступен для записи. Обработка, которая «переделывает» элемент в группу присвоением, упадёт с ошибкой.

Перед выкладкой кода, работающего с группами
Выполнено: 0 / 5

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

Включает ли В ИЕРАРХИИ саму группу?
Да. В результат попадает и сама группа из параметра, и все её потомки. Если группа не нужна, добавьте условие Ссылка <> &Группа или НЕ ЭтоГруппа.
Что вернёт В ИЕРАРХИИ, если передать элемент, а не группу?
Только сам элемент: у элемента в иерархии групп и элементов нет потомков. Ошибки не будет.
Можно ли сделать справочник иерархическим, когда в нём уже есть данные?
Можно: флаг «Иерархический» включается в конфигураторе, существующие элементы окажутся на верхнем уровне. Это изменение структуры, поэтому потребуется обновление конфигурации базы данных.
Как получить полный путь группы строкой, например «Материалы / Краски»?
Поднимитесь по Родитель до корня и соберите наименования в массив, затем соедините их функцией СтрСоединить() в обратном порядке.
Почему у группы не заполняется реквизит, хотя в коде я его присваиваю?
У реквизита свойство «Использование» стоит «Для элемента». У группы такого реквизита нет. Поменяйте значение на «Для группы и элемента», если реквизит нужен обоим.