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