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