При разработке внешних печатных форм (ВПФ) в среде 1С:Предприятие 8.3 (особенно в конфигурациях на базе БСП, таких как ЗУП 3.1, ERP или КА 2) разработчики часто сталкиваются с ситуацией, когда стандартный механизм печати «склеивает» все документы в один или выводит только последний сформированный документ. Рассмотрим ситуацию: в документе «Работа в выходной день» указано 20 сотрудников, и для каждого нужно сформировать отдельное уведомление, которое должно открыться в собственном окне или быть отправлено отдельным файлом по e-mail.
В этой статье мы подробно разберем, почему стандартный цикл в процедуре Печать не дает нужного результата, и изучим несколько способов решения этой задачи.
Проанализируем типичную ошибку. В модуле внешней печатной формы разработчик пишет следующий код:
Процедура Печать(МассивОбъектов, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт
Для каждого СсылкаДокумента Из МассивОбъектов Цикл
Для каждого СтрокаСотрудника Из СсылкаДокумента.Сотрудники Цикл
ТабДок = СформироватьУведомление(СсылкаДокумента, СтрокаСотрудника.Сотрудник);
// Ошибка здесь:
УправлениеПечатью.ВывестиТабличныйДокументВКоллекцию(КоллекцияПечатныхФорм,
"Уведомление", "Уведомление о работе", ТабДок);
КонецЦикла;
КонецЦикла;
КонецПроцедуры
Разберем причину неудачи. Процедура ВывестиТабличныйДокументВКоллекцию ищет в структуре КоллекцияПечатныхФорм строку с именем «Уведомление». Если такая строка уже есть, система либо перезаписывает её (в зависимости от версии БСП), либо дополняет существующую. В итоге на клиент возвращается коллекция, содержащая одну-единственную печатную форму, в которую «влиты» все данные, либо только данные последнего сотрудника. Более того, штатное окно предварительного просмотра БСП (форма ПечатьДокументов) спроектировано так, чтобы отображать содержимое коллекции в одном общем окне.
Если ваша основная задача — распечатать уведомления на принтере, но так, чтобы каждый сотрудник начинался с нового листа, проще всего использовать один табличный документ, вставляя разделители. Это самый экономный с точки зрения ресурсов сервера способ. При необходимости можно использовать печатную форму для вывода каждого сотрудника списочного документа на отдельный лист.
Проанализируем алгоритм:
ТабличныйДокумент.Вывести.ВывестиГоризонтальныйРазделительСтраниц().
// Внутри процедуры Печать
РезультирующийТабДок = Новый ТабличныйДокумент;
Для каждого СтрокаСотрудника Из СсылкаДокумента.Сотрудники Цикл
ТекущийУчедомление = СформироватьУведомление(СсылкаДокумента, СтрокаСотрудника.Сотрудник);
// Если в документе уже есть текст, добавляем разрыв страницы
Если РезультирующийТабДок.ВысотаТаблицы > 0 Тогда
РезультирующийТабДок.ВывестиГоризонтальныйРазделительСтраниц();
КонецЕсли;
РезультирующийТабДок.Вывести(ТекущийУчедомление);
КонецЦикла;
УправлениеПечатью.ВывестиТабличныйДокументВКоллекцию(КоллекцияПечатныхФорм,
"Уведомление", "Уведомления (все сотрудники)", РезультирующийТабДок);
Как правильно было замечено в обсуждении, метод Показать() доступен только на клиенте. Чтобы открыть 20 разных окон, нам нужно обойти стандартный механизм возврата коллекции в БСП. Рассмотрим, как это реализовать.
Для этого в состав внешней обработки нужно включить форму. В этой форме мы создадим команду, которая будет выполнять следующие действия:
ТабличныйДокумент для конкретного сотрудника.ТабДок.Показать("Уведомление: " + ФИО).Пример клиентского кода в форме обработки:
&НаКлиенте
Процедура КомандаПечатьВРазныеОкна(Команда)
Для каждого СотрудникСсылка Из МассивВыбранныхСотрудников Цикл
// Получаем документ с сервера
ТабДок = СформироватьПечатнуюФормуНаСервере(СотрудникСсылка);
// Открываем отдельное окно для каждого
ТабДок.ТолькоПросмотр = Истина;
ТабДок.ОтображатьСетку = Ложь;
ТабДок.Показать("Уведомление для " + СотрудникСсылка);
КонецЦикла;
КонецПроцедуры
Важно: этот метод не рекомендуется использовать для большого количества сотрудников (более 10-15), так как создание большого количества окон может привести к значительному потреблению оперативной памяти клиентским приложением и зависанию интерфейса.
Выясним, как реализовать задачу автора сообщения: не просто показать, а отправить по e-mail. Если открывать каждое окно вручную для отправки, это займет слишком много времени. БСП предоставляет инструменты для автоматизации этого процесса.
Выясним, как использовать функцию УправлениеПечатью.СформироватьПечатныеФормыДляОтправкиПоЭлектроннойПочте. Рассмотрим шаги:
ТабличныйДокумент.РаботаСПочтовымиСообщениями.Пример кода для сохранения во временные файлы на сервере:
// В цикле по сотрудникам на сервере
ИмяФайла = ПолучитьИмяВременногоФайла("pdf");
ТабДок.Записать(ИмяФайла, ТипФайлаТабличногоДокумента.PDF);
// Далее работаем с файлом: прикрепляем к письму или сохраняем в папку
ДвоичныеДанныеФайла = Новый ДвоичныеДанные(ИмяФайла);
// Отправка...
УдалитьФайлы(ИмяФайла);
Разберем еще один «хитрый» способ. Если мы хотим, чтобы БСП все же открыла одно окно, но внутри него документы были разделены (в некоторых версиях это выглядит как дерево документов слева), мы можем попробовать генерировать уникальные имена макетов в коллекции.
Вместо фиксированного имени "Уведомление", используем "Уведомление_" + Строка(НомерСотрудника). Однако помните, что вызывающая процедура (в модуле формы документа) должна знать об этих именах, чтобы корректно их обработать. В стандартных ВПФ это реализовать сложно без изменения общего кода подсистемы печати, поэтому данный метод лучше применять в собственных самописных механизмах. Для изменения логики типовой подсистемы печати без модификации конфигурации можно применить решение для постобработки печатных форм БСП через расширение.
Проанализировав ситуацию, мы можем подвести итоги:
ТабличныйДокумент с методом ВывестиГоризонтальныйРазделительСтраниц().Показать(), вынеся логику из стандартного механизма ВПФ в отдельную форму.Помните, что передача больших ТабличныйДокумент между сервером и клиентом в цикле — ресурсоемкая операция. Старайтесь оптимизировать запросы и формировать данные пакетами, где это возможно.