Как вывести строки табличной части документа в динамический список?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В практике разработки на платформе 1С:Предприятие 8.3 часто возникает задача: отобразить в списке документов (например, путевых листов или маршрутов) агрегированные данные из их табличных частей — перечень адресов доставки, номенклатуру или связанные заказы. При реализации этой задачи разработчики регулярно сталкиваются с архитектурными ограничениями платформы. Выясним причину данных сложностей и подробно разберем все способы реализации.

Почему нельзя просто соединить таблицы в запросе динамического списка?

Проанализируем технические ограничения встроенного языка запросов и механизма динамических списков:

  1. Отсутствие строковой агрегации. Во встроенном языке запросов 1С нет аналогов SQL-функций STRING_AGG или GROUP_CONCAT. Нельзя средствами одного запроса «склеить» строки подчиненной таблицы через запятую или перенос строки.
  2. Размножение строк при соединении. Если связать документ с его табличной частью через левое соединение в запросе динамического списка, произойдет соединение типа «один-ко-многим». Строка документа будет дублироваться столько раз, сколько строк содержится в его табличной части, что разрушает постраничную выборку данных и делает работу со списком некорректной.
  3. Запрет итогов в динамическом списке. Конструкция ИТОГИ запрещена к использованию в запросах динамических списков платформы.
  4. Проблемы с ключевыми полями дерева. При попытке включить режим отображения «Дерево» на произвольном запросе динамического списка возникают конфликты уникальности записей, если в качестве основной таблицы не указана конкретная таблица базы данных со своими ключевыми полями.

Ниже подробно рассмотрим практические варианты решения задачи, от простых настроек до полноценной программной обработки.

Способ 1. Динамическое заполнение через событие «ПриПолученииДанныхНаСервере»

Это наиболее гибкий метод, который не требует изменения структуры метаданных и дублирования информации в базе данных. Данные табличной части будут считываться и форматироваться «на лету» только для тех строк, которые физически отображаются на экране пользователя.

Разберем реализацию по шагам:

  1. Добавим в динамический список пользовательскую колонку (например, СписокАдресов) со строковым типом данных. В свойствах элемента формы укажем свойства АвтоВысотаСтроки = Истина или зададим фиксированную высоту.
  2. Для таблицы формы назначим обработчик события ПриПолученииДанныхНаСервере.
  3. В коде обработчика соберем массив ссылок отображаемых документов, выполним пакетный запрос к табличной части и заполним текст ячеек.

Посмотрим на пример кода обработчика формы:


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

Важное ограничение: так как эти данные формируются исключительно для визуального оформления видимых строк, по такой виртуальной колонке стандартный поиск платформы (Ctrl+F) и сортировка работать не будут.

Способ 2. Денормализация данных (хранение строки в реквизите или регистре)

Если по списку адресов или заказов пользователям требуется выполнять быстрый отбор или поиск, наилучшим архитектурным решением будет денормализация данных.

Рассмотрим подробнее варианты денормализации:

  1. Строковый реквизит документа. Добавим в документ реквизит СписокАдресов (строка неограниченной длины или фиксированной, например, 1000 символов). В обработчике ПередЗаписью модуля документа сформируем итоговую строку по табличной части и запишем ее в этот реквизит. Поле реквизита напрямую выводится в динамический список с включенной автовысотой строки.
  2. Отдельный регистр сведений. Если данные адресов могут меняться в подчиненных документах (например, адрес изменился непосредственно в заказе) и не хочется лишний раз перезаписывать сам документ маршрута, вынесем эту информацию в непериодический регистр сведений. Измерения: Маршрут; ресурсы: СтрокаАдресов. Обновлять запись в регистре можно как при проведении маршрута, так и при изменении связанного заказа. В динамическом списке левым соединением подтягиваем ресурс регистра.

Способ 3. Вывод данных в виде дерева через группировку

Если требуется просматривать каждую строчку табличной части в виде раскрывающейся ветки дерева, настроим отображение динамического списка без создания промежуточных таблиц значений:

  1. Откроем запрос динамического списка и установим в качестве источника саму табличную часть: Документ.Маршрут.Заказы.
  2. Укажем в свойствах динамического списка свойство Основная таблица: Документ.Маршрут.Заказы. Это исключит конфликт ключевых полей, так как платформа будет использовать пару ключей Ссылка и НомерСтроки.
  3. В свойствах таблицы формы переключим вид Отображение в значение Дерево.
  4. В настройках динамического списка (закладка «Группировка») добавим группировку по полю Ссылка (сам документ).

В результате в родительских строках дерева будут сгруппированы данные шапки маршрута, а при раскрытии плюсика отобразятся подчиненные строки заказов с их адресами.

Способ 4. Организация связанного динамического списка («Master-Detail»)

Разберем классический интерфейсный прием, решающий проблему перегруженности экранной формы:

  1. Разместим на форме два элемента: основной динамический список маршрутов и подчиненный динамический список заказов.
  2. В подчиненном динамическом списке настроим запрос к табличной части документов:
    
    ВЫБРАТЬ
        МаршрутЗаказы.НомерСтроки КАК НомерСтроки,
        МаршрутЗаказы.Заказ КАК Заказ,
        МаршрутЗаказы.Адрес КАК Адрес
    ИЗ
        Документ.Маршрут.Заказы КАК МаршрутЗаказы
    ГДЕ
        МаршрутЗаказы.Ссылка = &ТекущийМаршрут
    
  3. В обработчике события ПриАктивизацииСтроки основного списка маршрутов передаем ссылку на выделенный маршрут в параметр запроса подчиненного списка:
    
    &НаКлиенте
    Процедура СписокМаршрутовПриАктивизацииСтроки(Элемент)
        ТекущиеДанные = Элемент.ТекущиеДанные;
        Если ТекущиеДанные <> Неопределено Тогда
            Элементы.СписокЗаказов.Отбор.Ссылка.Установить(ТекущиеДанные.Ссылка);
            // Либо через установку параметра динамического списка
        КонецЕсли;
    КонецПроцедуры
    

Такой подход обеспечивает максимальную производительность, не нагружает базу тяжелыми выборками и оставляет интерфейс понятным для пользователя.

← На главную