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