Как настроить расшифровку в отчете 1С при выводе нескольких макетов СКД в один табличный документ?

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

При создании сложных и красивых отчетов разработчики часто сталкиваются с задачей, когда в один результирующий табличный документ необходимо вывести данные из нескольких разных схем компоновки данных (СКД) или разных макетов. Мы последовательно выводим каждый отчет через ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент, объединяем их в один большой документ, и все выглядит замечательно. Однако при попытке дважды кликнуть по ячейке с документом или справочником мы замечаем, что стандартная расшифровка не работает или открывает совершенно не те объекты.

В этой статье мы подробно разберем, почему ломается этот механизм, как устроена расшифровка «под капотом» платформы 1С и какими способами можно вернуть интерактивность вашему отчету без необходимости полностью его переписывать.


Почему ломается стандартная расшифровка при выводе нескольких СКД?

Выясним причину этой проблемы. Когда платформа 1С выполняет компоновку макета, она генерирует специальный объект типа ДанныеРасшифровкиКомпоновкиДанных. Этот объект представляет собой коллекцию, в которой хранятся подробные описания полей, группировок и их реальных значений для каждой ячейки отчета.

В ячейках результирующего табличного документа в свойстве Расшифровка сохраняются не сами ссылки на элементы справочников или документы, а числовые идентификаторы (индексы типа ИдентификаторРасшифровкиКомпоновкиДанных), которые указывают на конкретный элемент внутри объекта данных расшифровки.

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

  1. Дублирование идентификаторов: Для каждого из 4 макетов генерация индексов начинается заново (например, от 0 до N). В итоге в итоговом документе окажется несколько ячеек с одинаковым идентификатором расшифровки (например, «5»), но в первом отчете этот индекс соответствует «Контрагенту», а во втором — «Номенклатуре».
  2. Потеря контекста данных: Стандартный реквизит формы управляемого отчета ДанныеРасшифровки (или адрес во временном хранилище) может хранить данные только одной схемы компоновки (как правило, последней выведенной). При клике на ячейки из предыдущих трех макетов система пытается искать их индексы в данных расшифровки четвертого макета, что вызывает ошибки или открывает неверные данные.

Ниже мы подробно рассмотрим три практических способа решения этой проблемы, начиная от самого простого (замена индексов на реальные ссылки) и заканчивая созданием динамического обработчика расшифровки.


Способ 1. Постобработка ячеек (замена идентификаторов на реальные ссылки)

Проанализируем самый простой и эффективный способ. Если ваша главная задача — сделать так, чтобы по двойному клику мыши на ячейку у пользователя просто открывалась карточка документа или справочника, нам вообще не нужно хранить сложные объекты данных расшифровки в форме. Мы можем заменить абстрактные числовые идентификаторы в ячейках на прямые ссылки.

Платформа 1С «из коробки» умеет открывать стандартные формы объектов, если в свойстве ячейки Расшифровка записана непосредственная ссылка (например, ДокументСсылка.РеализацияТоваровУслуг или СправочникСсылка.Номенклатура).

Разберем по шагам, как реализовать этот алгоритм на сервере при компоновке отчета:


// Функция формирует один из макетов и выполняет постобработку его области
Процедура ВывестиМакетСПостобработкой(ИмяМакета, ТабличныйДокументРезультат)
    
    СКД = ПолучитьМакет(ИмяМакета);
    Настройки = СКД.НастройкиПоУмолчанию;
    
    // Создаем локальный объект данных расшифровки для текущего макета
    ДанныеРасшифровкиТекущие = Новый ДанныеРасшифровкиКомпоновкиДанных;
    
    КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
    МакетКомпоновки = КомпоновщикМакета.Выполнить(СКД, Настройки, ДанныеРасшифровкиТекущие);
    
    ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
    ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , ДанныеРасшифровкиТекущие, Истина);
    
    // Запоминаем строку, с которой начнется вывод текущего макета
    СтрокаНачала = ТабличныйДокументРезультат.ВысотаТаблицы + 1;
    
    ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
    ПроцессорВывода.УстановитьДокумент(ТабличныйДокументРезультат);
    ПроцессорВывода.Вывести(ПроцессорКомпоновки);
    
    // Запоминаем строку, на которой вывод макета завершился
    СтрокаКонца = ТабличныйДокументРезультат.ВысотаТаблицы;
    
    // Запускаем постобработку выведенной области
    ЗаменитьИдентификаторыНаСсылки(ТабличныйДокументРезультат, ДанныеРасшифровкиТекущие, СтрокаНачала, СтрокаКонца);
    
