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