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