КонецПроцедуры

Теперь напишем саму процедуру постобработки ячеек ЗаменитьИдентификаторыНаСсылки. Она будет обходить только что выведенные строки табличного документа, находить числовые идентификаторы и заменять их на реальные значения из объекта расшифровки:


Процедура ЗаменитьИдентификаторыНаСсылки(ТабДок, ДанныеРасшифровки, СтрокаНачала, СтрокаКонца)
    
    // Обходим только область строк текущего выведенного макета
    Для Строка = СтрокаНачала По СтрокаКонца Цикл
        Для Колонка = 1 По ТабДок.ШиринаТаблицы Цикл
            
            ОбластьЯчейки = ТабДок.Область(Строка, Колонка);
            ЗначениеРасшифровки = ОбластьЯчейки.Расшифровка;
            
            // Проверяем, содержит ли ячейка идентификатор расшифровки СКД
            Если ТипЗнч(ЗначениеРасшифровки) = Тип("ИдентификаторРасшифровкиКомпоновкиДанных") Тогда
                
                // Получаем элемент расшифровки по идентификатору
                ЭлементРасшифровки = ДанныеРасшифровки.Элементы[ЗначениеРасшифровки];
                
                // Если это поле компоновки, извлекаем его реальное значение
                Если ТипЗнч(ЭлементРасшифровки) = Тип("ЭлементРасшифровкиКомпоновкиДанныхПоля") Тогда
                    ПоляРасшифровки = ЭлементРасшифровки.ПолучитьПоля();
                    Для Каждого Поле Из ПоляРасшифровки Цикл
                        // Записываем реальную ссылку (или значение) напрямую в расшифровку ячейки
                        ОбластьЯчейки.Расшифровка = Поле.Значение;
                        Прервать; // Берем первое доступное значение поля
                    КонецЦикла;
                КонецЕсли;
                
            КонецЕсли;
            
        КонецЦикла;
    КонецЦикла;
    
КонецПроцедуры

Результат: После выполнения этого кода в итоговом документе больше нет никаких "конфликтующих" индексов. В ячейках лежат готовые ссылки. Пользователь делает двойной клик — и платформа 1С автоматически открывает нужный документ или справочник без написания какого-либо кода в модуле формы!


Способ 2. Динамическое сопоставление данных расшифровки по диапазонам строк

Рассмотрим более сложную ситуацию. Что делать, если вам нужна полноценная сложная расшифровка (например, при двойном клике должен открываться другой отчет с передачей параметров и отборов из текущей строки)? В этом случае просто заменить идентификаторы на ссылки нельзя, так как системе требуется вся структура группировок СКД.

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

Посмотрим на пример реализации. На сервере при выводе отчетов мы будем сохранять все 4 объекта данных расшифровки во временное хранилище и записывать соответствие строк табличного документа адресам этих хранилищ в специальный массив на форме.

Создадим на форме отчета реквизит ГраницыТаблиц с типом Произвольный (в нем мы будем хранить массив структур).


&НаСервере
Процедура СкомпоноватьРезультатНаСервере()
    
    // Очищаем итоговый документ и массив границ
    Результат.Очистить();
    ГраницыТаблиц = Новый Массив;
    
    МассивМакетов = Новый Массив;
    МассивМакетов.Добавить("МакетОстатки");
    МассивМакетов.Добавить("МакетОбороты");
    // Добавьте остальные макеты при необходимости...
    
    Для Каждого ИмяМакета Из МассивМакетов Цикл
        
        ДанныеРасшифровкиТекущие = Новый ДанныеРасшифровкиКомпоновкиДанных;
        
        // ... (код стандартной инициализации и вывода макета в документ "Результат") ...
        
        СтрокаНачала = Результат.ВысотаТаблицы + 1;
        
        // Выводим макет
        ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
        ПроцессорВывода.УстановитьДокумент(Результат);
        ПроцессорВывода.Вывести(ПроцессорКомпоновки);
        
        СтрокаКонца = Результат.ВысотаТаблицы;
        
        // Помещаем данные расшифровки во временное хранилище
        АдресХранилища = ПоместитьВоВременноеХранилище(ДанныеРасшифровкиТекущие, УникальныйИдентификатор);
        
        // Фиксируем границы выведенного отчета
        СтруктураГраниц = Новый Структура;
        СтруктураГраниц.Вставить("СтрокаНачала", СтрокаНачала);
        СтруктураGradницы.Вставить("СтрокаКонца", СтрокаКонца);
        СтруктураГраниц.Вставить("АдресРасшифровки", АдресХранилища);
        
        ГраницыТаблиц.Добавить(СтруктураГраниц);
        
    КонецЦикла;
    
    // Помещаем массив границ во временное хранилище или реквизит формы для доступа на клиенте
    АдресГраницВоВременномХранилище = ПоместитьВоВременноеХранилище(ГраницыТаблиц, УникальныйИдентификатор);
    
