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