Как программно создать и инициализировать любой прикладной объект в 1С, используя его тип?

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

При разработке универсальных решений в 1С, особенно связанных с динамической обработкой данных или интеграциями, часто возникает необходимость программно создать объект определенного прикладного типа (например, элемент справочника или новый документ), зная только его Тип. На первый взгляд, кажется логичным использовать оператор Новый(Тип), но, как выясняется, этот подход имеет свои особенности и ограничения, особенно для ссылочных объектов, таких как СправочникОбъект или ДокументОбъект.

Давайте подробнее рассмотрим эту проблему, выясним, почему прямое использование Новый(Тип) для прикладных объектов не работает так, как ожидается, и предложим эффективные решения.

1. Понимание оператора Новый(Тип) и его ограничений

Один из первых инстинктов программиста при необходимости создать объект по типу — это использование конструкции Новый(Тип). Автор темы столкнулся именно с этим: Новый(Тип) действительно создает некий объект, но с ним невозможно полноценно работать – его нельзя записать, заполнить реквизиты, и он ведет себя как "пустой" или "неинициализированный".

Почему так происходит?

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

Итак, проблема заключается в том, что для прикладных объектов, таких как справочники и документы, платформа 1С предоставляет другой механизм создания – через их менеджеры.

2. Правильный подход: Использование менеджеров объектов

Для создания полноценных, инициализированных объектов прикладных типов (таких как элементы справочника, документы, наборы записей регистров и т.д.) необходимо использовать специальные объекты-менеджеры. Каждый прикладной объект в 1С имеет свой соответствующий менеджер, например, для СправочникОбъект.Товары существует СправочникМенеджер.Товары, для ДокументОбъект.РеализацияТоваровУслугДокументМенеджер.РеализацияТоваровУслуг.

Эти менеджеры предоставляют методы для создания новых объектов: СоздатьЭлемент() для справочников, СоздатьДокумент() для документов, СоздатьНаборЗаписей() для регистров сведений и накопления, СоздатьВид() для планов видов характеристик и т.д. Эти методы не просто создают объект, но и выполняют его необходимую инициализацию, заполняя значениями по умолчанию (например, дату документа текущей датой).

2.1. Динамическое получение менеджера объекта по типу

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

  1. Через метаданные и оператор Новый():

    Этот способ является наиболее распространенным и гибким. Мы сначала получаем объект метаданных для нужного типа, а затем, зная полное имя менеджера (которое отличается от имени объекта суффиксом "Менеджер" вместо "Объект"), используем оператор Новый() для создания экземпляра менеджера.

    Рассмотрим пошагово:

    1. Определяем тип прикладного объекта. Допустим, у нас есть переменная ТипОбъекта, содержащая Тип("СправочникОбъект.Валюты").
    2. Находим объект метаданных, соответствующий этому типу. Это можно сделать с помощью метода Метаданные.НайтиПоТипу(ТипОбъекта).
      
      ТипОбъекта = Тип("СправочникОбъект.Валюты");
      МетаданныеОбъекта = Метаданные.НайтиПоТипу(ТипОбъекта);
      
    3. Формируем полное имя менеджера. Полное имя менеджера обычно получается заменой подстроки ".Объект." на ".Менеджер." в полном имени объекта метаданных.
      
      // Пример: "СправочникОбъект.Валюты" -> "СправочникМенеджер.Валюты"
      ИмяМенеджера = СтрЗаменить(МетаданныеОбъекта.ПолноеИмя, "Объект.", "Менеджер.");
      
    4. Создаем экземпляр менеджера. Используем оператор Новый() с полученным именем менеджера.
      
      МенеджерОбъекта = Новый(ИмяМенеджера);
      

    Объединим это в один универсальный фрагмент кода:

    
    // Предположим, у нас есть Тип (например, полученный из реквизита формы или где-то еще)
    ТипДляСоздания = Тип("СправочникОбъект.Номенклатура");
    
    // 1. Получаем объект метаданных по типу
    МетаданныеОбъекта = Метаданные.НайтиПоТипу(ТипДляСоздания);
    
    // Проверяем, что это не примитивный тип и не тип, не имеющий менеджера
    Если МетаданныеОбъекта = Неопределено Тогда
        Сообщить("Для типа " + ТипДляСоздания + " не удалось получить метаданные или он не поддерживается.");
        Возврат;
    КонецЕсли;
    
    // 2. Формируем полное имя менеджера
    // Например: "СправочникОбъект.Номенклатура" -> "СправочникМенеджер.Номенклатура"
    // или "ДокументОбъект.РеализацияТоваровУслуг" -> "ДокументМенеджер.РеализацияТоваровУслуг"
    ИмяТипаМенеджера = СтрЗаменить(МетаданныеОбъекта.ПолноеИмя, "Объект.", "Менеджер.");
    
    // 3. Создаем экземпляр менеджера
    МенеджерОбъекта = Новый(ИмяТипаМенеджера);
    
    // Теперь МенеджерОбъекта содержит нужный менеджер,
    // например, СправочникМенеджер.Номенклатура или ДокументМенеджер.РеализацияТоваровУслуг
    // и мы можем использовать его методы для создания объектов.
    
  2. Через универсальные функции:

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

    Использование такой функции значительно упрощает код:

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

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

