При разработке внешних печатных форм (ВПФ) на базе Библиотеки Стандартных Подсистем (БСП) программисты часто сталкиваются с неприятной ошибкой: «В обработчике печати не был сформирован табличный документ». На первый взгляд код кажется абсолютно верным, данные выбираются без ошибок, но система упорно отказывается выводить печатную форму на экран.
В этой статье мы подробно разберем, почему возникает эта ошибка, проанализируем реальный пример кода с форума, выявим неочевидные баги и научимся правильно отлаживать внешние печатные формы.
Давайте внимательно изучим код регистрации внешней обработки и процедуру печати из примера автора. Обратите внимание на регистрацию команды в функции СведенияОВнешнейОбработке:
ДобавитьКоманду(ТаблицаКоманд, "Заказ поставщику ПФ", "ЗаказПоставшику", "ВызовСерверногоМетода", Истина, "ПечатьMXL");
А теперь посмотрим, как этот документ регистрируется в коллекции печатных форм в процедуре Печать:
УправлениеПечатью.ВывестиТабличныйДокументВКоллекцию(КоллекцияПечатныхФорм, "ЗаказПоставщику", "Заказ поставщику ПФ", ТабличныйДокумент);
Заметили разницу? В функции регистрации используется идентификатор "ЗаказПоставшику" (через букву «ш»), а в процедуре вывода в коллекцию — "ЗаказПоставщику" (через букву «щ»).
Почему это приводит к ошибке?
Механизм БСП работает следующим образом: при нажатии кнопки печати система ищет в зарегистрированных командах идентификатор запущенной команды (в нашем случае это зарегистрированный "ЗаказПоставшику"). Затем она выполняет процедуру Печать, ожидая, что в переданную коллекцию КоллекцияПечатныхФорм будет добавлен табличный документ именно с этим идентификатором. Но наш код добавляет в коллекцию документ с именем "ЗаказПоставщику". В итоге БСП видит, что для запрошенной команды "ЗаказПоставшику" табличный документ так и не был сформирован. Это и вызывает исключение.
Даже если мы исправим досадную опечатку с буквой «ш», код автора содержал еще несколько критических уязвимостей, которые неизбежно привели бы к падению системы в реальных условиях эксплуатации. Давайте проанализируем их по шагам.
1. Несоответствие параметров при вызове функции формирования
Посмотрим, как в процедуре Печать вызывается функция формирования табличного документа:
ТабличныйДокумент = СформироватьПечатнуюФорму(МассивОбъектов[0], ИмяМакета);
А теперь обратим внимание на сигнатуру самой функции СформироватьПечатнуюФорму:
Функция СформироватьПечатнуюФорму(МассивОбъектов, ОбъектыПечати)
Вместо коллекции ОбъектыПечати (которая используется БСП для сопоставления объектов метаданных и областей макета) автор передает строковую переменную ИмяМакета со значением "Макет". Если внутри функции или механизмов БСП произойдет обращение к методам коллекции ОбъектыПечати, система аварийно завершит работу с ошибкой несоответствия типов.
2. Обращение к несуществующему свойству Ссылка
В процедуру Печать БСП передает первый параметр МассивОбъектов. Автор берет его первый элемент МассивОбъектов[0] (который сам по себе является ссылкой на документ типа ДокументСсылка.ЗаказПоставщика) и передает его в функцию под именем МассивОбъектов. Внутри функции мы видим следующую строку:
ОбластьШапка.Параметры.ЗаказПоставщику = МассивОбъектов.Ссылка;
Поскольку в переменную МассивОбъектов была передана одиночная ссылка, обращение к свойству .Ссылка у ссылки избыточно. В некоторых случаях это может работать, но если тип переданного значения по какой-то причине изменится (например, при отладке будет передан другой тип данных), программа выдаст ошибку «Поле объекта не обнаружено (Ссылка)».
3. Игнорирование групповой печати
Архитектура БСП изначально проектируется с учетом того, что пользователь может выделить в списке несколько документов и отправить их на печать одновременно. В этом случае в процедуру Печать в параметре МассивОбъектов придет массив из нескольких ссылок.
Жесткая привязка к первому элементу массива МассивОбъектов[0] лишает пользователей возможности групповой печати. Будет распечатан только один документ, сколько бы строк в списке пользователь ни выбрал.
4. Проблема с версиями БСП (количество параметров)
В зависимости от версии БСП, используемой в вашей конфигурации, процедура Печать в модуле объекта внешней печатной формы может требовать разное количество параметров. В современных конфигурациях (например, ERP 2.5, УТ 11.5, КА 2.5) процедура ожидает 5 параметров:
Процедура Печать(МассивОбъектов, ПараметрыПечати, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт
В коде автора параметров всего 4:
Процедура Печать(МассивОбъектов, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт
Если запустить такой код в новой версии конфигурации, БСП передаст вторым параметром структуру параметров печати, которая в коде автора запишется в переменную КоллекцияПечатныхФорм. Из-за этого вызов метода ВывестиТабличныйДокументВКоллекцию завершится фатальной ошибкой выполнения.
Многие начинающие разработчики считают, что отлаживать ВПФ невозможно, так как они запускаются во временном хранилище. На самом деле существует два отличных и простых способа отладки.
Этот способ позволяет запускать и отлаживать печатную форму как обычную обработку через конфигуратор.
СсылкаНаДокумент с типом ДокументСсылка.ЗаказПоставщика) и кнопку «Печать».
&НаСервере
Процедура ПечатьНаСервере()
// Создаем аналоги параметров БСП
МассивОбъектов = Новый Массив;
МассивОбъектов.Добавить(Объект.СсылкаНаДокумент);
КоллекцияПечатныхФорм = УправлениеПечатью.ПодготовитьКоллекциюПечатныхФорм("ЗаказПоставщику");
ОбъектыПечати = Новый СписокЗначений;
ПараметрыВывода = УправлениеПечатью.ПодготовитьСтруктуруПараметровВывода();
// Вызываем экспортную процедуру печати нашей обработки
Печать(МассивОбъектов, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода);
// Получаем сформированный документ и показываем его
Если КоллекцияПечатныхФорм.Количество() > 0 Тогда
ТабДок = КоллекцияПечатныхФорм[0].ТабличныйДокумент;
// Сохраняем во временное хранилище или передаем на клиент для отображения
АдресХранилища = ПоместитьВоВременноеХранилище(ТабДок, УникальныйИдентификатор);
КонецЕсли;
КонецПроцедуры
Теперь вы можете открыть обработку в режиме «1С:Предприятие» через меню Файл -> Открыть, выбрать нужный документ в реквизите формы и нажать кнопку. В Конфигураторе при этом сработают любые точки останова (клавиша F9), установленные в модуле объекта.
Этот метод незаменим, когда печатная форма зависит от сложного контекста вызова БСП и должна запускаться непосредственно из интерфейса документа.
C:\Distr\MyPrintForm.epf.
Процедура Печать(МассивОбъектов, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт
// Загружаем актуальный код обработки напрямую с диска
ПутьКФайлу = "C:\Distr\MyPrintForm.epf";
ТекущаяОбработка = ВнешниеОбработки.Создать(ПутьКФайлу, Ложь);
ТекущаяОбработка.Печать(МассивОбъектов, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода);
КонецПроцедуры
Откройте файл MyPrintForm.epf в Конфигураторе, поставьте точки останова в нужных местах кода. Запустите печать из интерфейса программы. Система обратится к зарегистрированной обработке, та загрузит файл с диска, и управление перейдет в открытый в Конфигураторе файл, активировав отладчик.
Если вы проверили все идентификаторы и исправили опечатки, но ошибка все равно сохраняется, обратите внимание на следующие моменты:
Если РезультатЗапроса.Пустой() Тогда
Возврат ТабличныйДокумент
КонецЕсли;
"ЗаказПоставщика" (все русские) и "ЗаказПоставщика" (с латинской «а») — это совершенно разные вещи. Проверяйте идентификаторы копированием или используйте встроенные средства проверки правописания.БезопасныйРежим = Истина, платформа блокирует выполнение привилегированных модулей, запись/чтение файлов, COM-соединения и работу со сторонними базами данных. Если внутри вашего алгоритма печати происходит попытка такого вызова, выполнение кода прерывается ошибкой до момента записи табличного документа в коллекцию. Для решения проблемы установите значение БезопасныйРежим = Ложь или настройте необходимые разрешения.Давайте соберем все исправления воедино и напишем красивый, стабильно работающий код для нашей внешней печатной формы, поддерживающий групповую печать и корректную обработку параметров.
Код модуля объекта внешней обработки:
Функция СведенияОВнешнейОбработке() Экспорт
ПараметрыРегистрации = Новый Структура;
МассивНазначений = Новый Массив;
МассивНазначений.Добавить("Документ.ЗаказПоставщику");
ПараметрыРегистрации.Вставить("Вид", "ПечатнаяФорма");
ПараметрыРегистрации.Вставить("Назначение", МассивНазначений);
ПараметрыРегистрации.Вставить("Наименование", "Заказ поставщику ПФ (Исправленная)");
ПараметрыРегистрации.Вставить("Версия", "1.2");
ПараметрыРегистрации.Вставить("БезопасныйРежим", Ложь);
ПараметрыРегистрации.Вставить("Информация", "Печатная форма документа Заказ поставщику");
ТаблицаКоманд = ПолучитьТаблицуКоманд();
// Исключили опечатку в идентификаторе "ЗаказПоставщику"
ДобавитьКоманду(ТаблицаКоманд, "Заказ поставщику ПФ", "ЗаказПоставщику", "ВызовСерверногоМетода", Истина, "ПечатьMXL");
ПараметрыРегистрации.Вставить("Команды", ТаблицаКоманд);
Возврат ПараметрыРегистрации;
КонецФункции
Функция ПолучитьТаблицуКоманд()
Команды = Новый ТаблицаЗначений;
Команды.Колонки.Добавить("Представление", Новый ОписаниеТипов("Строка"));
Команды.Колонки.Добавить("Идентификатор", Новый ОписаниеТипов("Строка"));
Команды.Колонки.Добавить("Использование", Новый ОписаниеТипов("Строка"));
Команды.Колонки.Добавить("ПоказыватьОповещение", Новый ОписаниеТипов("Булево"));
Команды.Колонки.Добавить("Модификатор", Новый ОписаниеТипов("Строка"));
Возврат Команды;
КонецФункции
Процедура ДобавитьКоманду(ТаблицаКоманд, Представление, Идентификатор, Использование, ПоказыватьОповещение = Ложь, Модификатор = "")
НоваяКоманда = ТаблицаКоманд.Добавить();
НоваяКоманда.Представление = Представление;
НоваяКоманда.Идентификатор = Идентификатор;
НоваяКоманда.Использование = Использование;
НоваяКоманда.ПоказыватьОповещение = ПоказыватьОповещение;
НоваяКоманда.Модификатор = Модификатор;
КонецПроцедуры
// Используем универсальную сигнатуру БСП с 5 параметрами
Процедура Печать(МассивОбъектов, ПараметрыПечати, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт
// Проверяем наличие нашей команды в коллекции печатных форм
Если УправлениеПечатью.НужноПечататьМакет(КоллекцияПечатныхФорм, "ЗаказПоставщику") Тогда
// Формируем табличный документ для всего массива объектов
ТабличныйДокумент = СформироватьПечатнуюФорму(МассивОбъектов, ОбъектыПечати);
// Выводим результат в коллекцию
УправлениеПечатью.ВывестиТабличныйДокументВКоллекцию(
КоллекцияПечатныхФорм,
"ЗаказПоставщику",
"Заказ поставщику ПФ",
ТабличныйДокумент);
КонецЕсли;
КонецПроцедуры
Функция СформироватьПечатнуюФорму(МассивОбъектов, ОбъектыПечати)
ТабличныйДокумент = Новый ТабличныйДокумент;
ТабличныйДокумент.АвтоМасштаб = Истина;
Макет = ПолучитьМакет("Макет");
// Запрос адаптирован под выборку по ВСЕМУ массиву объектов (групповая печать)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЗаказПоставщикуТовары.Ссылка КАК Ссылка,
| ЗаказПоставщикуТовары.Номенклатура.Наименование КАК НоменклатураНаименование,
| ЗаказПоставщикуТовары.Номенклатура.Артикул КАК НоменклатураАртикул,
| ЗаказПоставщикуТовары.КоличествоУпаковок КАК КоличествоУпаковок,
| ЗаказПоставщикуТовары.Количество КАК Количество
|ИЗ
| Документ.ЗаказПоставщику.Товары КАК ЗаказПоставщикуТовары
|ГДЕ
| ЗаказПоставщикуТовары.Ссылка В(&МассивОбъектов)
|ИТОГИ ПО
| Ссылка";
Запрос.УстановитьПараметр("МассивОбъектов", МассивОбъектов);
РезультатЗапроса = Запрос.Выполнить();
Если РезультатЗапроса.Пустой() Тогда
// Если данных нет, выведем хотя бы пустой лист с предупреждением
ОбластьШапка = Макет.ПолучитьОбласть("Шапка");
ТабличныйДокумент.Вывести(ОбластьШапка);
Возврат ТабличныйДокумент;
КонецЕсли;
ВыборкаДокументов = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
ПервыйДокумент = Истина;
Пока ВыборкаДокументов.Следующий() Цикл
// При групповой печати каждый документ выводится на новой странице
Если Не ПервыйДокумент Тогда
ТабличныйДокумент.ВывестиГоризонтальныйРазделительСтраниц();
КонецЕсли;
ПервыйДокумент = Ложь;
// Запоминаем позицию начала вывода текущего документа
НомерСтрокиНачало = ТабличныйДокумент.ВысотаТаблицы + 1;
// Выводим шапку документа
ОбластьШапка = Макет.ПолучитьОбласть("Шапка");
ОбластьШапка.Параметры.ЗаказПоставщику = ВыборкаДокументов.Ссылка;
ТабличныйДокумент.Вывести(ОбластьШапка);
// Выводим строки товаров
ВыборкаСтрок = ВыборкаДокументов.Выбрать();
Пока ВыборкаСтрок.Следующий() Цикл
ОбластьСтрока = Макет.ПолучитьОбласть("Строка");
ЗаполнитьЗначенияСвойств(ОбластьСтрока.Параметры, ВыборкаСтрок);
ТабличныйДокумент.Вывести(ОбластьСтрока);
КонецЦикла;
// Регистрируем область печати для БСП
УправлениеПечатью.ЗадатьОбластьПечатиДокумента(
ТабличныйДокумент,
НомерСтрокиНачало,
ОбъектыПечати,
ВыборкаДокументов.Ссылка);
КонецЦикла;
Возврат ТабличныйДокумент;
КонецФункции