При доработке сложных отчетов на системе компоновки данных (СКД) (поможет встроенная консоль СКД для отладки решений) часто возникает задача обогащения данных из запроса дополнительной информацией, полученной программно. Например, мы хотим присоединить к результату запроса Таблицу Значений (ТЗ), сформированную сложным алгоритмом. Казалось бы, механизм "Внешний набор данных" создан именно для этого. Однако на практике разработчики сталкиваются с ситуацией, когда связь настроена, ошибок нет, количество строк верное, но добавленные поля выводятся пустыми.
В этой статье мы подробно разберем, почему так происходит, выясним фундаментальную разницу между Схемой и Макетом компоновки, а также рассмотрим специфику работы в типовых конфигурациях на базе БСП (например, "Зарплата и Управление Персоналом"), которая часто сводит на нет стандартные методы решения, в отличие от гибких инструментов вроде Пользовательской СКД.
Рассмотрим типичную ситуацию. Разработчик пытается доработать существующий отчет. В событии ПриКомпоновкеДанных (или ПриКомпоновкеРезультата) выполняется следующий алгоритм:
ТаблицаЗначений.Пример кода, который часто пишут ошибочно (обращаясь к Макету, а не Схеме):
// ОШИБОЧНЫЙ ПОДХОД
НоваяСвязь = МакетКомпоновкиДанных.СвязиНаборовДанных.Добавить();
НоваяСвязь.ВыражениеИсточник = "НачисленияУдержанияОбъединенные.Сотрудник";
НоваяСвязь.ВыражениеПриемник = "ВнешнийНаборДанных.ПолеДляСоединения";
// ... настройки наборов ...
В результате, при формировании отчета, строки выводятся, но колонки из внешнего источника абсолютно пустые. При этом отладчик показывает, что Таблица Значений заполнена корректно, и типы данных совпадают. Также полезна будет методика скрытия пустых столбцов, когда данных действительно нет.
Чтобы понять причину проблемы, нужно четко разделять два понятия в архитектуре СКД, которые часто путают новички (иногда в одном отчете могут даже сосуществовать две разные схемы):
КомпоновщикаМакета. Это техническое задание для процессора компоновки. В макете логические имена часто заменяются на внутренние псевдонимы (например, П1, П2), а структура оптимизируется для выполнения.В обсуждаемой теме проблема заключалась в том, что разработчик пытался добавить набор данных непосредственно в Макет (НаборДанныхОбъектМакетаКомпоновкиДанных), минуя Схему. Для упрощения таких манипуляций можно использовать библиотеки-обертки над СКД.
Почему это приводит к пустым полям? Когда мы добавляем набор данных в Схему, Компоновщик Макета при генерации макета автоматически создает соответствие между именами полей схемы и колонками внешнего источника данных. Если же вы добавляете набор сразу в Макет, этого автоматического связывания не происходит. Процессор компоновки видит набор, видит поля, но не знает, из какой именно колонки переданной Таблицы Значений брать данные для конкретного поля макета, так как метаданные связи отсутствуют.
Вывод: Модифицировать структуру наборов данных и полей нужно на уровне Схемы, до того как будет вызван метод КомпоновщикМакета.Выполнить().
Прежде чем переходить к решению проблемы с типовыми конфигурациями, убедимся, что сама Таблица Значений создана корректно. СКД — система строго типизированная.
Если вы связываете наборы по полю Сотрудник, то в вашей ТЗ колонка должна иметь тип СправочникСсылка.Сотрудники. Если тип не указан (составной тип, произвольный) или не совпадает, связь не сработает.
Правильный пример создания ТЗ:
ТЗ = Новый ТаблицаЗначений;
// Обязательно указываем типы!
ТЗ.Колонки.Добавить("ПолеДляСоединения", Новый ОписаниеТипов("СправочникСсылка.Сотрудники"));
ТЗ.Колонки.Добавить("ПолеТест", Новый ОписаниеТипов("Число"));
// Заполнение данными
НоваяСтрока = ТЗ.Добавить();
НоваяСтрока.ПолеДляСоединения = СсылкаНаСотрудника;
НоваяСтрока.ПолеТест = 100;
При добавлении полей в Схему (если мы делаем это программно), также важно указывать тип значения:
НовоеПоле = ВнешнийНабор.Поля.Добавить(Тип("ПолеНабораДанныхСхемыКомпоновкиДанных"));
НовоеПоле.Заголовок = "Тестовое поле";
НовоеПоле.Поле = "ПолеТест";
НовоеПоле.ПутьКДанным = "ПолеТест";
НовоеПоле.ТипЗначения = Новый ОписаниеТипов("Число"); // Критически важно
В ходе обсуждения выяснилась главная причина, почему автор полез менять Макет, а не Схему. В типовых конфигурациях (особенно построенных на базе Библиотеки Стандартных Подсистем и кадровых модулях) существует общий механизм формирования отчетов.
Когда вы открываете отчет и нажимаете "Сформировать", событие ПриКомпоновкеРезультата часто обращается к общим модулям (например, ЗарплатаКадрыОтчеты). Эти модули для обеспечения консистентности настроек могут заново инициализировать схему компоновки, сбрасывая все программные изменения, которые вы сделали в начале процедуры.
Сценарий провала:
СхемаКомпоновкиДанных.Существует два основных способа решения этой задачи. Проанализируем их.
Самый надежный способ внедриться в процесс формирования отчета в современной платформе 1С — это использование Расширений конфигурации. Вместо того чтобы пытаться "протиснуться" в модуле самого отчета до вызова общих процедур, нужно переопределить поведение общего модуля, который управляет компоновкой.
Если типовой механизм формирования отчета находится в общем модуле (например, ЗарплатаКадрыОтчеты), необходимо создать расширение и перекрыть соответствующий метод (используя аннотацию &Вместо или &Посли, если это возможно), добавив туда логику модификации схемы.
Это позволяет "подсунуть" измененную Схему (с добавленным НаборДанныхОбъект) непосредственно перед тем, как БСП начнет создавать Макет компоновки.
Если использование расширений невозможно или нежелательно, необходимо полностью отключить стандартную обработку и реализовать весь цикл вывода отчета самостоятельно. Это дает полный контроль над Схемой.
Рассмотрим пошаговый алгоритм такого решения:
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
// 1. Отключаем стандартный механизм, чтобы он не затер наши изменения
СтандартнаяОбработка = Ложь;
// 2. Получаем схему
Схема = ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
// 3. Программно модифицируем СХЕМУ (не макет!)
// Добавляем внешний набор данных
НаборТЗ = Схема.НаборыДанных.Добавить(Тип("НаборДанныхОбъектСхемыКомпоновкиДанных"));
НаборТЗ.Имя = "ВнешнийНаборДанных";
НаборТЗ.ИмяОбъекта = "ВнешняяТаблица"; // Имя ключа структуры для внешних наборов
// Добавляем поле в набор
ПолеНабора = НаборТЗ.Поля.Добавить(Тип("ПолеНабораДанныхСхемыКомпоновкиДанных"));
ПолеНабора.Поле = "Сотрудник";
ПолеНабора.ПутьКДанным = "Сотрудник";
ПолеНабора.ТипЗначения = Новый ОписаниеТипов("СправочникСсылка.Сотрудники");
// Настраиваем связи наборов данных в СХЕМЕ
Связь = Схема.СвязиНаборовДанных.Добавить();
Связь.НаборДанныхИсточник = "НачисленияУдержанияОбъединенные"; // Имя существующего набора
Связь.НаборДанныхПриемник = "ВнешнийНаборДанных";
Связь.ВыражениеИсточник = "Сотрудник";
Связь.ВыражениеПриемник = "Сотрудник";
// 4. Подготавливаем внешние данные
ТЗ = ПолучитьТаблицуДанных(); // Ваша функция получения ТЗ
ВнешниеНаборы = Новый Структура;
ВнешниеНаборы.Вставить("ВнешняяТаблица", ТЗ); // Ключ совпадает с ИмяОбъекта набора
// 5. Применяем настройки пользователя (отборы и т.д.)
Настройки = КомпоновщикНастроек.ПолучитьНастройки();
// 6. Компонуем МАКЕТ
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(Схема, Настройки, ДанныеРасшифровки);
// 7. Выполняем компоновку
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, ВнешниеНаборы, ДанныеРасшифровки);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
КонецПроцедуры
СхемаКомпоновкиДанных.ТаблицаЗначений и в описании полей внешнего набора должны совпадать с типами полей, по которым идет соединение.СтандартнаяОбработка = Ложь.ИмяОбъекта в наборе данных схемы должно совпадать с ключом структуры, которую вы передаете в метод ПроцессорКомпоновки.Инициализировать().Следуя этим правилам, вы сможете корректно обогащать любые типовые отчеты данными из внешних источников, избегая ошибок с пустыми полями.