Как добавить реквизит из расширения во внешнюю печатную форму и исправить ошибку «Поле объекта не обнаружено»?

Программист 1С v8.3 (Управляемые формы)
← На главную

При доработке типовых конфигураций с помощью расширений часто возникает задача добавить новый реквизит в документ и вывести его в печатную форму. Это стандартная задача, но на пути ее решения можно столкнуться с неочевидными трудностями. Одна из самых частых — ошибка «Поле объекта не обнаружено» при формировании печатной формы, даже если вы, казалось бы, правильно изменили запрос для получения данных.

Рассмотрим на конкретном примере, почему возникает эта ошибка и как ее правильно исправить. Проанализируем ситуацию, когда разработчик добавил реквизит ЕП2_Примечание в табличную часть «Товары» документа «СчетНаОплатуПокупателя», создал внешнюю печатную форму, но при попытке печати получил ошибку.

Анализ проблемы: почему не работает перехват функции?

Давайте по шагам разберем исходную ситуацию.

  1. Через расширение в документ СчетНаОплатуПокупателя, в табличную часть «Товары», добавлен новый реквизит ЕП2_Примечание.
  2. В форме документа (также в расширении) этот реквизит отображается и заполняется корректно.
  3. Создана внешняя печатная форма. В ее макете добавлена новая колонка для примечания.
  4. Для добавления нового реквизита в данные для печати предпринята попытка изменить запрос в модуле менеджера документа. Разработчик решил перехватить экспортную функцию с помощью аннотации &Вместо.
  5. При попытке печати система выдает ошибку: Поле объекта не обнаружено (ЕП2_Примечание).

Ошибка говорит нам о том, что в структуре или таблице значений, которая передается в макет для заполнения, отсутствует поле с таким именем. Разработчик был уверен, что изменил запрос и добавил это поле. Почему же его там нет?

Ключевая ошибка кроется в неправильном использовании аннотации &Вместо. Посмотрим на код из расширения, который не сработал:


&Вместо("ПолучитьТекстЗапросаДляФормированияТаблицыСведенийСчетаНаОплату")
Функция ЕП2_ПолучитьТекстЗапросаДляФормированияТаблицыСведенийСчетаНаОплатуВместо(...)
    // ...
КонецФункции

На первый взгляд все верно. Но позже выяснилось, что на самом деле была переопределена другая функция:


// Это пример ошибочного кода, который привел к проблеме.
// Пытаемся перехватить одну функцию...
&Вместо("ПолучитьТекстЗапросаДляФормированияТаблицыСведенийСчетаНаОплату") 
// ...а по факту реализуем другую, с другим именем и набором параметров!
Функция ЕП2_ПолучитьТаблицуСведенийСчетаНаОплату(Знач МассивДокументов, ...) Экспорт
    // ...
КонецФункции

В этом и заключается корень проблемы. Механизм расширений требует, чтобы сигнатура (имя функции и набор ее параметров) замещающей функции в точности совпадала с сигнатурой исходной функции из основной конфигурации. Если имена или параметры не совпадают, платформа просто игнорирует такую аннотацию, и вызов продолжает уходить в оригинальную функцию, а код из расширения не выполняется вовсе.

Убедиться в этом очень просто с помощью отладчика. Если поставить точку останова в начале функции в расширении, мы увидим, что отладчик на ней никогда не остановится. Это верный признак того, что ваш код не вызывается.

Решение 1: Правильный перехват экспортной функции

