Как программно создать номенклатуру и подчиненный справочник Единицы Измерения в 1С без ошибок?

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

При разработке обработок загрузки данных, интеграций или механизмов импорта в 1С:Предприятии программисты регулярно сталкиваются с классической архитектурной дилеммой — так называемой проблемой «курицы и яйца». Ярким примером этой проблемы является создание нового элемента справочника Номенклатура с одновременным заполнением его базовой единицы хранения из подчиненного справочника ЕдиницыИзмерения.

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

В чем суть проблемы циклической зависимости?

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


Наим = "1234";
СпрНоменкл = Справочники.Номенклатура.СоздатьЭлемент();
СпрНоменкл.Наименование = Наим;

БазоваяЕдиница = Справочники.ЕдиницыИзмерения.ПолучитьСсылку();
ЕдиницаПоКлассификатору = Справочники.КлассификаторЕдиницИзмерения.НайтиПоКоду("796");

НоваяЕдиница = Справочники.ЕдиницыИзмерения.СоздатьЭлемент();
НоваяЕдиница.УстановитьСсылкуНового(БазоваяЕдиница);
НоваяЕдиница.Владелец = СпрНоменкл.Ссылка; // Ошибка: Ссылка еще пустая!
НоваяЕдиница.Коэффициент = 1;
НоваяЕдиница.ЕдиницаПоКлассификатору = ЕдиницаПоКлассификатору;
НоваяЕдиница.Записать(); // Сбой при записи

СпрНоменкл.Записать();

Выполнение этого кода неизбежно приведет к ошибкам в СУБД или к аварийному завершению транзакции стандартными механизмами конфигурации. Выясним ключевые причины сбоя:

  1. Некорректное получение ссылки нового элемента. Метод ПолучитьСсылку() без передачи уникального идентификатора в качестве параметра возвращает пустую ссылку типа справочника. Платформа 1С заблокирует запись нового элемента с пустой ссылкой через УстановитьСсылкуНового().
  2. Пустой владелец (Circular Reference). Строка НоваяЕдиница.Владелец = СпрНоменкл.Ссылка пытается сослаться на номенклатуру. Но поскольку сам элемент номенклатуры еще не записан в базу данных, свойство Ссылка возвращает пустое значение. Записать элемент подчиненного справочника без указания владельца платформа не позволит.
  3. Конфликт со стандартными обработчиками "ПриЗаписи". В типовых конфигурациях (таких как УТ 10.3, УПП 1.3 или КА 1.1) модуль объекта номенклатуры содержит встроенные проверки. При попытке записать номенклатуру система проверяет заполнение реквизитов БазоваяЕдиницаИзмерения, ЕдиницаХраненияОстатков и других. Если эти связи нарушены или не заполнены, транзакция откатывается с ошибкой "Не удалось записать: Единицы измерения!".

Рассмотрим по шагам правильные варианты реализации этой бизнес-логики.

Вариант 1. Использование встроенных механизмов автосоздания (Для типовых конфигураций)

Если вы работаете в конфигурации, основанной на классической архитектуре 1С (УТ 10.3, УПП 1.3), вам не нужно вручную программировать создание подчиненного справочника ЕдиницыИзмерения. Разработчики фирмы 1С уже заложили автосоздание базовой единицы в модуль объекта номенклатуры при ее первичной записи.

Достаточно правильно заполнить реквизиты общего классификатора (ОКЕИ), а остальное платформа сделает сама.

Посмотрим на пример правильного кода:


Наим = "Шланг высокого давления 10м";
СпрНоменкл = Справочники.Номенклатура.СоздатьЭлемент();
СпрНоменкл.Наименование = Наим;

// Находим базовую единицу измерения в классификаторе ОКЕИ
ЕдиницаПоКлассификатору = Справочники.КлассификаторЕдиницИзмерения.НайтиПоКоду("796"); // Код для "шт"

Если ЕдиницаПоКлассификатору.Пустая() Тогда
    ВызватьИсключение "Не найден элемент классификатора единиц измерения с кодом 796!";
КонецЕсли;

// Заполняем реквизит номенклатуры ссылкой на классификатор
СпрНоменкл.БазоваяЕдиницаИзмерения = ЕдиницаПоКлассификатору;

// При записи стандартный код в обработчике "ПриЗаписи" или "ПередЗаписью" 
// самостоятельно создаст элемент подчиненного справочника "ЕдиницыИзмерения" 
// и автоматически привяжет его к реквизитам хранения остатков.
Попытка
    СпрНоменкл.Записать();
    Сообщить("Номенклатура успешно создана! Базовая единица сформирована автоматически.");
Исключение
    Сообщить("Ошибка при записи номенклатуры: " + ОписаниеОшибки());
КонецПопытки;

Вариант 2. Разрыв циклической зависимости через генерацию UUID (Для нетиповых конфигураций)

Что делать, если вы работаете в самописной конфигурации или в системе, где автоматическое создание отключено? Нам необходимо разорвать замкнутый круг: номенклатура требует записанную единицу измерения, а единица измерения требует записанного владельца (номенклатуру).

Для решения этой задачи мы заранее сгенерируем уникальные идентификаторы (UUID) и получим ссылки на оба объекта еще до их физической записи в базу данных. Всю операцию мы обязательно обернем в транзакцию для сохранения логической целостности БД.