2.2. Создание объектов с помощью менеджера

После того как мы получили нужный менеджер объекта, процесс создания становится простым и интуитивно понятным:

  1. Для справочников: Используем метод СоздатьЭлемент().
    
    // Предполагаем, что МенеджерОбъекта - это СправочникМенеджер
    НовыйЭлементСправочника = МенеджерОбъекта.СоздатьЭлемент();
    // Теперь НовыйЭлементСправочника готов к заполнению и записи
    НовыйЭлементСправочника.Наименование = "Новый товар " + Формат(ТекущаяДата(), "ДФ=dd.MM.yyyy ЧЧ:мм:сс");
    НовыйЭлементСправочника.Родитель = Справочники.ГруппыНоменклатуры.НайтиПоНаименованию("Основные товары");
    НовыйЭлементСправочника.Записать();
    Сообщить("Создан элемент справочника: " + НовыйЭлементСправочника.Наименование);
    
  2. Для документов: Используем метод СоздатьДокумент().
    
    // Предполагаем, что МенеджерОбъекта - это ДокументМенеджер
    НовыйДокумент = МенеджерОбъекта.СоздатьДокумент();
    // НовыйДокумент уже будет иметь заполненные реквизиты по умолчанию (например, Дата)
    НовыйДокумент.Дата = ТекущаяДата();
    НовыйДокумент.Организация = Справочники.Организации.МояОрганизация;
    // Заполняем табличную часть
    НоваяСтрока = НовыйДокумент.Товары.Добавить();
    НоваяСтрока.Номенклатура = Справочники.Номенклатура.НайтиПоКоду("00001");
    НоваяСтрока.Количество = 10;
    НоваяСтрока.Цена = 100;
    НоваяСтрока.Сумма = НоваяСтрока.Количество * НоваяСтрока.Цена;
    
    НовыйДокумент.Записать(); // Для проведения можно использовать НовыйДокумент.Записать(РежимЗаписиДокумента.Проведение);
    Сообщить("Создан документ: " + НовыйДокумент.Вид() + " №" + НовыйДокумент.Номер + " от " + НовыйДокумент.Дата);
    
  3. Для наборов записей регистров сведений и накопления: Используем метод СоздатьНаборЗаписей().
    
    // Предполагаем, что МенеджерОбъекта - это РегистрСведенийМенеджер или РегистрНакопленияМенеджер
    НаборЗаписей = МенеджерОбъекта.СоздатьНаборЗаписей();
    // НаборЗаписей.Отбор.Период.Установить(ТекущаяДата()); // Пример отбора для регистров сведений
    НоваяЗапись = НаборЗаписей.Добавить();
    НоваяЗапись.Период = ТекущаяДата();
    НоваяЗапись.Измерение1 = "Значение1";
    НоваяЗапись.Ресурс1 = 123;
    НаборЗаписей.Записать();
    Сообщить("Записаны записи регистра: " + МенеджерОбъекта.Имя());
    
  4. Для планов видов характеристик: Используем метод СоздатьВид().
    
    // Предполагаем, что МенеджерОбъекта - это ПланВидовХарактеристикМенеджер
    НовыйВид = МенеджерОбъекта.СоздатьВид();
    НовыйВид.Наименование = "Новая характеристика";
    НовыйВид.ТипЗначения = Новый ОписаниеТипов("Число");
    НовыйВид.Записать();
    Сообщить("Создан вид характеристики: " + НовыйВид.Наименование);
    

3. Инициализация и работа с созданными объектами

Важным преимуществом создания объектов через методы менеджеров (СоздатьЭлемент(), СоздатьДокумент() и др.) является то, что возвращаемые объекты уже инициализированы. Это означает, что они уже имеют некоторые значения по умолчанию (например, Дата для документов, ПометкаУдаления для справочников) и готовы к дальнейшему заполнению реквизитов и записи в базу данных. Вам не нужно выполнять дополнительную "инициализацию", как в случае с пустым объектом от Новый(Тип).

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

4. Особый случай: Создание объектов в управляемых формах через динамические реквизиты

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

Рассмотрим пример, приведенный в обсуждении:


&НаСервере
Процедура Команда1НаСервере()
    // 1. Определяем полное имя типа прикладного объекта, который хотим создать
    ИмяТипаПрикладногоОбъекта = "ДокументОбъект.Документ1"; // Например, "ДокументОбъект.ПоступлениеТоваровУслуг"

    // 2. Создаем массив реквизитов формы для добавления
    МассивРеквизитов = Новый Массив;

    // 3. Добавляем новый реквизит формы с нужным типом
    // ПрикладнойОбъект - это имя реквизита, которое будет использоваться на форме
    МассивРеквизитов.Добавить(Новый РеквизитФормы("ПрикладнойОбъект", Новый ОписаниеТипов(ИмяТипаПрикладногоОбъекта)));

    // 4. Изменяем реквизиты формы, добавляя новый
    ИзменитьРеквизиты(МассивРеквизитов);

    // 5. Получаем созданный объект из реквизита формы
    // На этом этапе платформа создает и инициализирует объект указанного типа
    ПрикладнойОбъект = РеквизитФормыВЗначение("ПрикладнойОбъект");

    // 6. Теперь можно работать с ПрикладнойОбъект
    ПрикладнойОбъект.Дата = ТекущаяДата();
    ПрикладнойОбъект.Контрагент = Справочники.Контрагенты.НайтиПоНаименованию("Поставщик 1");
    // ... заполняем другие реквизиты

    // 7. Записываем объект в базу данных
    // ПрикладнойОбъект.Записать();
    // Сообщить("Документ " + ПрикладнойОбъект.Вид() + " создан и заполнен на форме.");

    // Для демонстрации можно вывести что-то на клиент
    // ЗначениеВРеквизитФормы(ПрикладнойОбъект, "ПрикладнойОбъект"); // Если нужно вернуть на клиент для отображения
    Сообщить("Объект типа " + ПрикладнойОбъект.Метаданные().Имя + " создан и заполнен.");

КонецПроцедуры;

Этот подход достаточно сложен и применим преимущественно в контексте управляемых форм. Он позволяет "привязать" динамически созданный объект к форме, что может быть полезно для отладки, интерактивного заполнения или в ситуациях, когда объект должен быть частью данных формы, а не просто автономным программным объектом.

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

5. Применение в универсальной загрузке данных

Автор темы упомянул, что его задача связана с универсальной загрузкой данных, где после выбора загружаемого объекта (справочник или документ) необходимо выводить его реквизиты для сопоставления с колонками внешнего файла. Для реквизитов составного типа, которые являются ссылками, требуется создавать или искать объекты по выбранному в колонке типу.

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

  1. Определение типа загружаемого объекта: После выбора пользователем, например, "Справочник.Номенклатура", мы получаем его тип: ТипОбъектаДляЗагрузки = Тип("СправочникОбъект.Номенклатура");.
  2. Получение менеджера основного объекта: Используя алгоритм из раздела 2.1, получаем МенеджерОсновногоОбъекта.
  3. Создание или поиск основного объекта:

    Если мы создаем новый объект:

    
    НовыйОбъект = МенеджерОсновногоОбъекта.СоздатьЭлемент(); // или СоздатьДокумент()
    

    Если мы ищем существующий объект по какому-либо уникальному полю (например, коду или наименованию), то используем методы менеджера типа НайтиПоКоду(), НайтиПоНаименованию() или НайтиПоРеквизиту().

  4. Итерация по реквизитам и заполнение: Для каждого реквизита основного объекта, который нужно заполнить из загружаемых данных:
    1. Определяем имя реквизита (например, "Артикул", "ЕдиницаИзмерения").
    2. Определяем значение из источника данных.
    3. Обработка ссылочных реквизитов с составным типом: Если реквизит имеет составной тип и является ссылкой (например, реквизит "ЕдиницаИзмерения" в справочнике "Номенклатура", который может быть ссылкой на "Справочник.ЕдиницыИзмерения"), то выполняем следующие действия:
      1. Получаем тип ссылки, выбранный пользователем (например, Тип("СправочникОбъект.ЕдиницыИзмерения")).
      2. Получаем менеджер для этого ссылочного типа: МенеджерСсылочногоОбъекта = Новый(СтрЗаменить(Метаданные.НайтиПоТипу(ТипСсылочногоОбъекта).ПолноеИмя, "Объект.", "Менеджер.")).
      3. Ищем или создаем ссылочный объект:

        Например, ищем единицу измерения по наименованию из входных данных:

        
        // ЗначениеИзКолонки - это строковое наименование единицы измерения из файла
        СсылочныйОбъект = МенеджерСсылочногоОбъекта.НайтиПоНаименованию(ЗначениеИзКолонки);
        Если СсылочныйОбъект = Неопределено Тогда
            // Если не найдено, создаем новый элемент
            НоваяЕдиница = МенеджерСсылочногоОбъекта.СоздатьЭлемент();
            НоваяЕдиница.Наименование = ЗначениеИзКолонки;
            // ... Заполняем другие реквизиты НовойЕдиницы, если нужно
            НоваяЕдиница.Записать();
            СсылочныйОбъект = НоваяЕдиница.Ссылка;
        КонецЕсли;
        
      4. Присваиваем найденную/созданную ссылку реквизиту основного объекта:
        
        НовыйОбъект.ЕдиницаИзмерения = СсылочныйОбъект;
        
    4. Присвоение простых значений: Если реквизит имеет простой тип (строка, число, дата), просто присваиваем значение:
      
      НовыйОбъект.Наименование = ЗначениеИзКолонки;
      
  5. Запись основного объекта: После заполнения всех реквизитов, вызываем НовыйОбъект.Записать().

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

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

← На главную