Это наиболее корректный с точки зрения архитектуры способ. Он позволяет точечно изменить логику конфигурации, не дублируя ее и сохраняя совместимость с будущими обновлениями. Разберем по шагам.

  1. Находим нужную функцию. С помощью отладчика или глобального поиска по конфигурации необходимо найти, какая именно экспортная функция в модуле менеджера отвечает за подготовку данных для печати. Часто одна функция (например, ПолучитьТаблицуСведений...) вызывает другую (ПолучитьТекстЗапроса...). Нам нужно перехватить ту, которая формирует и возвращает текст запроса.
  2. Создаем замещающую функцию в расширении. В модуле менеджера нашего документа в расширении создаем новую функцию.
  3. Копируем сигнатуру и добавляем аннотацию. Полностью копируем имя и параметры исходной функции и ставим перед ней аннотацию &Вместо("ИмяОригинальнойФункции").
  4. Модифицируем логику. Самый безопасный способ — не копировать весь текст запроса из основной конфигурации, а получить его, вызвав оригинальный метод, и затем модифицировать. Для этого используется ключевое слово ПродолжитьВызов().

Посмотрим на пример правильного кода. Допустим, мы выяснили, что текст запроса готовит функция ПолучитьТекстЗапросаДляФормированияТаблицыСведенийСчетаНаОплату().


&Вместо("ПолучитьТекстЗапросаДляФормированияТаблицыСведенийСчетаНаОплату")
Функция ЕП2_ПолучитьТекстЗапросаДляФормированияТаблицыСведенийСчетаНаОплату(ДокументСсылка)

    // С помощью ПродолжитьВызов() получаем оригинальный текст запроса из типовой конфигурации.
    // Это делает наше расширение более устойчивым к обновлениям.
    ТекстЗапроса = ПродолжитьВызов(ДокументСсылка);

    // Модифицируем полученный текст, добавляя выборку нашего нового реквизита.
    // Находим строку, после которой нужно вставить наше поле.
    СтрокаДляЗамены = "| СчетНаОплатуТовары.СуммаНДС КАК СуммаНДС";
    
    // Формируем новую строку с добавлением нашего реквизита.
    НоваяСтрока = СтрокаДляЗамены + "," + Символы.ПС + "| СчетНаОплатуТовары.ЕП2_Примечание КАК Примечание";
    
    ТекстЗапроса = СтрЗаменить(ТекстЗапроса, СтрокаДляЗамены, НоваяСтрока);
    
    Возврат ТекстЗапроса;

КонецФункции

После применения такого кода платформа будет вызывать именно вашу функцию из расширения. Она, в свою очередь, вызовет стандартную, получит ее результат (текст запроса), добавит в него нужное поле и вернет измененный текст. В результате в итоговой таблице данных появится колонка «Примечание», и ошибка «Поле объекта не обнаружено» исчезнет.

Решение 2: Формирование данных напрямую во внешней печатной форме (обходной путь)

Автор исходной темы нашел решение, «отказавшись от выноса экспортной функции и заменив ее своей». Вероятнее всего, это означает, что он реализовал получение всех данных для печати непосредственно в модуле объекта внешней печатной формы, полностью проигнорировав экспортные функции менеджера документа.

Этот подход тоже имеет право на жизнь, особенно в небольших доработках.

Как это выглядит?

Вместо того чтобы вызывать стандартную функцию типа УправлениеПечатью.СформироватьПечатнуюФорму(...), которая сама обращается к менеджеру объекта, вы пишете свой запрос прямо в процедуре Печать() модуля вашей ВПФ.


// Пример кода в модуле объекта внешней печатной формы
Функция Печать(МассивОбъектов, ОбъектыПечати)

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

    // ... здесь идет код по заполнению шапки и подвала макета ...
    
    // Выводим строки в макет
    ОбластьСтрока = Макет.ПолучитьОбласть("Строка");
    Пока ВыборкаТоваров.Следующий() Цикл
        ОбластьСтрока.Параметры.Заполнить(ВыборкаТоваров);
        ТабличныйДокумент.Вывести(ОбластьСтрока);
    КонецЦикла;
    
    // ...
    
    Возврат ТабличныйДокумент;

КонецФункции

Плюсы и минусы этого подхода:

В итоге, выбор способа зависит от сложности задачи, но для качественной и долгосрочной поддержки доработок всегда предпочтительнее первый вариант — корректный перехват и модификация стандартной логики через расширение.

← На главную