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