Почему не присваивается реквизит «Основание» в 1С и как правильно работать с составными типами данных?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

В этой статье мы подробно разберем, почему возникает эта проблема, детально изучим внутреннее устройство объектной модели 1С, выясним разницу между ссылками и объектами, а также рассмотрим лучшие практики написания кода для решения подобных задач.

Анализируем исходную проблему

Давайте проанализируем ситуацию, которая обсуждалась на форуме. Разработчик пытается программно создать документ типа СписаниеСЯчеек и заполнить его реквизиты, включая реквизит Основание, который имеет составной тип данных (включающий ДокументСсылка.ЗаказПокупателя и ДокументСсылка.РеализацияТоваровУслуг).

Код, написанный разработчиком, выглядел следующим образом:


РТУ = Документы.СписаниеСЯчеек.СоздатьДокумент(); 
РТУ.Комментарий = Строка(Резерв) + " + " + Строка(Заказ);
РТУ.ДокументРезервирования = Резерв.Ссылка;
РТУ.Основание = Заказ;
РТУ.Товары.Загрузить(ТаблицаОбработки);

При отладке этого кода выясняется, что переменная Заказ содержит значение типа ДокументОбъект.ЗаказПокупателя, а реквизит РТУ.Основание после выполнения строки присвоения остается равным Неопределено. Давайте разберем по шагам, почему так происходит.

1. Объектная модель 1С: разница между ДокументОбъект и ДокументСсылка

Главная причина кроется в фундаментальном разделении типов данных в платформе 1С:Предприятие. Платформа жестко разграничивает сущности, находящиеся в оперативной памяти для редактирования, и сущности, хранящиеся в базе данных в виде неизменяемых уникальных идентификаторов.

Рассмотрим эти понятия подробнее:

В коде автора переменная Заказ содержала именно ДокументОбъект. Попытка присвоить объект туда, где ожидается ссылка, приводит к конфликту типов на уровне платформы.

2. Как платформа 1С обрабатывает присвоение значений составным типам

Давайте выясним, почему платформа не выдала ошибку выполнения (Runtime Error), а просто молча сбросила значение в Неопределено. Это связано с механизмом неявного приведения типов, встроенным в 1С.

Когда реквизит имеет простой тип (например, только ДокументСсылка.ЗаказПокупателя), попытка присвоить ему ДокументОбъект вызовет ошибку несовместимости типов в процессе выполнения транзакции.

Однако для составного типа данных правила работы меняются:

  1. При присвоении значения реквизиту составного типа платформа проверяет, входит ли тип присваиваемого значения в список разрешенных типов реквизита.
  2. Если тип совпадает (например, мы присваиваем ДокументСсылка.ЗаказПокупателя в поле, где этот тип разрешен), значение успешно записывается.
  3. Если тип не совпадает напрямую, система пытается выполнить неявное приведение типов (например, число автоматически преобразуется в строку, если в составе типа есть строка).
  4. Поскольку тип ДокументОбъект невозможно никаким образом неявно преобразовать в ДокументСсылка (ведь объект — это сложная структура в памяти, а ссылка — это просто 128-битный UUID), платформа считает данное приведение невозможным. В результате значение сбрасывается в системное значение по умолчанию для составного типа — Неопределено.

Решение этой проблемы чрезвычайно простое: необходимо всегда передавать ссылку объекта. Исправленная строка кода должна выглядеть так:

РТУ.Основание = Заказ.Ссылка; // Присваиваем именно Ссылку!

3. Работа с типами: проверка и жесткое приведение через ОписаниеТипов

Проанализируем ситуацию, когда нам необходимо программно контролировать типы данных или выполнять их безопасное приведение в сложных алгоритмах обмена.

Для программного определения типа переменной используется встроенная функция ТипЗнч(). Например:


Если ТипЗнч(Заказ) = Тип("ДокументОбъект.ЗаказПокупателя") Тогда
    РТУ.Основание = Заказ.Ссылка;
ИначеЕсли ТипЗнч(Заказ) = Тип("ДокументСсылка.ЗаказПокупателя") Тогда
    РТУ.Основание = Заказ;
КонецЕсли;

Если же мы хотим программно привести значение к нужному типу или проверить, допустим ли данный тип в реквизите, мы можем использовать объект метаданных ОписаниеТипов. Давайте посмотрим на практический пример использования метода ПривестиЗначение():


// Получаем описание типов реквизита "Основание" из метаданных документа
ОписаниеТиповОснования = Метаданные.Документы.СписаниеСЯчеек.Реквизиты.Основание.Тип;

// Проверяем, разрешен ли тип "ЗаказПокупателя" в данном реквизите
Если ОписаниеТиповОснования.СодержитТип(Тип("ДокументСсылка.ЗаказПокупателя")) Тогда
    // Пытаемся безопасно привести значение
    РТУ.Основание = ОписаниеТиповОснования.ПривестиЗначение(Заказ.Ссылка);
КонецЕсли;

