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