Как параллельно вывести две табличные части в макет табличного документа 1С?

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

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

Анализ типичных ошибок при параллельном выводе строк

Проанализируем две распространенные ошибки, с которыми сталкиваются разработчики при попытке расположить две таблицы рядом.

1. Ошибочная логика работы с методом Присоединить(). Иногда пытаются вывести сначала все строки первой табличной части через ТабличныйДокумент.Вывести(), а затем в отдельном цикле добавить вторую таблицу через ТабличныйДокумент.Присоединить(). Такой подход не работает, так как каретка вывода в табличном документе после первого цикла уже смещена в самый низ. Метод Присоединить() прикрепляет область строго справа от последней выведенной строки, поэтому данные второй таблицы окажутся прижатыми к последней строке первой таблицы далеко внизу документа. Формирование документа должно происходить построчно.

2. Логическая ошибка в условии сравнения индекса. Посмотрим на фрагмент кода, где выполняется попытка синхронного обхода по индексу:


КолОрг = Объект.ПоДаннымОрганизации.Количество();
КолКонтр = Объект.ПоДаннымКонтрагента.Количество();
КоличествоЗаписей = Макс(КолОрг, КолКонтр);

Для Инд = 1 По КоличествоЗаписей Цикл
    Если КолОрг <= Инд Тогда
        // Заполнение параметров левой стороны
    КонецЕсли;
    // ...
КонецЦикла;

В приведенном условии допущена грубая ошибка: условие Если КолОрг <= Инд Тогда выполняется только тогда, когда счетчик цикла доходит до самого конца или превышает количество строк. В итоге данные попадают в область только на последней итерации, из-за чего в печатную форму выводится исключительно последняя строка. Корректная проверка при индексации с нуля выглядит как Если Инд < КолОрг Тогда.

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

Способ 1. Построчный вывод единой горизонтальной области (по индексам)

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

Рассмотрим код вывода:


КолОрг = Объект.ПоДаннымОрганизации.Количество();
КолКонтр = Объект.ПоДаннымКонтрагента.Количество();
МаксСтрок = Макс(КолОрг, КолКонтр);

Для Индекс = 0 По МаксСтрок - 1 Цикл
    
    // Получаем чистую область макета на каждой итерации
    ОбластьСтроки = Макет.ПолучитьОбласть("СтрокаТаблицы");
    
    // Проверяем, есть ли строка в первой табличной части
    Если Индекс < КолОрг Тогда
        СтрокаОрг = Объект.ПоДаннымОрганизации[Индекс];
        ОбластьСтроки.Параметры.Орг_Дата = СтрокаОрг.Дата;
        ОбластьСтроки.Параметры.Орг_Представление = СтрокаОрг.Представление;
        ОбластьСтроки.Параметры.Орг_Дебет = СтрокаОрг.Дебет;
        ОбластьСтроки.Параметры.Орг_Кредит = СтрокаОрг.Кредит;
    КонецЕсли;
    
    // Проверяем, есть ли строка во второй табличной части
    Если Индекс < КолКонтр Тогда
        СтрокаКонтр = Объект.ПоДаннымКонтрагента[Индекс];
        ОбластьСтроки.Параметры.Контр_Дата = СтрокаКонтр.Дата;
        ОбластьСтроки.Параметры.Контр_Представление = СтрокаКонтр.Представление;
        ОбластьСтроки.Параметры.Контр_Дебет = СтрокаКонтр.Дебет;
        ОбластьСтроки.Параметры.Контр_Кредит = СтрокаКонтр.Кредит;
    КонецЕсли;
    
    // Выводим готовую объединенную строку
    ТабличныйДокумент.Вывести(ОбластьСтроки);
    
КонецЦикла;

В этом примере мы за один проход проверяем наличие данных в обеих табличных частях по текущему номеру строки и выводим полностью сформированную строку в ТабличныйДокумент.

Способ 2. Построчное использование методов Вывести() и Присоединить()

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

В макете на пересечении строк и колонок задаются области: Строка|Лево и Строка|Право.

Посмотрим на пример реализации:


КолОрг = Объект.ПоДаннымОрганизации.Количество();
КолКонтр = Объект.ПоДаннымКонтрагента.Количество();
МаксСтрок = Макс(КолОрг, КолКонтр);

Для Индекс = 0 По МаксСтрок - 1 Цикл
    
    // 1. Формируем левую сторону
    ОбластьЛево = Макет.ПолучитьОбласть("Строка|Лево");
    Если Индекс < КолОрг Тогда
        СтрокаОрг = Объект.ПоДаннымОрганизации[Индекс];
        ОбластьЛево.Параметры.Заполнить(СтрокаОрг);
    КонецЕсли;
    ТабличныйДокумент.Вывести(ОбластьЛево);
    
    // 2. Формируем правую сторону и присоединяем ее справа от только что выведенной левой
    ОбластьПраво = Макет.ПолучитьОбласть("Строка|Право");
    Если Индекс < КолКонтр Тогда
        СтрокаКонтр = Объект.ПоДаннымКонтрагента[Индекс];
        ОбластьПраво.Параметры.Заполнить(СтрокаКонтр);
    КонецЕсли;
    ТабличныйДокумент.Присоединить(ОбластьПраво);
    
КонецЦикла;

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

Способ 3. Предварительное объединение данных в одну таблицу значений

