Как исправить ошибку «Значение не является значением объектного типа (ПолучитьОбъект)» при печати маршрутной карты в 1С:MES

Программист 1С v8.3 (Управляемые формы) Управленческий учет Промышленность, строительство и АПК
← На главную

Приветствуем вас, уважаемые коллеги! Сегодня мы детально разберем одну из весьма распространенных и неприятных проблем, с которой часто сталкиваются как начинающие разработчики, так и специалисты при доработке механизмов печати в решениях на платформе «1С:Предприятие 8» (в частности, в отраслевой конфигурации 1С:MES Оперативный учет производства). Речь идет об ошибке аварийного завершения работы программы при попытке вывести на печать маршрутную карту:

{Документ.МаршрутнаяКартаПроизводства.МодульМенеджера(2031)}: Значение не является значением объектного типа (ПолучитьОбъект)

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

Анализ исходного кода и локализация проблемы

Давайте посмотрим на то, как выглядит команда печати на стороне клиента, предоставленная автором темы:


#Область ОбработкаКоманды
&НаКлиенте
Процедура ОбработкаКоманды(ПараметрКоманды, ПараметрыВыполненияКоманды)
    
    ПараметрыПечати = Новый Структура;
    ПараметрыПечати.Вставить("ВывестиОперации", Истина);
    ПараметрыПечати.Вставить("ВывестиМатериалы", Истина);
    ПараметрыПечати.Вставить("ДополнительныйВкладыш", Ложь);
    
    УправлениеПечатьюКлиент.ВыполнитьКомандуПечати("Документ.МаршрутнаяКартаПроизводства", "МаршрутнаяКарта", 
        ПараметрКоманды, ПараметрыВыполненияКоманды.Источник, ПараметрыПечати);
        
КонецПроцедуры
#КонецОбласти

На первый взгляд, клиентский метод написан абсолютно корректно и по стандартам БСП. Функция УправлениеПечатьюКлиент.ВыполнитьКомандуПечати берет на себя всю рутину по передаче параметров на сервер, формированию табличного документа и выводу его на экран пользователя. Однако сбой происходит дальше — на сервере, в модуле менеджера документа Документ.МаршрутнаяКартаПроизводства, в строке:

СпецификацияОбъект = Спецификация.ПолучитьОбъект();

Выясним истинную причину этого поведения.

Почему возникает ошибка: разбор заблуждений

В ходе обсуждения на форумах часто озвучивается мнение: «В тонком клиенте нельзя получить объект, поэтому печать возможна только в толстом клиенте». Это глубокое заблуждение!

Давайте разберем по шагам, почему это не так:

  1. Механизм печати БСП устроен таким образом, что после вызова ВыполнитьКомандуПечати на клиенте управление незамедлительно передается на сервер.
  2. Сам код формирования печатной формы выполняется внутри процедуры в Модуле Менеджера документа. Модуль менеджера компилируется и выполняется исключительно на Сервере.
  3. Следовательно, серверный контекст там присутствует в полной мере, и платформа технически имеет доступ к базам данных и методу ПолучитьОбъект().

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

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

Почему использование ПолучитьОбъект() в процедурах печати — это архитектурная ошибка?

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

Варианты исправления проблемы

Рассмотрим подробнее два основных способа решения проблемы: быстрый (для оперативного восстановления работоспособности) и правильный (соответствующий стандартам разработки «1С»).

Вариант 1. Быстрое исправление (проверка заполненности реквизита)

Если вам необходимо срочно починить печать «прямо сейчас», самым простым способом будет добавление проверки на заполненность ссылки перед попыткой получить объект. Измените проблемный участок кода в модуле менеджера следующим образом:


Если ЗначениеЗаполнено(Спецификация) Тогда
    СпецификацияОбъект = Спецификация.ПолучитьОбъект();
    // Здесь продолжается ваш оригинальный код обработки объекта спецификации
