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

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

При разработке универсальных обработок в среде «1С:Предприятие» программисты часто сталкиваются с задачей выбора произвольного документа из базы данных. Типичный пример — создание обработки для выгрузки и загрузки данных («перегрузки»), где пользователь должен указать конкретный документ-источник, а система должна динамически считать его структуру, экспортировать информацию в файл и воссоздать аналогичный объект в другой базе.

Начинающие разработчики (особенно те, кто переходит в 1С из других языков программирования, таких как C++) часто совершают классическую ошибку: они пытаются вручную заполнить список выбора поля ввода именами документов из метаданных. В результате поле ввода заполняется строковыми значениями, а попытка обратиться к свойствам выбранного элемента (например, Номер или Дата) вызывает критическую ошибку выполнения. Разберем по шагам, почему возникает эта проблема, как правильно настроить интерфейс для выбора любого документа и как написать универсальный алгоритм обхода реквизитов через метаданные.

Выясняем причину ошибки: Метаданные vs Ссылки

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

Когда мы пишем:


Для каждого Документ из Метаданные.Документы Цикл
    ЭлементыФормы.ПолеВвода1.СписокВыбора.Добавить(Документ.Имя, Документ.Имя);
КонецЦикла;

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

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

Шаг 1. Правильная настройка поля ввода на форме

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

Рассмотрим порядок настройки для обычной формы (толстый клиент):

  1. Откройте палитру свойств нужного поля ввода на форме.
  2. Найдите свойство ТипЗначения.
  3. Нажмите на кнопку выбора типа (кнопка с тремя точками или символом «Т»).
  4. В открывшемся окне выбора типов разверните ветку документов. Вместо выбора конкретного вида документа установите флажок на общем типе ДокументСсылка. В результате тип данных поля станет составным, объединяя ссылки на все документы конфигурации.
  5. Убедитесь, что для свойства КнопкаВыбора установлено значение Да (или Авто).

Для управляемой формы (тонкий клиент) логика аналогична:

  1. Создайте реквизит формы (например, с именем ДокументСсылка).
  2. В качестве типа данных укажите составной тип, отметив флажками все необходимые виды документов вашей конфигурации (либо выберите общий тип ДокументСсылка, если платформа позволяет сделать это напрямую в вашей версии).
  3. Перетащите созданный реквизит на рабочую область формы — платформа автоматически создаст поле ввода и назначит ему кнопку выбора.

Посмотрим, как это работает на практике. При нажатии на кнопку выбора в поле ввода платформа сначала откроет стандартное диалоговое окно «Выбор типа данных». В нем пользователь выбирает нужный вид документа (например, «Поступление товаров и услуг»). После выбора типа кнопка поля ввода изменит свой вид на стандартное троеточие («...»). При повторном нажатии на нее откроется стандартная форма списка выбранного документа, где пользователь сможет кликнуть по конкретной записи (например, «Поступление №0001 от 12.03.2026») и перенести ее в поле ввода. Теперь в поле хранится полноценный объект типа ДокументСсылка!

Шаг 2. Защита от обращений к пустым значениям

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

Добавим в код кнопку выполнения («Выгрузить») и пропишем в ее обработчике обязательную проверку:


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

Шаг 3. Универсальный обход реквизитов через метаданные

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

Для решения этой задачи мы воспользуемся свойством Метаданные(), которое доступно у любого ссылочного объекта. Проанализируем пример кода для работы на стороне сервера (директива &НаСервере):


&НаСервере
Процедура ВыгрузитьНаСервере(ВыбранныйДокумент)
    
    // Получаем доступ к метаданным конкретного выбранного документа
    МетаданныеДокумента = ВыбранныйДокумент.Метаданные();
    
    // Создаем структуру для хранения выгружаемых данных шапки
    ДанныеШапки = Новый Структура;
    
    // Добавляем стандартные реквизиты, общие для всех документов
    ДанныеШапки.Вставить("Дата", ВыбранныйДокумент.Дата);
    ДанныеШапки.Вставить("Номер", ВыбранныйДокумент.Номер);
    
    // Динамически обходим все дополнительные реквизиты шапки документа
    Для каждого Реквизит Из МетаданныеДокумента.Реквизиты Цикл
        ИмяРеквизита = Реквизит.Имя;
        ЗначениеРеквизита = ВыбранныйДокумент[ИмяРеквизита];
        
        // Записываем имя и значение в нашу структуру данных
        ДанныеШапки.Вставить(ИмяРеквизита, ЗначениеРеквизита);
    КонецЦикла;
    
    // Выведем полученные данные в окно сообщений для демонстрации работы
    Для каждого Элемент Из ДанныеШапки Цикл
        Сообщить(ШаблонСтроки("Реквизит: %1, Значение: %2", Элемент.Ключ, Элемент.Значение));
    КонецЦикла;
    
КонецПроцедуры

В этом примере мы использовали конструкцию ВыбранныйДокумент[ИмяРеквизита]. Обращение через квадратные скобки позволяет динамически считывать значение свойства объекта, передавая его имя в виде строковой переменной. Это стандартный и крайне эффективный прием разработки в среде 1С.

Шаг 4. Обход табличных частей документа

Помимо реквизитов шапки, практически каждый документ содержит одну или несколько табличных частей (например, «Товары», «Услуги»). Их структуру также необходимо считывать динамически. Модернизируем наш серверный алгоритм, добавив в него обход табличных частей:


