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