Чтобы исключить сложную логику и условные операторы непосредственно в момент вывода документа, данные можно свести в единую вспомогательную таблицу значений перед формированием макета.

  1. Создаем программную таблицу значений, содержащую поля обеих табличных частей.
  2. Добавляем в нее строки в количестве, равном максимальному числу строк из двух табличных частей.
  3. Последовательно заполняем поля из первой и второй табличных частей.
  4. Выводим данные стандартным циклом Для Каждого без дополнительных проверок индексов.

Рассмотрим пример кода:


ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Орг_Дата");
ТЗ.Колонки.Добавить("Орг_Представление");
ТЗ.Колонки.Добавить("Орг_Дебет");
ТЗ.Колонки.Добавить("Контр_Дата");
ТЗ.Колонки.Добавить("Контр_Представление");
ТЗ.Колонки.Добавить("Контр_Дебет");

КолОрг = Объект.ПоДаннымОрганизации.Количество();
КолКонтр = Объект.ПоДаннымКонтрагента.Количество();
МаксСтрок = Макс(КолОрг, КолКонтр);

// Создаем пустые строки
Для Счетчик = 1 По МаксСтрок Цикл
    ТЗ.Добавить();
КонецЦикла;

// Заполняем данные первой ТЧ
Для НомерСтроки = 0 По КолОрг - 1 Цикл
    СтрокаИсточника = Объект.ПоДаннымОрганизации[НомерСтроки];
    СтрокаПриемника = ТЗ[НомерСтроки];
    СтрокаПриемника.Орг_Дата = СтрокаИсточника.Дата;
    СтрокаПриемника.Орг_Представление = СтрокаИсточника.Представление;
    СтрокаПриемника.Орг_Дебет = СтрокаИсточника.Дебет;
КонецЦикла;

// Заполняем данные второй ТЧ
Для НомерСтроки = 0 По КолКонтр - 1 Цикл
    СтрокаИсточника = Объект.ПоДаннымКонтрагента[НомерСтроки];
    СтрокаПриемника = ТЗ[НомерСтроки];
    СтрокаПриемника.Контр_Дата = СтрокаИсточника.Дата;
    СтрокаПриемника.Контр_Представление = СтрокаИсточника.Представление;
    СтрокаПриемника.Контр_Дебет = СтрокаИсточника.Дебет;
КонецЦикла;

// Выводим сформированную таблицу в документ
ОбластьСтроки = Макет.ПолучитьОбласть("СтрокаТаблицы");
Для Каждого СтрокаТЗ Из ТЗ Цикл
    ОбластьСтроки.Параметры.Заполнить(СтрокаТЗ);
    ТабличныйДокумент.Вывести(ОбластьСтроки);
КонецЦикла;

Способ 4. Объединение через запрос с автонумерацией

Если исходные данные можно поместить в запрос, для сопоставления строк используется ПОЛНОЕ СОЕДИНЕНИЕ по полю НомерСтроки.

Разберем логику построения такого запроса:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    ТЧ_Орг.НомерСтроки КАК НомерСтроки,
    |    ТЧ_Орг.Дата КАК Орг_Дата,
    |    ТЧ_Орг.Представление КАК Орг_Представление,
    |    ТЧ_Орг.Дебет КАК Орг_Дебет
    |ПОМЕСТИТЬ ВТ_Орг
    |ИЗ
    |    &ТЧ_Организация КАК ТЧ_Орг
    |;
    |
    |ВЫБРАТЬ
    |    ТЧ_Контр.НомерСтроки КАК НомерСтроки,
    |    ТЧ_Контр.Дата КАК Контр_Дата,
    |    ТЧ_Контр.Представление КАК Контр_Представление,
    |    ТЧ_Контр.Дебет КАК Контр_Дебет
    |ПОМЕСТИТЬ ВТ_Контр
    |ИЗ
    |    &ТЧ_Контрагент КАК ТЧ_Контр
    |;
    |
    |ВЫБРАТЬ
    |    ВТ_Орг.Орг_Дата КАК Орг_Дата,
    |    ВТ_Орг.Орг_Представление КАК Орг_Представление,
    |    ВТ_Орг.Орг_Дебет КАК Орг_Дебет,
    |    ВТ_Контр.Контр_Дата КАК Контр_Дата,
    |    ВТ_Контр.Контр_Представление КАК Контр_Представление,
    |    ВТ_Контр.Контр_Дебет КАК Контр_Дебет
    |ИЗ
    |    ВТ_Орг КАК ВТ_Орг
    |        ПОЛНОЕ СОЕДИНЕНИЕ ВТ_Контр КАК ВТ_Контр
    |        ПО ВТ_Орг.НомерСтроки = ВТ_Контр.НомерСтроки";

Запрос.УстановитьПараметр("ТЧ_Организация", Объект.ПоДаннымОрганизации.Выгрузить());
Запрос.УстановитьПараметр("ТЧ_Контрагент", Объект.ПоДаннымКонтрагента.Выгрузить());

Выборка = Запрос.Выполнить().Выбрать();

ОбластьСтроки = Макет.ПолучитьОбласть("СтрокаТаблицы");
Пока Выборка.Следующий() Цикл
    ОбластьСтроки.Параметры.Заполнить(Выборка);
    ТабличныйДокумент.Вывести(ОбластьСтроки);
КонецЦикла;

Такой вариант исключает необходимость ручного расчета максимального числа итераций и упрощает дальнейший вывод строк в отчет.

← На главную