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