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