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