Для примера возьмем некоторый документ с табличной частью Остатки на конец смены. Структура полей табличной части представлена на рисунке справа.
Заказчику неудобно работать с ней в таком виде, и он желает просматривать ее и редактировать в виде кросс-таблицы, как на рисунке слева.
Замечание: на картинке приводится две таблицы одновременно для наглядности. В реальных задачах обычно требуется отображать только кросс-таблицу.
Основная сложность в сопоставлении колонок кросс-таблицы с соответствующими строками табличной части. Ниже представлены три процедуры, которые решают данную задачу за несколько строк кода. Вот пример их использования:
Процедура ТранспорироватьТабличнуюЧастьВКроссТаблицу()
// 1. Рассчитаем необходимое количество колонок в новой таблице значений
ТаблицаСоответствий = ПолучитьИдентификаторыДанных(
ОстаткиНаКонецСмены.Выгрузить(), // исходная таблица значений
"Стеллаж"); // поля вертикальной группировки через запятую
ОстаткиНаКонецСмены_Служебная.Загрузить(ТаблицаСоответствий);
Для Каждого Строка Из ОстаткиНаКонецСмены_Служебная Цикл
Строка.Заголовок = Строка.Стеллаж; // заголовок колонки при разворачивании
Строка.Тип = Новый ОписаниеТипов("Число");
КонецЦикла;
// 2. Получим транспорированную таблицу
КроссТаблица = ТрансформироватьДанныеВКроссТаблицу(
ОстаткиНаКонецСмены.Выгрузить(), // исходная таблица значений
ОстаткиНаКонецСмены_Служебная.Выгрузить(), // служебная таблица значений
"Номенклатура", // поля горизонтальной группировки через запятую
"Количество"); // поле, из которого будет подставляться значение
// на пересечении вертикальных и горизонтальных группировок
КонецПроцедуры
Процедура ТранспорироватьКроссТаблицуВТабличнуюЧасть()
// Обратная трансформация
ТабличнаяЧасть = ТрансформироватьКроссТаблицуВДанные(
ОстаткиНаКонецСмены_КроссТаблица.Выгрузить(),
ОстаткиНаКонецСмены_Служебная.Выгрузить(),
"Количество"); // поле, в которое будут записываться значения
// из пересечения вертикальных и горизонтальных группировок
ОстаткиНаКонецСмены.Загрузить(ТабличнаяЧасть);
КонецПроцедуры
Небольшие пояснения:
1. Дополнительная информация для связи между исходной и конечной таблицами хранится в служебной таблице. Ее необходимо сохранять на протяжении всей работы с кросс-таблицей. Ее структура простая: Имя, Заголовок и Тип колонок, а также имена полей табличной части, по сочетанию которых будут создаваться колонки. В моем примере это одно поле “Стеллаж”, но их может быть и несколько, например “Номенклатура,Характеристика”. Для рассматриваемого примера структура служебной таблицы с данными представлена на рисунке
2. Прямое преобразование состоит из двух шагов:
- генерируем идентификаторы колонок для кросс-таблицы, заполняем заголовки и типы дополнительных полей. В моем примере в заголовок я пишу наименование стеллажа, тип на пересечении горизонтальных группировок - числовой.
- получаем кросс-таблицу
Сами процедуры
// Пусть нужно по некоторой таблице значений создать другую таблицу значений
// При этом колонки второй таблицы соответствуют сочетанию полей в первой таблице
// (например, в первой таблице одна колонка Номенклатура, а во второй должна быть отдельная колонка для каждой номенклатуры)
// Тогда первую таблицу будем для краткости называть ДАННЫЕ, а вторрую КРОССТАБЛИЦА
// для решения этой задачи потребуется дополнительная таблица, которую будем назывть
// ИДЕНТИФИКАТОРЫКОЛОНОК
// Данный алгоритм выполняет преобразования
// - ДАННЫЕ (1)-> ИДЕНТИФИКАТОРЫКОЛОНОК (2)-> КРОССТАБЛИЦА
// - КРОССТАБЛИЦА (3)-> ДАННЫЕ
Функция ПолучитьИдентификаторыДанных(тДанные, стрПоляВертикальныхГруппировок) Экспорт
тИдентификаторыКолонок = тДанные.Скопировать(,стрПоляВертикальныхГруппировок);
тИдентификаторыКолонок.Свернуть(стрПоляВертикальныхГруппировок);
тИдентификаторыКолонок.Колонки.Добавить("Имя");
тИдентификаторыКолонок.Колонки.Добавить("Заголовок");
тИдентификаторыКолонок.Колонки.Добавить("Тип");
Для сч = 1 по тИдентификаторыКолонок.Количество() Цикл
тИдентификаторыКолонок[сч-1].Имя = "Группировка_" + Формат(сч, "ЧЦ=5; ЧВН=; ЧГ=0");
КонецЦикла;
Возврат тИдентификаторыКолонок;
КонецФункции
Функция ТрансформироватьДанныеВКроссТаблицу(тДанные, тИдентификаторыКолонок, стрГоризонтальныеГруппировки, ИмяКолонкиРесурс) Экспорт
тКроссТаблица = тДанные.Скопировать(,стрГоризонтальныеГруппировки);
тКроссТаблица.Свернуть(стрГоризонтальныеГруппировки);
Для каждого Строка из тИдентификаторыКолонок Цикл
тКроссТаблица.Колонки.Добавить(Строка.Имя, Строка.Тип, Строка.Заголовок);
КонецЦикла;
СтруктураПоискаВертикальныхГруппировок = Новый Структура;
Для каждого Колонка из тИдентификаторыКолонок.Колонки Цикл
Если Колонка.Имя = "Имя" Тогда
Продолжить;
ИначеЕсли Колонка.Имя = "Заголовок" Тогда
Продолжить;
ИначеЕсли Колонка.Имя = "Тип" Тогда
Продолжить;
КонецЕсли;
СтруктураПоискаВертикальныхГруппировок.Вставить(Колонка.Имя);
КонецЦикла;
СтруктураПоискаГоризонтальныхГруппировок = Новый Структура(стрГоризонтальныеГруппировки);
Для каждого Строка из тДанные Цикл
ЗаполнитьЗначенияСвойств(СтруктураПоискаГоризонтальныхГруппировок, Строка);
НайденныеСтроки = тКроссТаблица.НайтиСтроки(СтруктураПоискаГоризонтальныхГруппировок);
Если НайденныеСтроки.Количество() Тогда
СтрокаОтображения = НайденныеСтроки[0];
Иначе
СтрокаОтображения = тКроссТаблица.Добавить();
ЗаполнитьЗначенияСвойств(СтрокаОтображения, СтруктураПоискаГоризонтальныхГруппировок);
КонецЕсли;
ЗаполнитьЗначенияСвойств(СтруктураПоискаВертикальныхГруппировок, Строка);
НайденныеСтроки = тИдентификаторыКолонок.НайтиСтроки(СтруктураПоискаВертикальныхГруппировок);
ИмяКолонкиОтображения = НайденныеСтроки[0].Имя; // найденное значение ВСЕГДА будет. Если нет, то это несоблюдение требований алгоритма. Выдаем программную ошибку
СтрокаОтображения[ИмяКолонкиОтображения] = Строка[ИмяКолонкиРесурс];
КонецЦикла;
Возврат тКроссТаблица;
КонецФункции
Функция ТрансформироватьКроссТаблицуВДанные(тКроссТаблица, тИдентификаторыКолонок, ИмяКолонкиРесурс) Экспорт
тДанные = Новый ТаблицаЗначений;
тДанные.Колонки.Добавить(ИмяКолонкиРесурс);
Для каждого Колонка из тКроссТаблица.Колонки Цикл
Если Найти(Колонка.Имя, "Группировка_") = 1 Тогда
Продолжить;
КонецЕсли;
тДанные.Колонки.Добавить(Колонка.Имя, Колонка.ТипЗначения);
КонецЦикла;
Для каждого Колонка из тИдентификаторыКолонок.Колонки Цикл
Если Колонка.Имя = "Имя" Тогда
Продолжить;
ИначеЕсли Колонка.Имя = "Заголовок" Тогда
Продолжить;
КонецЕсли;
тДанные.Колонки.Добавить(Колонка.Имя);
КонецЦикла;
Для каждого КолонкаОтображения из тКроссТаблица.Колонки Цикл
Если НЕ Найти(КолонкаОтображения.Имя, "Группировка_") = 1 Тогда
Продолжить;
КонецЕсли;
СтрокаСоответствияКолонок = тИдентификаторыКолонок.Найти(КолонкаОтображения.Имя, "Имя"); // Строка должна быть найдена ВСЕГДА. Иначе это несоблюдение требований алгоритма - будет выдана программная ошибка
Для каждого СтрокаОтображения из тКроссТаблица Цикл
Значение = СтрокаОтображения[КолонкаОтображения.Имя];
Если НЕ ЗначениеЗаполнено(Значение) Тогда
Продолжить;
КонецЕсли;
НоваяСтрока = тДанные.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаОтображения);
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаСоответствияКолонок);
НоваяСтрока[ИмяКолонкиРесурс] = Значение;
КонецЦикла;
КонецЦикла;
Возврат тДанные;
КонецФункции
Отмечу также, что данные методы можно использовать не только для разворачивания табличной части, но и таблицы из запроса (например, в АРМе)
Процедуры тестировались на платформе 8.3 в режиме совместимости с 8.2. Однако скорее всего будут работать и на платформе 8.1
Прикладываю работающий пример для обычных и управляемых форм. Отмечу, что для обычных форм задача намеренно несколько усложнена - в кросс-таблицу добавлена служебная колонка с суммой по строке, а также выводятся все стеллажи, которые связаны со складом, в том числе и те, которых нет в табличной части.