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