Метод ПривестиЗначение() хорош тем, что если приведение невозможно, он вернет пустую ссылку одного из допустимых типов реквизита, предотвращая появление нежелательного значения Неопределено.

4. Особый случай: создание связи для нового (незаписанного) документа

Рассмотрим важный нюанс. Что делать, если документ-основание (например, ЗаказПокупателя) создается в этой же транзакции программно и еще ни разу не был записан в базу данных? В этом случае обращение к свойству Заказ.Ссылка вернет пустую ссылку (так как объект еще не сохранен физически).

Чтобы связать новый документ-основание со вновь создаваемым подчиненным документом до их физической записи в базу данных, мы должны использовать специальный механизм генерации ссылки:


// 1. Создаем объект нового заказа
НовыйЗаказ = Документы.ЗаказПокупателя.СоздатьДокумент();

// 2. Генерируем для него новую уникальную ссылку заранее
СсылкаНовогоЗаказа = Документы.ЗаказПокупателя.ПолучитьСсылку(Новый УникальныйИдентификатор());
НовыйЗаказ.УстановитьСсылкуНового(СсылкаНовогоЗаказа);

// 3. Создаем документ Списание с ячеек
Списание = Документы.СписаниеСЯчеек.СоздатьДокумент();
// Записываем сгенерированную ссылку в реквизит Основание
Списание.Основание = СсылкаНовогоЗаказа; 

// 4. Теперь можем безопасно записывать оба документа
НовыйЗаказ.Записать();
Списание.Записать();

Этот прием позволяет соблюсти целостность данных и избежать записи пустых ссылок в базу данных.

5. Правильный архитектурный подход: Ввод на основании

Ручное присвоение реквизитов (таких как Основание, Товары и т.д.) в сторонних обработках, как это делал автор темы, считается плохой практикой в профессиональной разработке 1С. Это нарушает принцип инкапсуляции кода. Если в будущем структура метаданных изменится, вам придется переписывать код во всех внешних обработках.

Правильным решением является использование стандартного платформенного механизма ввода на основании с помощью метода Заполнить(). Давайте посмотрим, как это должно выглядеть на практике.

Внешний вызов создания документа упрощается до минимума:


РТУ = Документы.СписаниеСЯчеек.СоздатьДокумент();
// Передаем ссылку на заказ в метод Заполнить
РТУ.Заполнить(Заказ.Ссылка); 
РТУ.Записать();

Вся логика заполнения переносится внутрь модуля объекта создаваемого документа (СписаниеСЯчеек) в предопределенный обработчик ОбработкаЗаполнения:


Процедура ОбработкаЗаполнения(ДанныеЗаполнения, СтандартнаяОбработка)
    
    // Проверяем тип входящего документа-основания
    Если ТипЗнч(ДанныеЗаполнения) = Тип("ДокументСсылка.ЗаказПокупателя") Тогда
        
        // Заполняем реквизит связи
        ЭтотОбъект.Основание = ДанныеЗаполнения;
        ЭтотОбъект.Комментарий = "Создано на основании " + Строка(ДанныеЗаполнения);
        
        // Программируем заполнение табличной части "Товары"
        Для Каждого СтрокаИсточника Из ДанныеЗаполнения.Товары Цикл
            НоваяСтрока = ЭтотОбъект.Товары.Добавить();
            ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаИсточника);
        КонецЦикла;
        
    ИначеЕсли ТипЗнч(ДанныеЗаполнения) = Тип("ДокументСсылка.РеализацияТоваровУслуг") Тогда
        
        ЭтотОбъект.Основание = ДанныеЗаполнения;
        // Специфичная логика заполнения из реализации товаров
        
    КонецЕсли;
    
КонецПроцедуры

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

6. Роль Библиотеки Стандартных Подсистем (БСП)

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

Для интеграции документов в общую систему ввода на основании БСП, разработчик регистрирует правила заполнения в модуле менеджера документа с помощью процедуры ДобавитьКомандыСозданияНаОсновании(). БСП автоматически отрисовывает кнопки создания документов в интерфейсе пользователя и сама управляет передачей ссылок между формами документов на клиенте и сервере, обеспечивая высокую отказоустойчивость программы.

Резюме

Подводя итоги нашего анализа, выделим главные правила при работе с реквизитами связей в 1С:

  1. Никогда не присваивайте тип ДокументОбъект в реквизиты базы данных. Всегда извлекайте ссылку через свойство .Ссылка.
  2. Помните, что попытка присвоить неподходящий тип в поле составного типа в 1С не вызывает падения системы, а молча возвращает Неопределено.
  3. Для проверки типов используйте ТипЗнч(), а для безопасного приведения — объект ОписаниеТипов.
  4. Для создания связей между незаписанными объектами используйте метод УстановитьСсылкуНового().
  5. Придерживайтесь стандартов разработки 1С: инкапсулируйте логику создания документов на основании других внутри обработчика ОбработкаЗаполнения в модуле объекта.
← На главную