Рассмотрим интересную ситуацию, с которой периодически сталкиваются разработчики при построении отчетов в 1С. Допустим, мы используем Систему Компоновки Данных (СКД) и в качестве источника данных передаем внешнюю таблицу значений. Эта таблица содержит колонку Клиент, в которой вместо привычной ссылки на справочник или строки хранится объект типа Структура (например, с ключами Наименование и Код). Возникает логичный вопрос: как мы можем использовать значения из этой структуры в полях выбора или отборах, обращаясь к ним в стиле Клиент.Наименование?
Проанализируем эту проблему детально, выясним причину такого поведения платформы и разберем эффективные способы решения данной задачи.
Если мы попытаемся в настройках СКД добавить в отбор поле Клиент.Код, система выдаст ошибку вида "Не найдено поле замены". Посмотрим на причины такого поведения платформы.
В 1С механизмы компоновки данных реализованы таким образом, чтобы полностью устранить неоднозначность типов. СКД умеет автоматически строить дерево дочерних полей только для тех объектов, которые жестко описаны в дереве метаданных конфигурации (справочники, документы, регистры) и имеют конкретные, заранее известные реквизиты. При настройке схемы компоновки платформа считывает метаданные и понимает, какие свойства есть у ссылочного типа.
Однако Структура, как и ТаблицаЗначений или Массив — это универсальные коллекции времени выполнения. У них нет постоянной типизированной схемы. В 1С нет "произвольных объектов" в том смысле, как это реализовано в некоторых других языках. СКД на этапе настройки просто не знает и не может знать, какие ключи существуют внутри объекта, переданного в поле. Следовательно, платформа не может развернуть их для пользователя. Таблица значений, передаваемая в СКД, должна содержать только те данные, которые четко описаны как "Тип значения" простого (Число, Строка, Дата, Булево) или ссылочного типа, либо УникальныйИдентификатор.
Разберем по шагам, как можно обойти это ограничение.
Наиболее правильным, производительным и надежным подходом при работе с внешними наборами данных считается предварительная подготовка исходной таблицы. Что нам мешает положить каждый реквизит в свою колонку? Вместо того чтобы передавать колонку с типом "Структура" и пытаться заставить СКД ее парсить, мы сами подготовим данные.
Клиент_Наименование с типом Строка и Клиент_Код с типом Число или Строка).Рассмотрим пример кода, как это может выглядеть перед инициализацией СКД:
// Предположим, ИсходнаяТаблица содержит колонку "Клиент" типа Структура
ИсходнаяТаблица.Колонки.Добавить("Клиент_Наименование", Новый ОписаниеТипов("Строка"));
ИсходнаяТаблица.Колонки.Добавить("Клиент_Код", Новый ОписаниеТипов("Строка"));
Для Каждого СтрокаТЗ Из ИсходнаяТаблица Цикл
Если ТипЗнч(СтрокаТЗ.Клиент) = Тип("Структура") Тогда
СтрокаТЗ.Клиент_Наименование = СтрокаТЗ.Клиент.Наименование;
СтрокаТЗ.Клиент_Код = СтрокаТЗ.Клиент.Код;
КонецЕсли;
КонецЦикла;
// Теперь в схеме СКД описываем поля Клиент_Наименование и Клиент_Код
Такой подход позволяет полноценно использовать эти поля в отборах, сортировках, группировках и условном оформлении. В самой СКД свойство внешнего набора данных должно содержать таблицу с колонками, строго совпадающими по именам и типам с полями набора данных.
Если по каким-то архитектурным причинам вы не можете или не хотите модифицировать исходную таблицу значений, мы можем использовать функционал вычисляемых полей СКД и экспортных функций общих модулей. Поскольку напрямую написать Клиент.Наименование нельзя, мы "попросим" общий модуль достать это значение.
Разберем, как это реализовать:
ОбщийМодульСКД (обязательно ставим галки "Сервер", "Вызов сервера").
Функция ПолучитьЗначениеКлюча(ДанныеСтруктура, ИмяКлюча) Экспорт
Если ТипЗнч(ДанныеСтруктура) = Тип("Структура") И ДанныеСтруктура.Свойство(ИмяКлюча) Тогда
Возврат ДанныеСтруктура[ИмяКлюча];
КонецЕсли;
Возврат Неопределено;
КонецФункции
Затем переходим в схему компоновки данных:
ИмяКлиента.ОбщийМодульСКД.ПолучитьЗначениеКлюча(Клиент, "Наименование").Важный технический нюанс! По умолчанию СКД в целях безопасности запрещает вызов произвольных внешних функций. Чтобы наш код сработал, при программном формировании отчета необходимо явно разрешить использование внешних функций. Для этого в метод Инициализировать() объекта ПроцессорКомпоновкиДанных в качестве четвертого параметра нужно передать значение Истина.
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
// 4-й параметр Истина разрешает внешние функции
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, ВнешниеНаборыДанных, ДанныеРасшифровки, Истина);
При использовании этого метода стоит помнить о производительности. Вызов функции из общего модуля для каждой строки отчета при большом объеме данных может существенно замедлить формирование. Поэтому первый вариант с предварительной подготовкой плоской таблицы всегда является предпочтительным.