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