Как в СКД вывести две независимые табличные части одного документа горизонтально («таблица в таблице»)?

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

При разработке сложных аналитических и регламентированных отчетов в 1С программисты часто сталкиваются со следующей задачей: необходимо вывести данные одного документа (или контрагента), у которого есть две или более независимые табличные части. Классический пример — вывод информации по документу продажи, где параллельно в одной строке должны отображаться списки отгруженных товаров и оказанных услуг.

Если попытаться связать такие таблицы простым соединением в запросе, возникнет так называемое декартово произведение: строки табличных частей перемножатся между собой. В результате вместо аккуратных параллельных списков мы получим избыточную таблицу, где на каждую строчку товара продублируются все услуги. Нам же требуется красивый горизонтальный вывод: таблица товаров слева, таблица услуг справа, при этом они не должны дублировать друг друга.

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

Способ 1. Соединение по виртуальному номеру строки в запросе

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

Идея заключается в том, чтобы пронумеровать строки каждой табличной части по порядку для каждого документа (начиная с 1), а затем связать их по полному соединению по двум полям: Ссылка (Документ) и НомерСтроки.

Разберем по шагам, как построить такой запрос:

  1. Помещаем первую табличную часть (например, товары) во временную таблицу и нумеруем её строки.
  2. Помещаем вторую табличную часть (например, услуги) в другую временную таблицу и также нумеруем её строки.
  3. Соединяем эти таблицы по условию равенства документов и номеров строк.

Для генерации порядкового номера строки в запросе используется классическое левое соединение таблицы самой с собой. Рассмотрим пример реализации такого запроса:


// Шаг 1: Получаем товары с автонумерацией строк
ВЫБРАТЬ
    ТЧТовары.Ссылка КАК Ссылка,
    ТЧТовары.Номенклатура КАК Товар,
    ТЧТовары.Количество КАК КоличествоТовара,
    ТЧТовары.Сумма КАК СуммаТовара,
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ТЧТоварыДубль.НомерСтроки) КАК НомерСтрокиДляСвязи
ПОМЕСТИТЬ ВТ_Товары
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК ТЧТовары
    ЛЕВОЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг.Товары КАК ТЧТоварыДубль
        ПО ТЧТовары.Ссылка = ТЧТоварыДубль.Ссылка
        И ТЧТовары.НомерСтроки >= ТЧТоварыДубль.НомерСтроки
СГРУППИРОВАТЬ ПО
    ТЧТовары.Ссылка,
    ТЧТовары.Номенклатура,
    ТЧТовары.Количество,
    ТЧТовары.Сумма
;

// Шаг 2: Получаем услуги с автонумерацией строк
ВЫБРАТЬ
    ТЧУслуги.Ссылка КАК Ссылка,
    ТЧУслуги.Номенклатура КАК Услуга,
    ТЧУслуги.Количество КАК КоличествоУслуг,
    ТЧУслуги.Сумма КАК СуммаУслуг,
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ТЧУслугиДубль.НомерСтроки) КАК НомерСтрокиДляСвязи
ПОМЕСТИТЬ ВТ_Услуги
ИЗ
    Документ.РеализацияТоваровУслуг.Услуги КАК ТЧУслуги
    ЛЕВОЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг.Услуги КАК ТЧУслугиДубль
        ПО ТЧУслуги.Ссылка = ТЧУслугиДубль.Ссылка
        И ТЧУслуги.НомерСтроки >= ТЧУслугиДубль.НомерСтроки
СГРУППИРОВАТЬ ПО
    ТЧУслуги.Ссылка,
    ТЧУслуги.Номенклатура,
    ТЧУслуги.Количество,
    ТЧУслуги.Сумма
;

// Шаг 3: Полное соединение табличных частей по сгенерированному номеру строки
ВЫБРАТЬ
    ЕСТЬNULL(ВТ_Товары.Ссылка, ВТ_Услуги.Ссылка) КАК Ссылка,
    ЕСТЬNULL(ВТ_Товары.НомерСтрокиДляСвязи, ВТ_Услуги.НомерСтрокиДляСвязи) КАК ПорядковыйНомер,
    ВТ_Товары.Товар КАК Товар,
    ВТ_Товары.КоличествоТовара КАК КоличествоТовара,
    ВТ_Товары.СуммаТовара КАК СуммаТовара,
    ВТ_Услуги.Услуга КАК Услуга,
    ВТ_Услуги.КоличествоУслуг КАК КоличествоУслуг,
    ВТ_Услуги.СуммаУслуг КАК СуммаУслуг
ИЗ
    ВТ_Товары КАК ВТ_Товары
    ПОЛНОЕ СОЕДИНЕНИЕ ВТ_Услуги КАК ВТ_Услуги
        ПО ВТ_Товары.Ссылка = ВТ_Услуги.Ссылка
        И ВТ_Товары.НомерСтрокиДляСвязи = ВТ_Услуги.НомерСтрокиДляСвязи

При таком подходе СКД выведет строки параллельно. Если в товарах у нас 5 строк, а в услугах — всего 2, то в итоговом отчете первые 2 строки будут содержать информацию и по товару, и по услуге, а последующие 3 строки будут содержать данные только по товарам (в колонках услуг отобразятся пустые значения).

Способ 2. Использование механизма вложенных отчетов в СКД

Для сложных печатных форм, структура которых жестко регламентирована, отлично подходит архитектура Вложенных схем компоновки данных. Именно по такому принципу реализован вывод многотабличных структур в типовых конфигурациях 1С (например, печать Расчетного листка в ЗУП).

Выполним настройку по шагам:

  1. В основной схеме компоновки данных создаем набор данных, который выбирает только "шапку" (например, Список Документов и Контрагентов).
  2. Переходим на вкладку Настройки. Добавляем группировку по документу.
  3. Внутри этой группировки вместо обычных детальных записей нажимаем ПКМ и выбираем команду Новый вложенный отчет.
  4. Для вложенного отчета настраивается отдельная схема компоновки данных, где источником данных служат только товары или только услуги.
  5. На вкладке Связи наборов данных основной схемы связываем параметры вложенного отчета с текущим полем Ссылка родительской схемы.

Этот способ позволяет разместить полноценные таблицы внутри ячеек родительского отчета. Система компоновки данных самостоятельно позаботится об отрисовке границ, рамок и итогов для каждой подчиненной таблицы.

Способ 3. Перехват компоновки и программный вывод в Табличный Документ

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

Для реализации этого метода мы переопределяем событие ПриКомпоновкеРезультата в модуле отчета. Процесс выглядит следующим образом:

  1. Отключаем стандартный вывод отчета, устанавливая параметр СтандартнаяОбработка = Ложь.
  2. Выгружаем результат работы процессора компоновки во временное ДеревоЗначений или ТаблицуЗначений.
  3. Программно обходим полученную структуру и выводим области в ТабличныйДокумент, используя методы Вывести() и Присоединить().

Посмотрим на пример шаблона кода в модуле отчета:


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

КонецПроцедуры

Использование метода Присоединить() позволяет динамически стыковать ячейки по горизонтали. Таким образом, даже если таблицы содержат разное количество строк, итоговый результат будет выглядеть монолитно и профессионально.

Вывод и рекомендации

Выбор конкретного решения зависит от требований к отчету и имеющегося времени на разработку:

← На главную