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