Как вывести список связанных значений через запятую в динамическом списке 1С при ограничениях платформы

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

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

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

Проблема: ограничения динамического списка

Рассмотрим ситуацию: у нас есть документ ЗаявкаНаВывоз. По одной заявке может быть сформировано несколько документов ПутевойЛист, в каждом из которых указан свой Водитель. Нам нужно в списке заявок видеть колонку "Водители", где фамилии перечислены через запятую.

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

Решение 1: Денормализация данных (Хранение строки в БД)

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

Вариант А: Дополнительный реквизит в документе.
В документ ЗаявкаНаВывоз добавляется строковый реквизит СписокВодителей (длина 200–500 символов). При записи любого путевого листа, связанного с этой заявкой, мы пересчитываем строку и записываем её в заявку.

Вариант Б: Независимый регистр сведений.
Если вы не хотите менять структуру самого документа, создадим регистр сведений СведенияОВодителяхЗаявок. Измерение: Заявка (Ссылка), Ресурс: СписокВодителей (Строка).

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

  1. Создадим общую процедуру в модуле.
  2. Настроим подписку на событие ПриЗаписи для документа ПутевойЛист.
  3. Внутри процедуры будем находить родительскую заявку и обновлять данные.

Процедура ОбновитьСписокВодителейВЗаявке(ЗаявкаСсылка) Экспорт
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ РАЗЛИЧНЫЕ
        |   ПутевойЛист.Водитель.Наименование КАК ФИО
        |ИЗ
        |   Документ.ПутевойЛист КАК ПутевойЛист
        |ГДЕ
        |   ПутевойЛист.ДокументОснование = &Заявка
        |   И ПутевойЛист.Проведен = ИСТИНА";
    
    Запрос.УстановитьПараметр("Заявка", ЗаявкаСсылка);
    Результат = Запрос.Выполнить().Выбрать();
    
    МассивВодителей = Новый Массив;
    Пока Результат.Следующий() Цикл
        МассивВодителей.Добавить(Результат.ФИО);
    КонецЦикла;
    
    СтрокаВодителей = СтрСоединить(МассивВодителей, ", ");
    
    // Записываем в регистр или реквизит документа
    Менеджер = РегистрыСведений.СведенияОВодителяхЗаявок.СоздатьМенеджерЗаписи();
    Менеджер.Заявка = ЗаявкаСсылка;
    Менеджер.СписокВодителей = СтрокаВодителей;
    Менеджер.Записать();
КонецПроцедуры

Решение 2: Программное заполнение в событии списка

Если режим совместимости позволяет использовать событие ПриПолученииДанныхНаСервере (доступно с версии 8.3.8, но мы рассмотрим его как альтернативу для тех, кто может поднять режим), то можно обойтись без хранения данных в БД. Выясним, как это работает: динамический список запрашивает только те строки, которые видны на экране (например, 20-30 штук). Мы перехватываем эти ссылки и одним запросом получаем по ним водителей.

Разберем алгоритм в модуле формы:


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

Важно: У этого метода есть существенный минус — по колонке "Водители" будет невозможно сделать стандартную сортировку или поиск, так как данных нет в базе данных, они вычисляются "на лету" только для отображения.

Решение 3: Организация поиска по связанным данным

Если мы выбрали Решение 1 (хранение строки), то поиск работает "из коробки". Но что делать, если нужно отфильтровать список по конкретному водителю (выбор из справочника), а не просто искать по подстроке?

Проанализируем ситуацию: пользователь выбирает водителя в шапке формы. Мы должны наложить отбор на динамический список. Выясним, как это сделать эффективно:

  1. Добавим на форму реквизит ОтборВодитель (тип СправочникСсылка.Водители).
  2. В событии ПриИзменении этого реквизита будем программно устанавливать отбор по полю Ссылка документа ЗаявкаНаВывоз.

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

Выводы и рекомендации

Для старых режимов совместимости (8.2.13) оптимальным решением является комбинированный подход:

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

← На главную