В процессе разработки в среде 1С:Предприятие программисты часто сталкиваются с необходимостью использования полей ввода с составным типом данных. Это очень удобный инструмент, позволяющий пользователю выбирать документы разных видов в одном и том же поле. Однако при работе с такими реквизитами часто возникает классическая ошибка: «Значение не является значением объектного типа (Ссылка)». В этой статье мы подробно разберем, почему возникает данная неполадка, проанализируем типичные ошибки в коде и разработаем надежное, архитектурно правильное решение.
Давайте разберемся, почему код, который отлично работал с одиночным типом данных, начинает аварийно завершаться после того, как типу реквизита назначили свойство составного. Нам важно понять разницу в инициализации полей платформой 1С:
ДокументСсылка.МаршрутноеЗадание). При создании формы или при очистке этого поля платформа автоматически инициализирует его пустой ссылкой данного конкретного типа. Пустая ссылка — это вполне легитимный объект системы. У него можно запросить свойство Ссылка (хотя в этом и нет практического смысла), и система не выдаст ошибку, а просто вернет ту же самую пустую ссылку.МаршрутноеЗадание, и РеализацияТоваровУслуг). В момент открытия формы или при нажатии пользователем на «крестик» (очистка поля) платформа не знает, пустую ссылку какого именно документа ей следует подставить. Из-за этой неопределенности система присваивает реквизиту системное значение Неопределено.Когда в коде происходит обращение к свойству через точку у переменной, содержащей Неопределено (например, ВыборМЗ.Ссылка), система 1С:Предприятие не находит у данного типа свойства с именем Ссылка. В результате выполнение программы немедленно прерывается с критической ошибкой: «Значение не является значением объектного типа (Ссылка)».
Рассмотрим следующую строчку кода, которая часто встречается у начинающих разработчиков:
СсылкаМЗ = ВыборМЗ.Ссылка;
Давайте проанализируем это действие. Реквизит формы ВыборМЗ уже сам по себе является ссылкой (тип ДокументСсылка.МаршрутноеЗадание или ДокументСсылка.РеализацияТоваровУслуг). Обращение к свойству .Ссылка у ссылочного типа абсолютно избыточно — оно просто возвращает саму себя. Более того, именно эта избыточность и приводит к падению программы, когда поле не заполнено. Если мы заменим этот код на прямое присваивание, программа отработает без ошибки, а в переменную запишется безопасное значение Неопределено, которое мы сможем обработать далее:
СсылкаМЗ = ВыборМЗ;
Для того чтобы наш код работал стабильно при любых действиях пользователя (открытие пустой формы, очистка поля ввода, выбор неверного типа), мы должны использовать встроенную функцию платформы ЗначениеЗаполнено(). Это золотой стандарт разработки в 1С. Данная функция возвращает Истина, если переданное ей значение отличается от дефолтных «пустышек» (таких как Неопределено, Null, пустая ссылка или пустая строка).
Давайте посмотрим на пример правильной проверки перед передачей параметра в запрос:
Если ЗначениеЗаполнено(ВыборМЗ) Тогда
// Поле заполнено реальным документом, можем безопасно работать
Запрос.УстановитьПараметр("Ссылка", ВыборМЗ);
Иначе
// Обрабатываем ситуацию, когда документ не выбран
Предупреждение("Пожалуйста, выберите документ для формирования отчета!");
Возврат;
КонецЕсли;
Поскольку наше поле ВыборМЗ теперь имеет составной тип, логика формирования отчета или заполнения таблицы должна зависеть от того, какой именно документ выбрал пользователь. Для этого нам необходимо проверять тип значения с помощью функции ТипЗнч(). Давайте разберем по шагам, как правильно реализовать такое ветвление:
Если ТипЗнч(ВыборМЗ) = Тип("ДокументСсылка.МаршрутноеЗадание") Тогда
// Здесь мы пишем алгоритм обработки Маршрутного задания
ВыполнитьЗапросПоМаршрутномуЗаданию(ВыборМЗ);
ИначеЕсли ТипЗнч(ВыборМЗ) = Тип("ДокументСсылка.РеализацияТоваровУслуг") Тогда
// Здесь мы пишем алгоритм обработки Реализации товаров и услуг
ВыполнитьЗапросПоРеализации(ВыборМЗ);
КонецЕсли;
В процессе анализа кода из обсуждения темы мы обнаружили еще две серьезные проблемы, которые могут привести к нестабильной работе и снижению производительности информационной базы.
Проблема 1: Использование свойства «ВладелецФормы» напрямую.
Обращение к реквизитам формы-владельца через точку (например, ЭтаФорма.ВладелецФормы.ДокументОбъект.Ссылка) — это опасный антипаттерн. Если пользователь откроет эту обработку не из формы документа «Реализация товаров и услуг» (например, напрямую через меню «Сервис» -> «Внешние обработки»), свойство ВладелецФормы вернет Неопределено. Код сразу же упадет с ошибкой. Кроме того, при обновлении конфигурации структура формы-владельца может измениться, что полностью сломает логику внешней обработки.
Правильным решением является передача ссылки на документ через Параметры формы при её открытии. Давайте посмотрим, как это выглядит на практике:
Код вызова обработки из формы документа «Реализация товаров и услуг»:
ПараметрыФормы = Новый Структура;
ПараметрыФормы.Вставить("ДокументОснование", Ссылка);
ОткрытьФорму("ВнешняяОбработка.СтатусыДокументов.Форма.Форма", ПараметрыФормы);
Код обработки параметров в модуле формы внешней обработки (в процедуре ПриСозданииНаСервере):
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
Если Параметры.Свойство("ДокументОснование") Тогда
// Безопасно получаем переданную ссылку и заполняем реквизит формы
ВыборМЗ = Параметры.ДокументОснование;
КонецЕсли;
КонецПроцедуры
Проблема 2: Разыменование полей составного типа в запросах.
В исходном запросе автора встречается конструкция: МаршрутноеЗаданиеДокументыРеализации.Реализация.Номер. Поле Реализация имеет составной тип. При выполнении такого запроса СУБД делает неявное левое соединение (LEFT JOIN) со всеми таблицами документов, входящих в этот составной тип. Это критически замедляет работу системы на реальных базах данных.
Чтобы избежать падения производительности, мы должны использовать конструкцию ВЫРАЗИТЬ() в тексте запроса. Это жестко указывает платформе, к какой таблице нужно обращаться напрямую. Посмотрим на пример оптимизированного кода:
Запрос.Текст =
"ВЫБРАТЬ
| ВЫРАЗИТЬ(МаршрутноеЗаданиеДокументыРеализации.Реализация КАК Документ.РеализацияТоваровУслуг).Номер КАК НомерНакладной,
| ВЫРАЗИТЬ(МаршрутноеЗаданиеДокументыРеализации.Реализация КАК Документ.РеализацияТоваровУслуг).Дата КАК ДатаНакладной
|ИЗ
| Документ.МаршрутноеЗадание.ДокументыРеализации КАК МаршрутноеЗаданиеДокументыРеализации";
Теперь давайте соберем все наши архитектурные улучшения в единую, красивую и отказоустойчивую процедуру заполнения статусов документов. Мы полностью исключим ошибки объектного типа, оптимизируем запрос и сделаем код понятным и поддерживаемым:
Процедура ЗагрузитьСтатусыДокументов()
// Шаг 1. Проверяем, заполнено ли поле выбора
Если Не ЗначениеЗаполнено(ВыборМЗ) Тогда
Предупреждение("Пожалуйста, выберите Маршрутное задание или Реализацию товаров и услуг!", 10, "Внимание");
Возврат;
КонецЕсли;
// Создаем объект запроса
Запрос = Новый Запрос;
// Шаг 2. Разделяем логику в зависимости от выбранного типа документа
Если ТипЗнч(ВыборМЗ) = Тип("ДокументСсылка.МаршрутноеЗадание") Тогда
// Запрос для Маршрутного задания
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| МаршрутноеЗаданиеДокументыРеализации.Реализация.Номер КАК НомерНакладной,
| МаршрутноеЗаданиеДокументыРеализации.Реализация.Дата КАК ДатаНакладной,
| МаршрутноеЗаданиеДокументыРеализации.Реализация.Контрагент КАК Контрагент,
| МаршрутноеЗаданиеДокументыРеализации.Ссылка.Номер КАК НомерМаршрутногоЗадания,
| МаршрутноеЗаданиеДокументыРеализации.Реализация КАК Документ,
| МаршрутноеЗаданиеДокументыРеализации.Реализация.СуммаДокумента КАК СуммаНакладной,
| ЕСТЬNULL(СтатусыДокументовСрезПоследних.Вернул, ""-"") КАК Вернул,
| ЕСТЬNULL(СтатусыДокументовСрезПоследних.НакладнаяЗакрыта, ""-"") КАК НакладнаяЗакрыта,
| ЕСТЬNULL(СтатусыДокументовСрезПоследних.БезПечати, ""-"") КАК БезПечати,
| ЕСТЬNULL(СтатусыДокументовСрезПоследних.ИзменениеЦены, ""-"") КАК ИзменениеЦены,
| ЕСТЬNULL(СтатусыДокументовСрезПоследних.НакладнаяСКорректировкой, ""-"") КАК НакладнаяСКорректировкой,
| СтатусыДокументовСрезПоследних.Комментарий,
| ЛОЖЬ КАК СохранятьИзменения
|ИЗ
| Документ.МаршрутноеЗадание.ДокументыРеализации КАК МаршрутноеЗаданиеДокументыРеализации
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СтатусыДокументов.СрезПоследних(, ) КАК СтатусыДокументовСрезПоследних
| ПО МаршрутноеЗаданиеДокументыРеализации.Реализация = СтатусыДокументовСрезПоследних.Документ
|ГДЕ
| МаршрутноеЗаданиеДокументыРеализации.Ссылка = &Ссылка";
// Передаем ссылку напрямую без избыточного свойства .Ссылка
Запрос.УстановитьПараметр("Ссылка", ВыборМЗ);
ИначеЕсли ТипЗнч(ВыборМЗ) = Тип("ДокументСсылка.РеализацияТоваровУслуг") Тогда
// Запрос для Реализации товаров и услуг
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| РеализацияТоваровУслуг.Ссылка КАК Документ,
| РеализацияТоваровУслуг.Номер КАК НомерНакладной,
| РеализацияТоваровУслуг.Дата КАК ДатаНакладной,
| РеализацияТоваровУслуг.Контрагент,
| РеализацияТоваровУслуг.СуммаДокумента КАК СуммаНакладной,
| """" КАК НомерМаршрутногоЗадания,
| ЕСТЬNULL(СтатусыДокументовСрезПоследних.Вернул, ""-"") КАК Вернул,
| ЕСТЬNULL(СтатусыДокументовСрезПоследних.НакладнаяЗакрыта, ""-"") КАК НакладнаяЗакрыта,
| ЕСТЬNULL(СтатусыДокументовСрезПоследних.БезПечати, ""-"") КАК БезПечати,
| ЕСТЬNULL(СтатусыДокументовСрезПоследних.ИзменениеЦены, ""-"") КАК ИзменениеЦены,
| ЕСТЬNULL(СтатусыДокументовСрезПоследних.НакладнаяСКорректировкой, ""-"") КАК НакладнаяСКорректировкой,
| СтатусыДокументовСрезПоследних.Комментарий,
| ЛОЖЬ КАК СохранятьИзменения
|ИЗ
| Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СтатусыДокументов.СрезПоследних(, ) КАК СтатусыДокументовСрезПоследних
| ПО РеализацияТоваровУслуг.Ссылка = СтатусыДокументовСрезПоследних.Документ
|ГДЕ
| РеализацияТоваровУслуг.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", ВыборМЗ);
КонецЕсли;
// Выполняем запрос и заполняем реквизит формы (табличную часть)
ТаблицаСтатуса = Запрос.Выполнить().Выгрузить();
КонецПроцедуры
Подводя итог нашей работы, мы видим, что большинство ошибок при работе с составными типами в 1С происходят из-за попыток обращаться к свойствам незаполненных объектов. Своевременное использование функции ЗначениеЗаполнено(), отказ от избыточных вызовов .Ссылка, правильное разделение логики через ТипЗнч() и оптимизация запросов с помощью конструкции ВЫРАЗИТЬ() делают ваш код профессиональным, производительным и устойчивым к любым изменениям в структуре базы данных!