КонецПроцедуры

Теперь перейдем на клиентскую сторону. У элемента формы "Результат" (который имеет тип ТабличныйДокумент) определим обработчик события ОбработкаРасшифровки:


&НаКлиенте
Процедура РезультатОбработкаРасшифровки(Элемент, Расшифровка, СтандартнаяОбработка, ДополнительныеПараметры)
    
    // Отключаем стандартный обработчик, так как будем вызывать его вручную
    СтандартнаяОбработка = Ложь;
    
    // Получаем строку, по которой кликнул пользователь
    ТекущаяСтрока = Элемент.ТекущаяОбласть.Верх;
    
    // Находим адрес расшифровки для этой строки на сервере
    ПараметрыВыполнения = ПолучитьДанныеДляРасшифровкиНаСервере(ТекущаяСтрока, Расшифровка);
    
    Если ПараметрыВыполнения <> Неопределено Тогда
        // Открываем стандартное окно расшифровки или выполняем свое действие
        // Для демонстрации откроем значение напрямую через ПоказатьЗначение()
        ПоказатьЗначение(, ПараметрыВыполнения.ЗначениеПоля);
    КонецЕсли;
    
КонецПроцедуры

Нам потребуется серверная функция, которая по номеру строки определит нужный адрес временного хранилища, извлечет из него объект ДанныеРасшифровкиКомпоновкиДанных и найдет значение расшифровки по индексу:


&НаСервере
Функция ПолучитьДанныеДляРасшифровкиНаСервере(ТекущаяСтрока, ЗначениеРасшифровки)
    
    // Восстанавливаем таблицу границ из временного хранилища
    ЛокальныеГраницыТаблиц = ПолучитьИзВременногоХранилища(АдресГраницВоВременномХранилище);
    
    АдресТекущейРасшифровки = "";
    Для Каждого ОбластьТаблицы Из ЛокальныеГраницыТаблиц Цикл
        Если ТекущаяСтрока >= ОбластьТаблицы.СтрокаНачала И ТекущаяСтрока <= ОбластьТаблицы.СтрокаКонца Тогда
            АдресТекущейРасшифровки = ОбластьТаблицы.АдресРасшифровки;
            Прервать;
        КонецЕсли;
    КонецЦикла;
    
    Если ПустаяСтрока(АдресТекущейРасшифровки) Тогда
        Возврат Неопределено;
    КонецЕсли;
    
    // Получаем сам объект расшифровки конкретного макета
    ДанныеРасшифр = ПолучитьИзВременногоХранилища(АдресТекущейРасшифровки);
    
    Если ТипЗнч(ЗначениеРасшифровки) = Тип("ИдентификаторРасшифровкиКомпоновкиДанных") Тогда
        ЭлементРасшифровки = ДанныеРасшифр.Элементы[ЗначениеРасшифровки];
        Если ТипЗнч(ЭлементРасшифровки) = Тип("ЭлементРасшифровкиКомпоновкиДанныхПоля") Тогда
            Для Каждого Поле Из ЭлементРасшифровки.ПолучитьПоля() Цикл
                
                // Возвращаем структуру с найденным значением
                РезультатСтруктура = Новый Структура;
                РезультатСтруктура.Вставить("ЗначениеПоля", Поле.Значение);
                Возврат РезультатСтруктура;
                
            КонецЦикла;
        КонецЕсли;
    КонецЕсли;
    
    Возврат Неопределено;
    
КонецФункции

Преимущество метода: Мы полностью сохраняем архитектуру расшифровок СКД. Зная строку клика, мы точно определяем, к какому из 4 макетов она относится, достаем нужный контейнер расшифровки и корректно обрабатываем нажатие.


Резюме и рекомендации

Мы подробно проанализировали причины сбоев при программной компоновке отчетов из нескольких источников в 1С и изучили практические варианты решения задачи. Какой метод выбрать для вашего отчета?

← На главную