Разберем данный метод по шагам:


НачатьТранзакцию();

Попытка
    Наим = "Кабель силовой ВВГ 3х2.5";
    ЕдиницаПоКлассификатору = Справочники.КлассификаторЕдиницИзмерения.НайтиПоКоду("796");
    
    // Шаг 1. Генерируем уникальные идентификаторы и получаем будущие ссылки
    НовыйIDНоменклатуры = Новый УникальныйИдентификатор();
    СсылкаНоменклатуры = Справочники.Номенклатура.ПолучитьСсылку(НовыйIDНоменклатуры);
    
    НовыйIDЕдиницы = Новый УникальныйИдентификатор();
    СсылкаЕдиницы = Справочники.ЕдиницыИзмерения.ПолучитьСсылку(НовыйIDЕдиницы);
    
    // Шаг 2. Создаем и настраиваем элемент номенклатуры
    СпрНоменкл = Справочники.Номенклатура.СоздатьЭлемент();
    СпрНоменкл.УстановитьСсылкуНового(СсылкаНоменклатуры); // Фиксируем ссылку
    СпрНоменкл.Наименование = Наим;
    СпрНоменкл.БазоваяЕдиницаИзмерения = ЕдиницаПоКлассификатору;
    
    // Привязываем будущую единицу к реквизитам номенклатуры
    СпрНоменкл.ЕдиницаХраненияОстатков = СсылкаЕдиницы;
    СпрНоменкл.ЕдиницаДляОтчетов = СсылкаЕдиницы;
    
    // Шаг 3. Создаем подчиненную единицу измерения
    НоваяЕдиница = Справочники.ЕдиницыИзмерения.СоздатьЭлемент();
    НоваяЕдиница.УстановитьСсылкуНового(СсылкаЕдиницы); // Фиксируем ссылку
    НоваяЕдиница.Владелец = СсылкаНоменклатуры; // Ссылаемся на создаваемую номенклатуру
    НоваяЕдиница.Коэффициент = 1;
    НоваяЕдиница.ЕдиницаПоКлассификатору = ЕдиницаПоКлассификатору;
    НоваяЕдиница.Наименование = СокрЛП(ЕдиницаПоКлассификатору);
    
    // Шаг 4. Записываем оба объекта в базу данных
    // Порядок записи теперь не имеет значения, так как ссылки уже согласованы
    СпрНоменкл.Записать();
    НоваяЕдиница.Записать();
    
    // Фиксируем транзакцию, если все прошло успешно
    ЗафиксироватьТранзакцию();
    Сообщить("Номенклатура и единица измерения успешно записаны в одной транзакции!");
    
Исключение
    // В случае любой ошибки отменяем все изменения в БД
    ОтменитьТранзакцию();
    ВызватьИсключение "Не удалось программно создать объекты. Причина: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
КонецПопытки;

Вариант 3. Обход проверок через режим загрузки данных

Данный вариант идеально подходит для сценариев первоначального переноса данных из внешних файлов или интеграционных обменов (например, через КД, XML или JSON). Если встроенные проверки конфигурации мешают корректной последовательной записи, мы можем временно отключить выполнение прикладного кода обработчиков с помощью свойства ОбменДанными.Загрузка = Истина.

Важное предупреждение: Применение этого режима отключает абсолютно все проверки бизнес-логики модуля объекта. Используйте его только тогда, когда вы полностью уверены в корректности входящих данных.

Реализуем алгоритм обхода проверок:


// Шаг 1. Создаем и записываем номенклатуру "как есть" в режиме загрузки данных
СпрНоменкл = Справочники.Номенклатура.СоздатьЭлемент();
СпрНоменкл.Наименование = "Подшипник радиальный";
СпрНоменкл.ОбменДанными.Загрузка = Истина; // Отключаем проверки "ПриЗаписи"
СпрНоменкл.Записать(); // Теперь объект гарантированно записан, у нас есть валидная ссылка!

// Шаг 2. Создаем подчиненную единицу измерения
НоваяЕдиница = Справочники.ЕдиницыИзмерения.СоздатьЭлемент();
НоваяЕдиница.Владелец = СпрНоменкл.Ссылка; // Передаем ссылку уже существующего владельца
НоваяЕдиница.Коэффициент = 1;
НоваяЕдиница.ЕдиницаПоКлассификатору = Справочники.КлассификаторЕдиницИзмерения.НайтиПоКоду("796");
НоваяЕдиница.Наименование = "шт";
НоваяЕдиница.Записать();

// Шаг 3. Возвращаемся в номенклатуру и обновляем реквизиты единиц
НоменклатураОбъект = СпрНоменкл.Ссылка.ПолучитьОбъект();
НоменклатураОбъект.ОбменДанными.Загрузка = Истина; // Повторно отключаем проверки при обновлении
НоменклатураОбъект.БазоваяЕдиницаИзмерения = НоваяЕдиница.ЕдиницаПоКлассификатору;
НоменклатураОбъект.ЕдиницаХраненияОстатков = НоваяЕдиница.Ссылка;
НоменклатураОбъект.Записать();

Сообщить("Объекты успешно синхронизированы в режиме загрузки данных.");

Подведем итоги

Мы подробно проанализировали три надежных подхода к решению проблемы циклической связи справочников. При выборе метода ориентируйтесь на следующие рекомендации:

← На главную