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