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

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

В типовых и нетиповых конфигурациях 1С часто возникает задача упорядочить строки документа перед печатью или проведением. Нередко требуется сгруппировать позиции не просто по наименованию, а по иерархии справочника — например, по группе (папке), в которой находится товар. Сложность заключается в том, что реквизит Родитель обычно отсутствует в табличной части документа, а сам объект ТабличнаяЧасть не имеет встроенного метода Сортировать().

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

Почему нельзя отсортировать табличную часть напрямую

Выясним причину, по которой стандартные методы работы с коллекциями вызывают затруднения у разработчиков:

  1. Объект ТабличнаяЧасть не содержит метода Сортировать(). Для него доступен лишь метод Сдвинуть(), который перемещает отдельные строки по одной позиции, что не подходит для быстрой программной сортировки массива данных.
  2. Реквизит Родитель является свойством ссылки справочника Номенклатура, а не отдельным полем табличной части документа. Следовательно, прямого доступа к значению родителя для встроенных механизмов сортировки таблицы документа нет.
  3. На управляемых формах табличная часть представлена объектом ДанныеФормыКоллекция. У этого типа данных также отсутствует возможность произвольной сортировки по полям, не выведенным в структуру коллекции на сервере.

Решение 1. Классический вариант через таблицу значений

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

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

  1. Выгружаем строки табличной части с помощью метода Выгрузить() в объект ТаблицаЗначений.
  2. Добавляем в коллекцию колонок таблицы значений новое поле через метод Колонки.Добавить() с типом СправочникСсылка.Номенклатура или Строка (если сортировка будет выполняться по наименованию родительской папки).
  3. Обходим строки таблицы значений в цикле Для Каждого и заполняем значение родителя через точку: СтрокаТЗ.Номенклатура.Родитель.
  4. Применяем к объекту ТаблицаЗначений метод Сортировать(), передавая имя созданной колонки.
  5. Загружаем отсортированные данные обратно в табличную часть документа с помощью метода Загрузить().

Посмотрим на пример программного кода на сервере:


// Получаем объект документа (например, ДокументОбъект)
ТаблицаТоваров = ДокументОбъект.Товары.Выгрузить();

// Добавляем колонку для хранения ссылки или наименования родительской папки
ТаблицаТоваров.Колонки.Добавить("РодительНоменклатуры", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));

// Заполняем добавленную колонку
Для Каждого Стр Из ТаблицаТоваров Цикл
	Стр.РодительНоменклатуры = Стр.Номенклатура.Родитель;
КонецЦикла;

// Выполняем сортировку по родителю и наименованию самой номенклатуры
ТаблицаТоваров.Сортировать("РодительНоменклатуры Возр, Номенклатура Возр");

// Загружаем результат обратно в табличную часть документа
ДокументОбъект.Товары.Загрузить(ТаблицаТоваров);

Этот метод прост и понятен, однако имеет существенный технический недостаток при работе с большими документами: обращение через точку Стр.Номенклатура.Родитель внутри цикла порождает неявные одиночные запросы к базе данных на каждую позицию (проблема «1+N запросов»). Если в накладной сотни или тысячи строк, это приведет к заметным задержкам.

Решение 2. Оптимальный способ через запрос и временную таблицу

Проанализируем более производительный вариант. Чтобы избежать множественных обращений к СУБД, используем объект Запрос и механизм временных таблиц. Вся обработка данных и получение родительских групп будут выполнены платформой за одно серверное обращение.

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

  1. Выгружаем табличную часть документа в таблицу значений.
  2. Передаем эту таблицу в качестве параметра запроса и помещаем ее во временную таблицу с помощью МенеджерВременныхТаблиц.
  3. В тексте запроса соединяем строки документа со справочником Номенклатура, получая поле Родитель.Наименование или Родитель.Порядок.
  4. Применяем предложение УПОРЯДОЧИТЬ ПО непосредственно в запросе.
  5. Выгружаем результат выполнения запроса в таблицу значений и загружаем обратно в документ через Загрузить().

Посмотрим на пример реализации данного подхода:


ТаблицаИсходная = ДокументОбъект.Товары.Выгрузить();

Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст = 
	"ВЫБРАТЬ
	|	ТЗ.НомерСтроки КАК НомерСтроки,
	|	ТЗ.Номенклатура КАК Номенклатура,
	|	ТЗ.Количество КАК Количество,
	|	ТЗ.Цена КАК Цена,
	|	ТЗ.Сумма КАК Сумма
	|ПОМЕСТИТЬ ВТ_Товары
	|ИЗ
	|	&ТаблицаТоваров КАК ТЗ
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|ВЫБРАТЬ
	|	ВТ.Номенклатура КАК Номенклатура,
	|	ВТ.Количество КАК Количество,
	|	ВТ.Цена КАК Цена,
	|	ВТ.Сумма КАК Сумма
	|ИЗ
	|	ВТ_Товары КАК ВТ
	|		ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК СпрНоменклатура
	|		ПО ВТ.Номенклатура = СпрНоменклатура.Ссылка
	|УПОРЯДОЧИТЬ ПО
	|	СпрНоменклатура.Родитель.Наименование ВОЗР,
	|	СпрНоменклатура.Наименование ВОЗР";

Запрос.УстановитьПараметр("ТаблицаТоваров", ТаблицаИсходная);

РезультатЗапроса = Запрос.Выполнить();
ТаблицаСортированная = РезультатЗапроса.Выгрузить();

ДокументОбъект.Товары.Загрузить(ТаблицаСортированная);

Важный момент: при выгрузке результата запроса обратно в табличную часть убедимся, что состав и имена выбранных полей строго совпадают с реквизитами табличной части документа. Лишние поля (например, искусственно выбранное поле наименования родителя) в результирующий список включать не нужно, достаточно указать их только в секции УПОРЯДОЧИТЬ ПО.

Особенности работы с иерархией справочника

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

  1. Многоуровневая вложенность: свойство Родитель возвращает только непосредственную вышестоящую группу (на один уровень вверх). Если справочник имеет глубокую структуру (например, «Бытовая техника -> Стиральные машины -> Фронтальные»), товары из разных подгрупп могут не сгруппироваться по верхнему уровню каталога. Если требуется сортировка по корневой группе, необходимо программно определять родителя верхнего уровня (пока Родитель.Родитель не равен пустой ссылке) либо упорядочивать строки по полному наименованию иерархии.
  2. Элементы в корне справочника: если позиция номенклатуры создана без группы (в корне каталога), значение реквизита Родитель равно значению Справочники.Номенклатура.ПустаяСсылка(). При сортировке по возрастанию такие позиции всегда окажутся первыми в списке.

Нюансы применения на управляемых формах

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

  1. В контексте формы табличная часть представлена объектом ДанныеФормыКоллекция. Сортировать ее напрямую на клиенте невозможно.
  2. Сортировку следует выполнять в серверной процедуре. Для этого преобразуем реквизит формы в прикладной объект с помощью функции РеквизитФормыВЗначение("Объект").
  3. После выполнения сортировки табличной части полученного объекта возвращаем данные в контекст формы через метод ЗначениеВРеквизитФормы(ДокументОбъект, "Объект").

Посмотрим на пример серверного метода формы:


&НаСервере
Процедура ОтсортироватьТабличнуюЧастьПоРодителюНаСервере()
	
	ДокументОбъект = РеквизитФормыВЗначение("Объект");
	
	ТаблицаТоваров = ДокументОбъект.Товары.Выгрузить();
	ТаблицаТоваров.Колонки.Добавить("РодительНаименование", Новый ОписаниеТипов("Строка",,,, Новый КвалификаторыСтроки(150)));
	
	Для Каждого Стр Из ТаблицаТоваров Цикл
		Если ЗначениеЗаполнено(Стр.Номенклатура.Родитель) Тогда
			Стр.РодительНаименование = Стр.Номенклатура.Родитель.Наименование;
		КонецЕсли;
	КонецЦикла;
	
	ТаблицаТоваров.Сортировать("РодительНаименование Возр, Номенклатура Возр");
	ДокументОбъект.Товары.Загрузить(ТаблицаТоваров);
	
	ЗначениеВРеквизитФормы(ДокументОбъект, "Объект");
	
КонецПроцедуры

Альтернативные варианты решения

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

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