Каждый начинающий разработчик в среде 1С:Предприятие рано или поздно сталкивается с ситуацией, когда при программном создании документов реквизит связи (например, Основание или ДокументОснование) упорно отказывается принимать значение и при отладке показывает Неопределено. Кажется, что код написан абсолютно верно: переменная заполнена, типы совпадают, но результат равен нулю.
В этой статье мы подробно разберем, почему возникает эта проблема, детально изучим внутреннее устройство объектной модели 1С, выясним разницу между ссылками и объектами, а также рассмотрим лучшие практики написания кода для решения подобных задач.
Давайте проанализируем ситуацию, которая обсуждалась на форуме. Разработчик пытается программно создать документ типа СписаниеСЯчеек и заполнить его реквизиты, включая реквизит Основание, который имеет составной тип данных (включающий ДокументСсылка.ЗаказПокупателя и ДокументСсылка.РеализацияТоваровУслуг).
Код, написанный разработчиком, выглядел следующим образом:
РТУ = Документы.СписаниеСЯчеек.СоздатьДокумент();
РТУ.Комментарий = Строка(Резерв) + " + " + Строка(Заказ);
РТУ.ДокументРезервирования = Резерв.Ссылка;
РТУ.Основание = Заказ;
РТУ.Товары.Загрузить(ТаблицаОбработки);
При отладке этого кода выясняется, что переменная Заказ содержит значение типа ДокументОбъект.ЗаказПокупателя, а реквизит РТУ.Основание после выполнения строки присвоения остается равным Неопределено. Давайте разберем по шагам, почему так происходит.
Главная причина кроется в фундаментальном разделении типов данных в платформе 1С:Предприятие. Платформа жестко разграничивает сущности, находящиеся в оперативной памяти для редактирования, и сущности, хранящиеся в базе данных в виде неизменяемых уникальных идентификаторов.
Рассмотрим эти понятия подробнее:
СоздатьДокумент() или ПолучитьОбъект(). Объект содержит в себе все данные документа, включая табличные части, движения по регистрам и свойства. Он предназначен исключительно для модификации (изменения реквизитов, добавления строк) и последующей записи в базу данных методом Записать(). Объект не может физически храниться внутри реквизита другого документа в базе данных.В коде автора переменная Заказ содержала именно ДокументОбъект. Попытка присвоить объект туда, где ожидается ссылка, приводит к конфликту типов на уровне платформы.
Давайте выясним, почему платформа не выдала ошибку выполнения (Runtime Error), а просто молча сбросила значение в Неопределено. Это связано с механизмом неявного приведения типов, встроенным в 1С.
Когда реквизит имеет простой тип (например, только ДокументСсылка.ЗаказПокупателя), попытка присвоить ему ДокументОбъект вызовет ошибку несовместимости типов в процессе выполнения транзакции.
Однако для составного типа данных правила работы меняются:
ДокументСсылка.ЗаказПокупателя в поле, где этот тип разрешен), значение успешно записывается.ДокументОбъект невозможно никаким образом неявно преобразовать в ДокументСсылка (ведь объект — это сложная структура в памяти, а ссылка — это просто 128-битный UUID), платформа считает данное приведение невозможным. В результате значение сбрасывается в системное значение по умолчанию для составного типа — Неопределено.Решение этой проблемы чрезвычайно простое: необходимо всегда передавать ссылку объекта. Исправленная строка кода должна выглядеть так:
РТУ.Основание = Заказ.Ссылка; // Присваиваем именно Ссылку!
Проанализируем ситуацию, когда нам необходимо программно контролировать типы данных или выполнять их безопасное приведение в сложных алгоритмах обмена.
Для программного определения типа переменной используется встроенная функция ТипЗнч(). Например:
Если ТипЗнч(Заказ) = Тип("ДокументОбъект.ЗаказПокупателя") Тогда
РТУ.Основание = Заказ.Ссылка;
ИначеЕсли ТипЗнч(Заказ) = Тип("ДокументСсылка.ЗаказПокупателя") Тогда
РТУ.Основание = Заказ;
КонецЕсли;
Если же мы хотим программно привести значение к нужному типу или проверить, допустим ли данный тип в реквизите, мы можем использовать объект метаданных ОписаниеТипов. Давайте посмотрим на практический пример использования метода ПривестиЗначение():
// Получаем описание типов реквизита "Основание" из метаданных документа
ОписаниеТиповОснования = Метаданные.Документы.СписаниеСЯчеек.Реквизиты.Основание.Тип;
// Проверяем, разрешен ли тип "ЗаказПокупателя" в данном реквизите
Если ОписаниеТиповОснования.СодержитТип(Тип("ДокументСсылка.ЗаказПокупателя")) Тогда
// Пытаемся безопасно привести значение
РТУ.Основание = ОписаниеТиповОснования.ПривестиЗначение(Заказ.Ссылка);
КонецЕсли;
Метод ПривестиЗначение() хорош тем, что если приведение невозможно, он вернет пустую ссылку одного из допустимых типов реквизита, предотвращая появление нежелательного значения Неопределено.
Рассмотрим важный нюанс. Что делать, если документ-основание (например, ЗаказПокупателя) создается в этой же транзакции программно и еще ни разу не был записан в базу данных? В этом случае обращение к свойству Заказ.Ссылка вернет пустую ссылку (так как объект еще не сохранен физически).
Чтобы связать новый документ-основание со вновь создаваемым подчиненным документом до их физической записи в базу данных, мы должны использовать специальный механизм генерации ссылки:
// 1. Создаем объект нового заказа
НовыйЗаказ = Документы.ЗаказПокупателя.СоздатьДокумент();
// 2. Генерируем для него новую уникальную ссылку заранее
СсылкаНовогоЗаказа = Документы.ЗаказПокупателя.ПолучитьСсылку(Новый УникальныйИдентификатор());
НовыйЗаказ.УстановитьСсылкуНового(СсылкаНовогоЗаказа);
// 3. Создаем документ Списание с ячеек
Списание = Документы.СписаниеСЯчеек.СоздатьДокумент();
// Записываем сгенерированную ссылку в реквизит Основание
Списание.Основание = СсылкаНовогоЗаказа;
// 4. Теперь можем безопасно записывать оба документа
НовыйЗаказ.Записать();
Списание.Записать();
Этот прием позволяет соблюсти целостность данных и избежать записи пустых ссылок в базу данных.
Ручное присвоение реквизитов (таких как Основание, Товары и т.д.) в сторонних обработках, как это делал автор темы, считается плохой практикой в профессиональной разработке 1С. Это нарушает принцип инкапсуляции кода. Если в будущем структура метаданных изменится, вам придется переписывать код во всех внешних обработках.
Правильным решением является использование стандартного платформенного механизма ввода на основании с помощью метода Заполнить(). Давайте посмотрим, как это должно выглядеть на практике.
Внешний вызов создания документа упрощается до минимума:
РТУ = Документы.СписаниеСЯчеек.СоздатьДокумент();
// Передаем ссылку на заказ в метод Заполнить
РТУ.Заполнить(Заказ.Ссылка);
РТУ.Записать();
Вся логика заполнения переносится внутрь модуля объекта создаваемого документа (СписаниеСЯчеек) в предопределенный обработчик ОбработкаЗаполнения:
Процедура ОбработкаЗаполнения(ДанныеЗаполнения, СтандартнаяОбработка)
// Проверяем тип входящего документа-основания
Если ТипЗнч(ДанныеЗаполнения) = Тип("ДокументСсылка.ЗаказПокупателя") Тогда
// Заполняем реквизит связи
ЭтотОбъект.Основание = ДанныеЗаполнения;
ЭтотОбъект.Комментарий = "Создано на основании " + Строка(ДанныеЗаполнения);
// Программируем заполнение табличной части "Товары"
Для Каждого СтрокаИсточника Из ДанныеЗаполнения.Товары Цикл
НоваяСтрока = ЭтотОбъект.Товары.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаИсточника);
КонецЦикла;
ИначеЕсли ТипЗнч(ДанныеЗаполнения) = Тип("ДокументСсылка.РеализацияТоваровУслуг") Тогда
ЭтотОбъект.Основание = ДанныеЗаполнения;
// Специфичная логика заполнения из реализации товаров
КонецЕсли;
КонецПроцедуры
Использование обработчика ОбработкаЗаполнения делает код масштабируемым, легко читаемым и гарантирует, что правила создания документов «на основании» будут работать одинаково как при интерактивном нажатии кнопки пользователем в интерфейсе, так и при программном создании документов из внешних обработок.
В современных конфигурациях, разработанных на базе БСП (например, 1С:ERP, 1С:Управление торговлей, 1С:Комплексная автоматизация), разработчики редко пишут вызовы ввода на основании вручную. Вместо этого задействуется подсистема «Подключаемые команды».
Для интеграции документов в общую систему ввода на основании БСП, разработчик регистрирует правила заполнения в модуле менеджера документа с помощью процедуры ДобавитьКомандыСозданияНаОсновании(). БСП автоматически отрисовывает кнопки создания документов в интерфейсе пользователя и сама управляет передачей ссылок между формами документов на клиенте и сервере, обеспечивая высокую отказоустойчивость программы.
Подводя итоги нашего анализа, выделим главные правила при работе с реквизитами связей в 1С:
ДокументОбъект в реквизиты базы данных. Всегда извлекайте ссылку через свойство .Ссылка.Неопределено.ТипЗнч(), а для безопасного приведения — объект ОписаниеТипов.УстановитьСсылкуНового().ОбработкаЗаполнения в модуле объекта.