При кастомизации типовых отчетов перед разработчиками часто встает задача: изменить оформление или текст конкретной ячейки в зависимости от того, какое прикладное значение в ней выведено. Например, в отчете «Оборотно-сальдовая ведомость по счету» (ОСВ) требуется дописать определенный текст к субконто определенного типа или с определенными реквизитами. Напрямую из текста ячейки получить объект базы данных невозможно, так как там находится лишь строковое представление. Единственный надежный способ — обратиться к данным расшифровки.
В этой статье мы подробно разберем, как устроена расшифровка в Системе компоновки данных (СКД), почему разработчики часто сталкиваются с ошибками при попытке ее прочитать, и как реализовать эту задачу элегантно, избегая избыточного заимствования форм в расширениях.
Когда мы пытаемся получить значение расшифровки непосредственно из ячейки табличного документа через свойство Область.Расшифровка, платформа возвращает нам объект типа ИдентификаторРасшифровкиКомпоновкиДанных. По сути, это просто уникальное число (индекс), которое само по себе не содержит ни ссылок на справочники, ни документов, ни названий полей.
Многие начинающие разработчики совершают здесь две классические ошибки:
Данные = ПолучитьИзВременногоХранилища(Адрес).ДанныеРасшифровки.Элементы;
Это вызывает ошибку выполнения, так как по адресу во временном хранилище уже лежит готовый объект типа ДанныеРасшифровкиКомпоновкиДанных, и повторное обращение к свойству ДанныеРасшифровки избыточно и ошибочно.
Рассмотрим, как правильно извлечь реальную ссылку (например, на номенклатуру, контрагента или договор) по числовому идентификатору из ячейки. Для этого нам понадобится доступ к объекту расшифровки и сам идентификатор.
Выполним следующие шаги в коде:
// 1. Извлекаем объект расшифровки из временного хранилища по его адресу
ДанныеРасшифровкиОбъект = ПолучитьИзВременногоХранилища(АдресВременногоХранилища);
// 2. Получаем идентификатор из конкретной ячейки табличного документа
Идентификатор = ОбластьЯчейки.Расшифровка;
// 3. Проверяем, что это действительно идентификатор СКД
Если ТипЗнч(Идентификатор) = Тип("ИдентификаторРасшифровкиКомпоновкиДанных") Тогда
// Получаем конкретный элемент расшифровки по его индексу
ЭлементРасшифровки = ДанныеРасшифровкиОбъект.Элементы[Идентификатор];
// Проверяем тип элемента. Для простых полей это ЭлементРасшифровкиКомпоновкиДанныхПоля
Если ТипЗнч(ЭлементРасшифровки) = Тип("ЭлементРасшифровкиКомпоновкиДанныхПоля") Тогда
// Обходим все поля, привязанные к этой ячейке
Для Каждого ПолеИзРасшифровки Из ЭлементРасшифровки.ПолучитьПоля() Цикл
ИмяПоля = ПолеИзРасшифровки.Поле; // Например, "Субконто1" или "Контрагент"
ЗначениеПоля = ПолеИзРасшифровки.Значение; // Реальная ссылка из базы данных
// Здесь мы можем выполнять любую логику анализа значения
Если ТипЗнч(ЗначениеПоля) = Тип("СправочникСсылка.Контрагенты") Тогда
// Наше целевое действие
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецЕсли;
Проанализируем ситуацию, когда пользователь кликает на ячейку с числовым ресурсом (например, «Сумма оборот» в ОСВ или продажах). Напрямую к такой ячейке не привязано конкретное поле вроде «Номенклатура». Элемент расшифровки для такой ячейки будет иметь тип ЭлементРасшифровкиКомпоновкиДанныхГруппировка.
Чтобы получить значения измерений, к которым относится этот ресурс, нам необходимо подняться вверх по иерархии группировок. Для этого напишем вспомогательную рекурсивную процедуру, которая будет собирать все родительские поля:
Процедура ПолучитьЗначенияРодительскихПолей(ЭлементРасшифровки, СтруктураДанных) Экспорт
// Получаем коллекцию родительских элементов расшифровки
МассивРодителей = ЭлементРасшифровки.ПолучитьРодителей();
Для Каждого Родитель Из МассивРодителей Цикл
// Если родитель содержит поля группировок
Если ТипЗнч(Родитель) = Тип("ЭлементРасшифровкиКомпоновкиДанныхПоля") Тогда
Для Каждого Поле Из Родитель.ПолучитьПоля() Цикл
// Заполняем структуру результатами, если поле еще не было заполнено ниже по иерархии
Если Не СтруктураДанных.Свойство(Поле.Поле) Или СтруктураДанных[Поле.Поле] = Неопределено Тогда
СтруктураДанных.Вставить(Поле.Поле, Поле.Значение);
КонецЕсли;
Цикла;
КонецЕсли;
// Рекурсивно поднимаемся к вышестоящим группировкам отчета
ПолучитьЗначенияРодительскихПолей(Roditel, СтруктураДанных);
КонецЦикла;
КонецПроцедуры
Передав в эту процедуру текущий элемент расшифровки и пустую структуру Новый Структура, на выходе мы получим полный набор ключевых полей (измерений), определяющих данную строку отчета. При необходимости можно воспользоваться примером отчета с алгоритмом получения группировок из данных расшифровки СКД.
В процессе обсуждения на форумах часто предлагается заимствовать форму отчета в расширение, чтобы перехватить переменную ДанныеРасшифровки (которая хранит адрес во временном хранилище). Это приводит к созданию лишних зависимостей, усложняет будущие обновления конфигурации и захламляет проект.
Выясним, как обойтись без заимствования формы. Наиболее изящный способ — перехватить компоновку результата непосредственно в Модуле объекта отчета с помощью стандартного обработчика события ПриКомпоновкеРезультата.
В расширении для модуля объекта отчета мы можем использовать аннотацию &После. В этот момент отчет уже сформирован, табличный документ заполнен, а объект расшифровки доступен напрямую в виде готового объекта 1С, без необходимости извлечения из временного хранилища!
Рассмотрим пример реализации такого расширения:
&После("ПриКомпоновкеРезультата")
Процедура Расш_ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
// Если по каким-то причинам расшифровка не формируется, выходим
Если ДанныеРасшифровки = Неопределено Тогда
Возврат;
КонецЕсли;
// Обходим ячейки сформированного табличного документа
// Для оптимизации можно ограничить обход только нужными колонками
Для НомерСтроки = 1 По ДокументРезультат.ВысотаТаблицы Цикл
Для НомерКолонки = 1 По ДокументРезультат.ШиринаТаблицы Цикл
Область = ДокументРезультат.Область(НомерСтроки, НомерКолонки);
Идентификатор = Область.Расшифровка;
Если ТипЗнч(Идентификатор) = Тип("ИдентификаторРасшифровкиКомпоновкиДанных") Тогда
// Напрямую получаем элемент из объекта ДанныеРасшифровки
Элемент = ДанныеРасшифровки.Элементы.Получить(Идентификатор);
Если ТипЗнч(Элемент) = Тип("ЭлементРасшифровкиКомпоновкиДанныхПоля") Тогда
Для Каждого Поле Из Элемент.ПолучитьПоля() Цикл
// Проверяем интересующее нас поле группировки
Если Поле.Поле = "Субконто1" Тогда
СсылкаНаСубконто = Поле.Значение;
// Анализируем ссылку и вносим коррективы в отображение ячейки
Если ТипЗнч(СсылкаНаСубконто) = Тип("СправочникСсылка.Номенклатура") Тогда
Если СсылкаНаСубконто.Услуга Тогда
Область.Текст = Область.Текст + " (Услуга)";
Область.ЦветФона = WebЦвета.СветлоЗеленый;
EndIf;
КонецЕсли;
КонецЕсли;
Цикла;
КонецЕсли;
КонецЕсли;
Цикла;
Цикла;
КонецПроцедуры
Преимущества данного метода очевидны: мы не трогаем форму отчета, код расширения максимально изолирован, а сам алгоритм работает быстро, так как выполняется целиком на стороне сервера в рамках единого контекста генерации отчета.