Приветствуем вас, уважаемые коллеги! Сегодня мы детально разберем одну из весьма распространенных и неприятных проблем, с которой часто сталкиваются как начинающие разработчики, так и специалисты при доработке механизмов печати в решениях на платформе «1С:Предприятие 8» (в частности, в отраслевой конфигурации 1С:MES Оперативный учет производства). Речь идет об ошибке аварийного завершения работы программы при попытке вывести на печать маршрутную карту:
{Документ.МаршрутнаяКартаПроизводства.МодульМенеджера(2031)}: Значение не является значением объектного типа (ПолучитьОбъект)
В этой статье мы подробно проанализируем причины возникновения этой ошибки, развенчаем популярные заблуждения разработчиков относительно клиент-серверного взаимодействия, разберем архитектурные стандарты разработки «1С» и предложим сразу несколько вариантов решения проблемы — от быстрого «костыля» до полноценного перевода печатной формы на современные стандарты БСП (Библиотеки Стандартных Подсистем) с использованием запросов.
Давайте посмотрим на то, как выглядит команда печати на стороне клиента, предоставленная автором темы:
#Область ОбработкаКоманды
&НаКлиенте
Процедура ОбработкаКоманды(ПараметрКоманды, ПараметрыВыполненияКоманды)
ПараметрыПечати = Новый Структура;
ПараметрыПечати.Вставить("ВывестиОперации", Истина);
ПараметрыПечати.Вставить("ВывестиМатериалы", Истина);
ПараметрыПечати.Вставить("ДополнительныйВкладыш", Ложь);
УправлениеПечатьюКлиент.ВыполнитьКомандуПечати("Документ.МаршрутнаяКартаПроизводства", "МаршрутнаяКарта",
ПараметрКоманды, ПараметрыВыполненияКоманды.Источник, ПараметрыПечати);
КонецПроцедуры
#КонецОбласти
На первый взгляд, клиентский метод написан абсолютно корректно и по стандартам БСП. Функция УправлениеПечатьюКлиент.ВыполнитьКомандуПечати берет на себя всю рутину по передаче параметров на сервер, формированию табличного документа и выводу его на экран пользователя. Однако сбой происходит дальше — на сервере, в модуле менеджера документа Документ.МаршрутнаяКартаПроизводства, в строке:
СпецификацияОбъект = Спецификация.ПолучитьОбъект();
Выясним истинную причину этого поведения.
В ходе обсуждения на форумах часто озвучивается мнение: «В тонком клиенте нельзя получить объект, поэтому печать возможна только в толстом клиенте». Это глубокое заблуждение!
Давайте разберем по шагам, почему это не так:
ВыполнитьКомандуПечати на клиенте управление незамедлительно передается на сервер.ПолучитьОбъект().Настоящая причина ошибки кроется в типе переменной Спецификация. В момент вызова метода переменная не содержит ссылку на элемент справочника (например, тип СправочникСсылка.СпецификацииНоменклатуры). Вместо этого там находится либо значение Неопределено (если реквизит «Спецификация» в документе пуст или не заполнен), либо значение NULL (если данные выбирались запросом через левое соединение, и соответствующая запись в БД отсутствует).
Поскольку у примитивных типов Неопределено и NULL нет и не может быть метода ПолучитьОбъект(), платформа генерирует исключение, и процесс формирования печатной формы прерывается.
Даже если бы реквизит Спецификация всегда был заполнен, использование метода ПолучитьОбъект() в коде печати является грубейшим нарушением стандартов разработки «1С» по следующим причинам:
ПолучитьОбъект() считывает из базы данных абсолютно все реквизиты объекта, включая все его табличные части, дополнительные сведения и метаданные, помещая их в оперативную память сервера. Для печатной формы, как правило, требуется лишь 3-4 поля (например, наименование, артикул, количество).Рассмотрим подробнее два основных способа решения проблемы: быстрый (для оперативного восстановления работоспособности) и правильный (соответствующий стандартам разработки «1С»).
Если вам необходимо срочно починить печать «прямо сейчас», самым простым способом будет добавление проверки на заполненность ссылки перед попыткой получить объект. Измените проблемный участок кода в модуле менеджера следующим образом:
Если ЗначениеЗаполнено(Спецификация) Тогда
СпецификацияОбъект = Спецификация.ПолучитьОбъект();
// Здесь продолжается ваш оригинальный код обработки объекта спецификации
Иначе
// Описываем поведение системы, если спецификация не заполнена
ОбщегоНазначенияКлиентСервер.СообщитьПользователю(
НСтр("ru = 'В маршрутной карте не заполнена спецификация! Печать некоторых данных будет пропущена.'"));
КонецЕсли;
Этот способ убережет программу от аварийного завершения, однако он не решает проблему неоптимального расхода ресурсов сервера.
Правильным и профессиональным решением является полный отказ от использования метода ПолучитьОбъект() в пользу объектных запросов. Перепишем логику получения данных спецификации. Вместо считывания всего объекта мы обратимся напрямую к его табличным частям с помощью класса Запрос.
Проанализируем пример такого запроса для получения комплектующих из спецификации:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| СпецификацияМатериалы.Номенклатура КАК Номенклатура,
| СпецификацияМатериалы.Количество КАК Количество,
| СпецификацияМатериалы.ЕдиницаИзмерения КАК ЕдиницаИзмерения
|ИЗ
| Справочник.СпецификацииНоменклатуры.ИсходныеКомплектующие КАК СпецификацияМатериалы
|ГДЕ
| СпецификацияМатериалы.Ссылка = &Спецификация";
Запрос.УстановитьПараметр("Спецификация", Спецификация);
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
// Заполняем область печатной формы данными из выборки
// Например: ОбластьМатериал.Параметры.Номенклатура = Выборка.Номенклатура;
КонецЦикла;
КонецЕсли;
Преимущества данного подхода:
Спецификация пуст (или равен Неопределено), запрос просто вернет пустой результат, не вызывая никаких ошибок.Поскольку в обсуждении упоминалось, что это первый опыт разработки печатных форм, разберем по шагам, как оформить данное решение в виде безопасной Внешней печатной формы (ВПФ). Это позволит вам не изменять типовую конфигурацию 1С:MES и без проблем обновлять ее в будущем.
Создайте новую внешнюю обработку и в ее Модуле Объекта пропишите стандартные экспортные методы интеграции с БСП:
// Функция возвращает сведения о внешней обработке для регистрации в справочнике "Дополнительные отчеты и обработки"
Функция СведенияОВнешнейОбработке() Экспорт
ПараметрыРегистрации = ДополнительныеОтчетыИОбработки.СведенияОВнешнейОбработке("2.2.2.1");
ПараметрыРегистрации.Вид = ДополнительныеОтчетыИОбработкиКлиентСервер.ВидОбработкиПечатнаяФорма();
ПараметрыРегистрации.Версия = "1.0";
ПараметрыРегистрации.БезопасныйРежим = Истина;
// Указываем, для какого документа предназначена форма
ПараметрыРегистрации.Назначение.Добавить("Документ.МаршрутнаяКартаПроизводства");
// Описание команды печати
НоваяКоманда = ПараметрыРегистрации.Команды.Добавить();
НоваяКоманда.Представление = НСтр("ru = 'Маршрутная карта производства (внешняя)'");
НоваяКоманда.Идентификатор = "МаршрутнаяКартаВнешняя";
НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыВызовСерверногоМетода();
НоваяКоманда.Модификатор = "ПечатьMXL";
Возврат ПараметрыРегистрации;
КонецФункции
// Основная процедура печати, вызываемая подсистемой БСП
Процедура Печать(МассивОбъектов, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт
ПараметрыПечати = Новый Структура;
// Проверяем, есть ли наша команда в списке на печать
ПечатнаяФорма = УправлениеПечатью.СведенияОПечатнойФорме(КоллекцияПечатныхФорм, "МаршрутнаяКартаВнешняя");
Если ПечатнаяФорма <> Неопределено Тогда
ТабличныйДокумент = СформироватьПечатнуюФорму(МассивОбъектов, ОбъектыПечати, ПараметрыПечати);
ПечатнаяФорма.ТабличныйДокумент = ТабличныйДокумент;
ПечатнаяФорма.НачалоЗаполнения = ТекущаяДата();
КонецЕсли;
КонецПроцедуры
// Внутренняя функция формирования табличного документа на основе запросов
Функция СформироватьПечатнуюФорму(МассивОбъектов, ОбъектыПечати, ПараметрыПечати)
ТабДок = Новый ТабличныйДокумент;
ТабДок.ИмяПараметровПечати = "ПАРАМЕТРЫ_ПЕЧАТИ_МаршрутнаяКартаВнешняя";
// Получаем макет внешней обработки
Макет = ПолучитьМакет("Макет");
// Оптимальный запрос для получения данных маршрутной карты и спецификации в одном пакете
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| МаршрутнаяКарта.Ссылка КАК Ссылка,
| МаршрутнаяКарта.Номер КАК Номер,
| МаршрутнаяКарта.Дата КАК Дата,
| МаршрутнаяКарта.Спецификация КАК Спецификация,
| МаршрутнаяКарта.Спецификация.Наименование КАК СпецификацияНаименование
|ИЗ
| Документ.МаршрутнаяКартаПроизводства КАК МаршрутнаяКарта
|ГДЕ
| МаршрутнаяКарта.Ссылка В(&МассивОбъектов)";
Запрос.УстановитьПараметр("МассивОбъектов", МассивОбъектов);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДокументов = РезультатЗапроса.Выбрать();
Пока ВыборкаДокументов.Следующий() Цикл
// Выводим шапку документа
ОбластьШапка = Макет.ПолучитьОбласть("Шапка");
ОбластьШапка.Параметры.Заполнить(ВыборкаДокументов);
ТабДок.Вывести(ОбластьШапка);
// Если спецификация заполнена, выбираем ее строки запросом без ПолучитьОбъект()
Если ЗначениеЗаполнено(ВыборкаДокументов.Спецификация) Тогда
ЗапросСтроки = Новый Запрос;
ЗапросСтроки.Текст =
"ВЫБРАТЬ
| СпецСтроки.Номенклатура.Наименование КАК Материал,
| СпецСтроки.Количество КАК Количество
|ИЗ
| Справочник.СпецификацииНоменклатуры.ИсходныеКомплектующие КАК СпецСтроки
|ГДЕ
| СпецСтроки.Ссылка = &Спецификация";
ЗапросСтроки.УстановитьПараметр("Спецификация", ВыборкаДокументов.Спецификация);
ВыборкаСтрок = ЗапросСтроки.Perform().Выбрать(); // Для совместимости с русским синтаксисом: ЗапросСтроки.Выполнить().Выбрать();
Пока ВыборкаСтрок.Следующий() Цикл
ОбластьСтрока = Макет.ПолучитьОбласть("Строка");
ОбластьСтрока.Параметры.Заполнить(ВыборкаСтрок);
ТабДок.Вывести(ОбластьСтрока);
КонецЦикла;
КонецЕсли;
КонецЦикла;
Возврат ТабДок;
КонецФункции
Используя данный шаблон, вы полностью застрахуете вашу конфигурацию от ошибок, связанных с пустыми ссылками, обеспечите высокую скорость работы системы и сохраните конфигурацию на полной поддержке поставщика, без необходимости снятия с замка.