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