&НаСервере
Процедура ВыгрузитьДокументСТабличнымиЧастями(ВыбранныйДокумент)
    
    МетаданныеДокумента = ВыбранныйДокумент.Метаданные();
    
    // 1. Обрабатываем шапку документа (аналогично предыдущему шагу)
    Сообщить("--- ВЫГРУЗКА РЕКВИЗИТОВ ШАПКИ ---");
    Сообщить("Дата: " + ВыбранныйДокумент.Дата);
    Сообщить("Номер: " + ВыбранныйДокумент.Номер);
    
    Для каждого Реквизит Из МетаданныеДокумента.Реквизиты Цикл
        Сообщить(Реквизит.Имя + ": " + Строка(ВыбранныйДокумент[Реквизит.Имя]));
    КонецЦикла;
    
    // 2. Обрабатываем табличные части документа
    Сообщить("--- ВЫГРУЗКА ТАБЛИЧНЫХ ЧАСТЕЙ ---");
    Для каждого МетаТЧ Из МетаданныеДокумента.ТабличныеЧасти Цикл
        ИмяТЧ = МетаТЧ.Имя;
        Сообщить("Найдена табличная часть: " + ИмяТЧ);
        
        // Получаем данные конкретной табличной части документа
        ТабличнаяЧастьДокумента = ВыбранныйДокумент[ИмяТЧ];
        
        // Обходим строки табличной части
        Для каждого СтрокаТЧ Из ТабличнаяЧастьДокумента Цикл
            Сообщить("  Строка ТЧ:");
            
            // Обходим реквизиты (колонки) данной табличной части
            Для каждого РеквизитТЧ Из МетаТЧ.Реквизиты Цикл
                ИмяКолонки = РеквизитТЧ.Имя;
                ЗначениеКолонки = СтрокаТЧ[ИмяКолонки];
                
                Сообщить(ШаблонСтроки("    Реквизит ТЧ: %1 = %2", ИмяКолонки, ЗначениеКолонки));
            КонецЦикла;
        КонецЦикла;
    КонецЦикла;
    
КонецПроцедуры

Шаг 5. Сериализация сложных типов данных в простой TXT

В реальных тестовых заданиях часто накладывается ограничение: запрещено использовать автоматическую сериализацию XML/JSON и встроенные функции работы со значениями в формате системной строки (такие как ЗначениеВСтрокуВнутр()). Требуется выгрузить все данные в обычный текстовый файл (.txt) так, чтобы затем их можно было прочитать и воссоздать точную копию документа.

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

Рассмотрим правила конвертации основных типов 1С в плоский текст:

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


&НаСервере
Функция СериализоватьЗначениеВСтроку(Значение)
    
    Если Значение = Неопределено Тогда
        Возврат "NULL";
    КонецЕсли;
    
    ТипЗначения = ТипЗнч(Значение);
    
    // Обрабатываем простые типы данных
    Если ТипЗначения = Тип("Строка") Тогда
        Возврат "Строка|" + Значение;
    ИначеЕсли ТипЗначения = Тип("Число") Тогда
        Возврат "Число|" + Строка(Значение);
    ИначеЕсли ТипЗначения = Тип("Булево") Тогда
        Возврат "Булево|" + ?(Значение, "1", "0");
    ИначеЕсли ТипЗначения = Тип("Дата") Тогда
        Возврат "Дата|" + Формат(Значение, "ДФ=yyyyMMddHHmmss");
    КонецЕсли;
    
    // Обрабатываем ссылочные типы данных через метаданные
    МетаданныеОбъекта = Метаданные.НайтиПоТипу(ТипЗначения);
    Если МетаданныеОбъекта <> Неопределено Тогда
        ИмяКлассаМетаданных = МетаданныеОбъекта.Имя;
        
        Если Справочники.ТипВсеСсылки().СодержитТип(ТипЗначения) Тогда
            // Если это пустая ссылка справочника
            Если Значение.Пустая() Тогда
                Возврат "Справочник|" + ИмяКлассаМетаданных + "|ПУСТО";
            КонецЕсли;
            // Выгружаем по коду и наименованию
            Возврат "Справочник|" + ИмяКлассаМетаданных + "|" + Значение.Код + "|" + Значение.Наименование;
            
        ИначеЕсли Перечисления.ТипВсеСсылки().СодержитТип(ТипЗначения) Тогда
            // Для перечисления получаем имя конкретного значения
            ИмяЗначенияПеречисления = XMLСтрока(Значение);
            Возврат "Перечисление|" + ИмяКлассаМетаданных + "|" + ИмяЗначенияПеречисления;
            
        ИначеЕсли Документы.ТипВсеСсылки().СодержитТип(ТипЗначения) Тогда
            Если Значение.Пустая() Тогда
                Возврат "Документ|" + ИмяКлассаМетаданных + "|ПУСТО";
            КонецЕсли;
            // Документы идентифицируем по дате и номеру
            СтрокаДаты = Формат(Значение.Дата, "ДФ=yyyyMMddHHmmss");
            Возврат "Документ|" + ИмяКлассаМетаданных + "|" + Значение.Номер + "|" + СтрокаДаты;
        КонецЕсли;
    КонецЕсли;
    
    Возврат "НеизвестныйТип|";
КонецФункции

Шаг 6. Загрузка и воссоздание документа (Создание копии)

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

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


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

Таким образом, использование динамического обращения к свойствам объекта через квадратные скобки НовыйДокумент[ИмяРеквизита] и методов коллекции Метаданные() позволяет полностью отказаться от жесткого кодирования («хардкода») конкретных названий полей. Наша обработка становится абсолютно универсальной и гибкой, легко подстраиваясь под любые изменения конфигурации информационной базы.

← На главную