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

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

При написании различных обменов данными, загрузок из текстовых файлов или Excel мы часто сталкиваемся с типичной задачей: у нас есть строковое наименование элемента (например, «Магазин №23»), и нам нужно программно записать его в реквизит документа, который имеет тип ссылки на справочник (например, СправочникСсылка.Организации).

Рассмотрим эту ситуацию подробнее. Если попытаться напрямую присвоить строку реквизиту-ссылке, как показано в примере ниже, платформа 1С просто проигнорирует такое действие или выдаст ошибку несоответствия типов:


// Так делать неправильно, если реквизит ссылочный!
ДокВозврат[ИмяРеквизита] = "Магазин №23";

Проанализируем по шагам, как правильно решать эту задачу, начиная от простого поиска элементов и заканчивая полностью динамическим определением типа реквизита через метаданные.

Поиск элемента справочника по наименованию

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

Посмотрим на классический пример использования метода НайтиПоНаименованию:


СтроковоеЗначение = "Магазин №23";
ИскомаяСсылка = Справочники.Организации.НайтиПоНаименованию(СтроковоеЗначение);
ДокВозврат["Организация"] = ИскомаяСсылка;

Однако здесь кроется одна неочевидная проблема. Разберем ситуацию: что произойдет, если организация с таким именем отсутствует в базе? Метод не вызовет ошибку, он вернет пустую ссылку. Присвоение пустой ссылки реквизиту приведет к тому, что поле останется визуально пустым. Поэтому всегда проверяем результат поиска с помощью функции ЗначениеЗаполнено():


ИскомаяСсылка = Справочники.Организации.НайтиПоНаименованию(СтроковоеЗначение);

Если ЗначениеЗаполнено(ИскомаяСсылка) Тогда
    ДокВозврат["Организация"] = ИскомаяСсылка;
Иначе
    Сообщить("Внимание! Элемент с наименованием '" + СтроковоеЗначение + "' не найден.");
КонецЕсли;

Динамическое определение типа реквизита через метаданные

Рассмотрим более сложный случай. Представим, что мы пишем универсальную загрузку. У нас есть имя реквизита (переменная Ключ) и строковое значение. Мы не знаем заранее, на какой именно справочник ссылается этот реквизит. Нам нужно получить эту информацию динамически "на лету".

Для этого обратимся к метаданным документа. Проанализируем алгоритм динамического поиска:

  1. Получаем объект метаданных нужного реквизита документа.
  2. Извлекаем тип этого реквизита.
  3. Получаем массив возможных типов (так как реквизит может быть составного типа).
  4. Находим объект метаданных для конкретного типа и узнаем его имя.
  5. Обращаемся к менеджеру справочников через квадратные скобки.

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


// Ключ - имя реквизита, например "Организация"
// Значение - строка для поиска

ТипРеквизита = ДокВозврат.Метаданные().Реквизиты[Ключ].Тип;
МассивТипов = ТипРеквизита.Виды();

Если МассивТипов.Количество() > 0 Тогда
    // Берем первый доступный тип
    ОбъектМД = Метаданные.НайтиПоТипу(МассивТипов[0]);
    
    Если ОбъектМД <> Неопределено И Метаданные.Справочники.Содержит(ОбъектМД) Тогда
        ИмяСправочника = ОбъектМД.Имя;
        
        // Динамическое обращение к нужному справочнику
        НайденнаяСсылка = Справочники[ИмяСправочника].НайтиПоНаименованию(Значение);
        
        Если ЗначениеЗаполнено(НайденнаяСсылка) Тогда
            ДокВозврат[Ключ] = НайденнаяСсылка;
        Иначе
            Сообщить("Значение " + Значение + " не найдено в справочнике " + ИмяСправочника);
        КонецЕсли;
    КонецЕсли;
КонецЕсли;

Важно: Если ваш реквизит имеет составной тип данных (может ссылаться на несколько разных справочников одновременно), вам потребуется обойти массив МассивТипов в цикле и попытаться выполнить поиск в каждом из них до первого успешного совпадения.

Автоматическое создание недостающих элементов

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

Дополним наш предыдущий код блоком создания нового элемента:


НайденнаяСсылка = Справочники[ИмяСправочника].НайтиПоНаименованию(Значение);

Если ЗначениеЗаполнено(НайденнаяСсылка) Тогда
    ДокВозврат[Ключ] = НайденнаяСсылка;
Иначе
    // Создаем новый элемент справочника
    НовыйЭлемент = Справочники[ИмяСправочника].СоздатьЭлемент();
    НовыйЭлемент.Наименование = Значение;
    Попытка
        НовыйЭлемент.Записать();
        ДокВозврат[Ключ] = НовыйЭлемент.Ссылка;
        Сообщить("Автоматически создан элемент: " + Значение);
    Исключение
        Сообщить("Ошибка при создании элемента: " + ОписаниеОшибки());
    КонецПопытки;
КонецЕсли;

Современные способы сериализации и сохранения ссылок

Часто задача поиска по наименованию возникает из-за того, что данные выгрузили в текстовый файл "как есть" с помощью функции Строка(). Ручная склейка строк символами вроде тильды (~) — это устаревший и нестабильный подход.

Рассмотрим более надежные способы переноса ссылочных данных между базами (если базы идентичны). Чтобы избежать проблем с поиском элементов по наименованию (особенно если имена могут дублироваться), передавайте Уникальный Идентификатор (UUID).

Пример выгрузки UUID:


УИД_Строкой = Строка(Документ[Ключ].УникальныйИдентификатор());
Текст.ЗаписатьСтроку(Ключ + "=" + УИД_Строкой);

Пример загрузки по UUID:


УИД = Новый УникальныйИдентификатор(УИД_Строкой);
ИскомаяСсылка = Справочники.Организации.ПолучитьСсылку(УИД);
ДокВозврат[Ключ] = ИскомаяСсылка;

Для сложных структур данных настоятельно рекомендуем использовать Сериализатор XDTO или функции работы с JSON (например, ЗаписатьJSON). Они позволяют автоматически корректно преобразовывать любые ссылки и документы в текст и восстанавливать их обратно без потери типизации. Если же задача стоит в передаче таблиц и списков значений через файлы, отличным выбором будут платформенные функции ЗначениеВФайл() и ЗначениеИзФайла().

← На главную