При разработке отчетов на системе компоновки данных (СКД) программисты часто сталкиваются с нестандартными требованиями бизнеса. Типичный пример — необходимость вывести в одном отчете две таблицы, которые логически связаны друг с другом, но имеют разные настройки фильтрации. Рассмотрим сложную, но жизненную задачу: построить отчет по закупкам, состоящий из двух раздельных таблиц.
Первая таблица должна выводить данные по продажам или закупкам за определенный период (Период 1) с отбором по конкретному контрагенту (или списку контрагентов), выбранному пользователем.
Вторая таблица должна выводить информацию за другой период (Период 2) по всем остальным контрагентам (которые не попали в первую таблицу). При этом номенклатурный состав второй таблицы должен строго ограничиваться только теми позициями, которые фигурировали в первой таблице.
Сложность задачи заключается в том, что стандартные механизмы отборов СКД применяются ко всей схеме глобально. Если пользователь установит отбор по контрагенту "Клиент А", то этот отбор по умолчанию применится ко всему отчету, и мы не сможем вывести во второй таблице данные по остальным контрагентам. Разберем по шагам несколько профессиональных способов реализации этой задачи в среде 1С:Предприятие 8.3.
Этот способ является наиболее изящным и декларативным, так как он практически не требует написания кода в модуле объекта и полностью полагается на штатные возможности процессора компоновки данных. Мы разделим логику получения данных на два отдельных набора данных (запроса) и свяжем их между собой.
Проанализируем структуру первого набора данных (НаборДанных1). Этот запрос будет получать данные по продажам за Период 1. В тексте запроса мы используем стандартные параметры для ограничения периода и наложения отбора по контрагенту:
ВЫБРАТЬ
ПродажиОбороты.Контрагент КАК Контрагент,
ПродажиОбороты.Номенклатура КАК Номенклатура,
ПродажиОбороты.КоличествоОборот КАК Количество,
ПродажиОбороты.СуммаВыручкиОборот КАК Сумма
ИЗ
РегистрНакопления.Продажи.Обороты(&НачалоПериода1, &КонецПериода1, Авто, Контрагент В (&ВыбранныеКонтрагенты)) КАК ПродажиОбороты
Теперь спроектируем второй набор данных (НаборДанных2). Ему предстоит выбирать данные за Период 2 по всем контрагентам, кроме выбранных, и только по той номенклатуре, которая была куплена в первом периоде. Запрос для второго набора будет выглядеть следующим образом:
ВЫБРАТЬ
ПродажиОбороты.Контрагент КАК КонтрагентПрочие,
ПродажиОбороты.Номенклатура КАК Номенклатура,
ПродажиОбороты.КоличествоОборот КАК КоличествоПрочие,
ПродажиОбороты.СуммаВыручкиОборот КАК СуммаПрочие
ИЗ
РегистрНакопления.Продажи.Обороты(&НачалоПериода2, &КонецПериода2, Авто, НЕ Контрагент В (&ВыбранныеКонтрагенты) И Номенклатура = &СвязьНоменклатура) КАК ПродажиОбороты
Обратите внимание на параметр &СвязьНоменклатура. Мы не будем заполнять его вручную. Перейдем на вкладку "Связи наборов данных" в конструкторе схемы компоновки и добавим новую связь:
НаборДанных1НаборДанных2НоменклатураНоменклатураСвязьНоменклатураБлагодаря этой связи СКД сама выполнит первый запрос, получит список уникальной номенклатуры, а затем порционно передаст этот список во второй запрос через параметр связи. В настройках компоновки мы создаем две отдельные таблицы. В первой таблице выводим поля из первого набора данных, во второй — из второго. Периоды и списки контрагентов будут регулироваться независимо через параметры схемы.
Если для конечного пользователя критически важно использовать стандартное окно отборов (компоновщик настроек) вместо выбора параметров на форме, нам придется прибегнуть к программному коду в модуле объекта отчета. Наша задача — перехватить отбор, установленный пользователем, использовать его для первой таблицы, а для второй таблицы инвертировать его и передать в качестве параметра запроса.
Выясним причину, почему это нельзя сделать стандартно: СКД применяет элементы коллекции КомпоновщикНастроек.Настройки.Отбор ко всем наборам данных сразу, если не указаны специфические ограничения. Мы программно отключим применение стандартного отбора ко второму набору данных и передадим список исключений через параметр.
Разберем по шагам алгоритм в процедуре ПриКомпоновкеРезультата модуля объекта отчета:
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
// Получаем текущие настройки пользователя
Настройки = КомпоновщикНастроек.ПолучитьНастройки();
// Найдем пользовательский отбор по контрагенту
СписокВыбранныхКонтрагентов = Новый СписокЗначений;
ЭлементОтбораКонтрагент = Неопределено;
Для Каждого ЭлементОтбора Из Настройки.Отбор.Элементы Цикл
Если ТипЗнч(ЭлементОтбора) = Тип("ЭлементОтбораКомпоновкиДанных") И ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Контрагент") Тогда
Если ЭлементОтбора.Использование Тогда
ЭлементОтбораКонтрагент = ЭлементОтбора;
Если ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.ВСписке Тогда
СписокВыбранныхКонтрагентов = ЭлементОтбора.ПравоеЗначение;
ИначеЕсли ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно Тогда
СписокВыбранныхКонтрагентов.Добавить(ЭлементОтбора.ПравоеЗначение);
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЦикла;
// Установим параметр для исключения контрагентов во второй таблице
Если СписокВыбранныхКонтрагентов.Количество() > 0 Тогда
Настройки.ПараметрыДанных.УстановитьЗначениеПараметра("ИсключаемыеКонтрагенты", СписокВыбранныхКонтрагентов);
Настройки.ПараметрыДанных.УстановитьЗначениеПараметра("ЕстьИсключения", Истина);
Иначе
Настройки.ПараметрыДанных.УстановитьЗначениеПараметра("ЕстьИсключения", Ложь);
КонецЕсли;
// Выполняем компоновку с измененными настройками
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки, ДанныеРасшифровки);
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки, Истина);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
КонецПроцедуры
В самом запросе СКД для второй таблицы мы прописываем условие исключения, ориентируясь на переданный параметр:
ГДЕ
ВЫБОР
КОГДА &ЕстьИсключения
ТОГДА НЕ Контрагент В (&ИсключаемыеКонтрагенты)
ИНАЧЕ ИСТИНА
КОНЕЦ
Этот подход гарантирует, что пользователь может интерактивно использовать привычный интерфейс отборов 1С, а разработчик полностью контролирует поведение логики исключения на уровне кода платформы.
Проанализируем еще один мощный архитектурный шаблон 1С — вложенные отчеты внутри одной схемы компоновки. Этот метод идеально подходит, если первая и вторая таблицы представляют собой концептуально разные структуры данных, которые тяжело объединить в рамках одного запроса или одного набора данных.
Разберем процесс настройки по шагам:
СхемаТаблица1 и СхемаТаблица2.СхемаТаблица1 настраиваем обычный запрос по продажам с параметрами начала и окончания периода, а также стандартным отбором по контрагенту.СхемаТаблица2 создаем аналогичный запрос, но с условием инверсии отбора (через параметр &ИсключаемыеКонтрагенты, как в Варианте 2).Преимуществом данного подхода является абсолютная изолированность логики построения таблиц. Вы можете редактировать макет оформления, группировки и формулы итогов для каждой таблицы независимо, не опасаясь нарушить работу смежной таблицы отчета.
Для того чтобы выбрать наиболее подходящий метод для вашей задачи, сопоставим рассмотренные варианты решений:
Рассмотрев все варианты, мы можем сделать вывод, что выбор конкретного метода зависит от квалификации разработчика и жесткости требований заказчика к интерфейсу настроек отчета. Использование связей наборов данных в сочетании с параметризацией является наиболее технологичным и производительным решением в рамках платформы 1С:Предприятие 8.3.