Иначе
    // Описываем поведение системы, если спецификация не заполнена
    ОбщегоНазначенияКлиентСервер.СообщитьПользователю(
        НСтр("ru = 'В маршрутной карте не заполнена спецификация! Печать некоторых данных будет пропущена.'"));
КонецЕсли;

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

Вариант 2. Архитектурно правильное решение через Запрос

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

Проанализируем пример такого запроса для получения комплектующих из спецификации:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	СпецификацияМатериалы.Номенклатура КАК Номенклатура,
    |	СпецификацияМатериалы.Количество КАК Количество,
    |	СпецификацияМатериалы.ЕдиницаИзмерения КАК ЕдиницаИзмерения
    |ИЗ
    |	Справочник.СпецификацииНоменклатуры.ИсходныеКомплектующие КАК СпецификацияМатериалы
    |ГДЕ
    |	СпецификацияМатериалы.Ссылка = &Спецификация";

Запрос.УстановитьПараметр("Спецификация", Спецификация);

РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
    Выборка = РезультатЗапроса.Выбрать();
    Пока Выборка.Следующий() Цикл
        // Заполняем область печатной формы данными из выборки
        // Например: ОбластьМатериал.Параметры.Номенклатура = Выборка.Номенклатура;
    КонецЦикла;
КонецЕсли;

Преимущества данного подхода:

  1. Код абсолютно безопасен. Если параметр Спецификация пуст (или равен Неопределено), запрос просто вернет пустой результат, не вызывая никаких ошибок.
  2. Высокая скорость выполнения: считываются только те поля, которые непосредственно выводятся на печать.
  3. Минимальное потребление памяти сервера приложений.

Создание Внешней Печатной Формы (ВПФ) для БСП

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

Создайте новую внешнюю обработку и в ее Модуле Объекта пропишите стандартные экспортные методы интеграции с БСП:


// Функция возвращает сведения о внешней обработке для регистрации в справочнике "Дополнительные отчеты и обработки"
Функция СведенияОВнешнейОбработке() Экспорт
    
    ПараметрыРегистрации = ДополнительныеОтчетыИОбработки.СведенияОВнешнейОбработке("2.2.2.1");
    
    ПараметрыРегистрации.Вид = ДополнительныеОтчетыИОбработкиКлиентСервер.ВидОбработкиПечатнаяФорма();
    ПараметрыРегистрации.Версия = "1.0";
    ПараметрыРегистрации.БезопасныйРежим = Истина;
    
    // Указываем, для какого документа предназначена форма
    ПараметрыРегистрации.Назначение.Добавить("Документ.МаршрутнаяКартаПроизводства");
    
    // Описание команды печати
    НоваяКоманда = ПараметрыРегистрации.Команды.Добавить();
    НоваяКоманда.Представление = НСтр("ru = 'Маршрутная карта производства (внешняя)'");
    НоваяКоманда.Идентификатор = "МаршрутнаяКартаВнешняя";
    НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыВызовСерверногоМетода();
    НоваяКоманда.Модификатор = "ПечатьMXL";
    
    Возврат ПараметрыРегистрации;
    
КонецФункции

// Основная процедура печати, вызываемая подсистемой БСП
Процедура Печать(МассивОбъектов, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт
    
    ПараметрыПечати = Новый Структура;
    
    // Проверяем, есть ли наша команда в списке на печать
    ПечатнаяФорма = УправлениеПечатью.СведенияОПечатнойФорме(КоллекцияПечатныхФорм, "МаршрутнаяКартаВнешняя");
    Если ПечатнаяФорма <> Неопределено Тогда
        
        ТабличныйДокумент = СформироватьПечатнуюФорму(МассивОбъектов, ОбъектыПечати, ПараметрыПечати);
        
        ПечатнаяФорма.ТабличныйДокумент = ТабличныйДокумент;
        ПечатнаяФорма.НачалоЗаполнения = ТекущаяДата();
        
    КонецЕсли;
    
КонецПроцедуры

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

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

← На главную