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