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