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