Задача: создать отчет на СКД таким образом, чтобы в нем некоторые группировки вывелись свернутыми.
Далее рассматриваются два примера — свертка всех группировок по условию на поле (свернуть все группы с неотрицательным значением по родительской группировке) и свертка подчиненной группировки у группировок с иерархией с произвольным количеством уровней.
Решать такую задачу без СКД просто — при выводе очередной строчки отчета требуется использовать методы НачатьГруппуСтрок/ЗакончитьГруппуСтрок. При использовании СКД возникают проблемы:
- необходимость перехвата вывода определенных группировок
- отключение автонастройки свертки/развертки групп.
- анализ изменений в структуре.
Далее в статье используется функция ПолучитьМакетГруппировкиПоПолюГруппировки из типового модуля СтандартныеОтчеты (БП 2.0) / БухгалтерскиеОтчеты (БП 3.0). Если вы делаете отчет для базы, в которой нет этих модулей, текст этих функций вам потребуется перенести в модуль объекта (либо общий модуль, доступный из объекта).
Конкретный пример — показать заказы на номенклатуру на дату отгрузки, структура — Номенклатура/Заказы.
Отчет формируется типовым образом:
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
СтандартнаяОбработка=Ложь;
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
ПроцессорКомпоновкиДанных = Новый ПроцессорКомпоновкиДанных;
МакетКомпоновкиДанных = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, КомпоновщикНастроек.Настройки, ДанныеРасшифровки);
ПроцессорКомпоновкиДанных.Инициализировать(МакетКомпоновкиДанных, ,ДанныеРасшифровки);
ДокументРезультат.Очистить();
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
Задача 0) Для начала рассмотрим более простую задачу — свернуть именованную группировку, которая может оказаться на любом (заранее неизвестном) уровне в структуре. Для сворачивания используется метод ПоказатьУровеньГруппировокСтрок объекта типа ТабличныйДокумент. Этот метод принимает параметр - уровень группировки, аналогичный уровню, показываемому в контекстном меню в разделе "уровни группировок", только нумерация начинается с нуля.
Задачи 1 и 2) Общая часть.
Для выборочной группировки требуется анализ выводимого макета, для чего используются методы НачатьВывод / Следующий / ВывестиЭлемент / ЗакончитьВывод объекта типа ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент.
Метод Следующий последовательно возвращает объекты типа ЭлементРезультатаКомпоновкиДанных.
Для решения задач используются следующие свойства этого объекта: полная информация о данных выводимой строчки в коллекции «ЗначенияПараметров», имя макета и тип элемента.
Имена макетов и параметров формализованы — все макеты именуются в виде «Макет#», и все параметры - «П#», где # это номер от одного и далее.
Для того чтобы понять, какая группировка выводится, используется функция ПолучитьМакетГруппировкиПоПолюГруппировки, которая возвращает массив объектов типа ОписаниеМакетаОбластиМакетаКомпоновкиДанных, содержащих данную группировку. Этот массив мы перезаписываем в массив строковых имен вида «Макет#», для удобства поиска.
МакетГруппировкиНом = ПолучитьМакетГруппировкиПоПолюГруппировки(МакетКомпоновкиДанных, "Номенклатура");
МакетНом = Новый Массив;
Для Каждого МакетГруппировки Из МакетГруппировкиНом Цикл
МакетНом.Добавить(МакетГруппировки.Имя);
КонецЦикла;
МакетГруппировкиЗаказ = ПолучитьМакетГруппировкиПоПолюГруппировки(МакетКомпоновкиДанных, "Заказ");
МакетЗаказ = Новый Массив;
Для Каждого МакетГруппировки Из МакетГруппировкиЗаказ Цикл
МакетЗаказ.Добавить(МакетГруппировки.Имя);
КонецЦикла;
Для того, чтобы получить данные о соблюдении условия свертки анализируется значение параметра П#, относящегося к полю, определяющему проверяемое значение и здесь у нас проблема — нет способа привязать параметр П к полю, как мы это сделали с макетами. Значение Параметра только содержит свойство Выражение, в котором прописано вычисляемое выражение. Для полей, описанных в разделе «Наборы данных», выражение имеет вид «НазваниеНабораДанных.НазваниеПоля», например НаборДанных1.Номенклатура, для вычисляемых же полей свойство «Выражение» содержит нормализованное значение в колонке «выражение» - добавлена функция «Представление()», расставлены пробелы, регистры для функций, удалены лишние нули в числах, к каждому параметру добавлен набор данных. Поэтому даже просто скопировать значение из этой колонки не всегда получится. Я предлагаю в начало выражения Вычисляемого поля добавить маркер «0 +» и искать по фрагменту "(0 +" - скобка от того что в начале у вас после преобразования будет "Представление(0 +....".
Вложенные группировки выводятся не просто по очереди а в следующем формате:
( (Р) < ( ( (п) ) [( (п) ) ...] ) > ),
где ( - элемент результата со свойством «ТипЭлемента» = ТипЭлементаРезультатаКомпоновкиДанных.Начало,
) - элемент результата со свойством «ТипЭлемента» = ТипЭлементаРезультатаКомпоновкиДанных.Конец,
(Р) — элемент результата для родительской группировки, имеющий свойство «ТипЭлемента» = ТипЭлементаРезультатаКомпоновкиДанных.НачалоИКонец,
(п) — ... для подчиненной группировки (см. (Р) ),
<, > - я обозначил те места между элементами результатов куда следует добавить НачатьГруппуСтрок/ЗакончитьГруппуСтрок соответственно.
То есть каждая группировка имеет тип НачалоИКонец, и обрамляется элементами Начало и Конец, после чего следуют подчиненные группировки, имеющие тип НачалоИКонец, все вместе обрамленные элементами Начало и Конец и каждая по отдельности обрамлена элементами Начало и Конец.
Судя по всему, типовой механизм СКД вешает группы строк на какие то из этих элементов результатов, но на какие - точно узнать невозможно, так как метода определения текущего назначения группы строк на строку нет. И если не соблюдать парность типов элементов при ручном назначении групп строк, то у вас либо получится каша, либо никакой реакции. Парность гарантированно работает.
Общая идея такая — при получении результата типа НачалоИКонец и соблюдении нашего условия мы сразу ставим НачатьГруппуСтрок, выводим все подчиненные группировки, после чего, соблюдая парность, ставим ЗакончитьГруппуСтрок.
Задача 1) Для отчета в разрезе Номенклатура/Заказ свернуть все группировки номенклатуры без дефицита (то есть таких, для которых остаток товара больше объема заказов).
ИмяПараметраДефицит = Неопределено;
ПроцессорВывода.НачатьВывод();
ВыражениеДефицита = "(0 +";
ЗначениеДефицита = 0;
Пока Истина Цикл
ЭлементРезультата = ПроцессорКомпоновкиДанных.Следующий();
Если ЭлементРезультата = Неопределено Тогда
Прервать;
КонецЕсли;
ПроцессорВывода.ВывестиЭлемент(ЭлементРезультата);
Если ЭлементРезультата.ТипЭлемента = ТипЭлементаРезультатаКомпоновкиДанных.НачалоИКонец Тогда
Если МакетНом.Найти(ЭлементРезультата.Макет) <> Неопределено Тогда
Если ИмяПараметраДефицит = Неопределено Тогда
Для каждого Параметр Из МакетКомпоновкиДанных.Макеты[ЭлементРезультата.Макет].Параметры Цикл
Попытка
Если ТипЗнч(Параметр.Выражение) = Тип("Строка") И Найти(Параметр.Выражение, ВыражениеДефицита)>0 Тогда
ИмяПараметраДефицит = Параметр.Имя;
Прервать
КонецЕсли;
Исключение
КонецПопытки;
КонецЦикла;
КонецЕсли;
ЗначениеДефицита = 0;
Если ИмяПараметраДефицит <> Неопределено Тогда
ЗначениеДефицита = ЭлементРезультата.ЗначенияПараметров[ИмяПараметраДефицит].Значение;
КонецЕсли;
Если ЗначениеДефицита>=0 Тогда
ДокументРезультат.НачатьГруппуСтрок(ЭлементРезультата.ЗначенияПараметров.П1.Значение,Ложь);
Иначе
ДокументРезультат.НачатьГруппуСтрок(ЭлементРезультата.ЗначенияПараметров.П1.Значение,Истина);
КонецЕсли;
//( (Н)!( ( (з) ) ( (з) ) )!)
УровеньНачала = 0;
Пока Истина Цикл
ЭлементРезультата = ПроцессорКомпоновкиДанных.Следующий();
Если ЭлементРезультата.ТипЭлемента = ТипЭлементаРезультатаКомпоновкиДанных.Начало Тогда
УровеньНачала = УровеньНачала + 1;
ИначеЕсли ЭлементРезультата.ТипЭлемента = ТипЭлементаРезультатаКомпоновкиДанных.Конец Тогда
УровеньНачала = УровеньНачала - 1;
КонецЕсли;
ПроцессорВывода.ВывестиЭлемент(ЭлементРезультата);
Если УровеньНачала = 0 Тогда Прервать КонецЕсли;
КонецЦикла;
ДокументРезультат.ЗакончитьГруппуСтрок();
КонецЕсли;
КонецЕсли;
КонецЦикла;
ПроцессорВывода.ЗакончитьВывод();
КонецПроцедуры
Прим. При поиске используется Попытка потому что не каждый Параметр имеет поле Выражение. Но это тема для отдельной статьи.
Пример результата данного отчета:
Задача 2) Для отчета в разрезе Номенклатура/Заказ свернуть все группировки номенклатуры с подчиненными заказами.
В приведенной выше структуре надо поставить тип группировки номенклатуры - Иерархия.
Значения параметров имеют скалярные типы, и номенклатуру мы получаем в виде строки с наименованием. Обычно существует опасность что в базе может быть несколько элементов номенклатуры с одинаковым наименованием, поэтому искать ссылку по наименованию не предлагается. Вместо этого сначала помещаем все результаты в массив. Потом делаем вывод результатов из этого массива, проверяя, не является ли третий результат впереди группировкой «Заказ».
ПроцессорВывода.НачатьВывод();
мРезультат = Новый Массив;
Пока Истина Цикл
ЭлементРезультата = ПроцессорКомпоновкиДанных.Следующий();
мРезультат.Добавить(ЭлементРезультата);
Если ЭлементРезультата = Неопределено Тогда Прервать; КонецЕсли;
КонецЦикла;
Для х=0 по мРезультат.ВГраница()-1 Цикл
ЭлементРезультата = мРезультат[х];
ПроцессорВывода.ВывестиЭлемент(ЭлементРезультата);
Если ЭлементРезультата.ТипЭлемента = ТипЭлементаРезультатаКомпоновкиДанных.НачалоИКонец Тогда
Если МакетНом.Найти(ЭлементРезультата.Макет) <> Неопределено Тогда
// Если у вас в номенклатуре гарантированно уникальные наименования номенклатуры тогда можно
// не помещать результаты в массив а использовать проверку вида
//ЗначРод = ЭлементРезультата.ЗначенияПараметров["П1"].Значение;
//СсылкаРод = Справочники.Номенклатура.НайтиПоНаименованию(ЗначРод,Истина);
//Если НЕ СсылкаРод.ЭтоГруппа Тогда
СледЭлт = мРезультат[х+3];
Если МакетЗаказ.Найти(СледЭлт.Макет) <> Неопределено Тогда
ДокументРезультат.НачатьГруппуСтрок(ЭлементРезультата.ЗначенияПараметров.П1.Значение,Ложь);
//( (Н)!( ( (з) ) ( (з) ) )!)
УровеньНачала = 0;
Пока Истина Цикл
х = х+1;
ЭлементРезультата = мРезультат[х];
Если ЭлементРезультата.ТипЭлемента = ТипЭлементаРезультатаКомпоновкиДанных.Начало Тогда
УровеньНачала = УровеньНачала + 1;
ИначеЕсли ЭлементРезультата.ТипЭлемента = ТипЭлементаРезультатаКомпоновкиДанных.Конец Тогда
УровеньНачала = УровеньНачала - 1;
КонецЕсли;
ПроцессорВывода.ВывестиЭлемент(ЭлементРезультата);
Если УровеньНачала = 0 Тогда Прервать КонецЕсли;
КонецЦикла;
ДокументРезультат.ЗакончитьГруппуСтрок();
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЦикла;
ПроцессорВывода.ЗакончитьВывод();
КонецПроцедуры
Пример результата:
К статье прикрепрен примерный СКД, созданный под задачи организации: заказы берутся без пометки на удаление, заказ на который уже выписана реализация считается полностью отгруженным (нет резервирования для допоставки). В группировке Заказ типовое представление заменено на наименование Партнера. Для конфиденциальности на образцах наименования обрезаны. Отчеты работают в УТ11, ERP 2, если необходим вариант для УТ10, напишите в комментариях.
Отчеты тестировались на версии Платформы 1С:Предприятие 8.3 (8.3.21.1622), хотя старые версии отчета работали в УТ10 с платформ версии 8.3.5. Теоретически должно работать на любой платформе с поддержкой СКД.
P.S. отправной точкой послужила статья СКД: вывод некоторых группировок свернутыми