В практике разработчика 1С часто возникают задачи, когда необходимо программно обработать все данные документа, не зная заранее их точного состава. Это может быть полезно для создания универсальных механизмов копирования, логирования изменений или автоматического заполнения связанных объектов. В данной статье мы подробно разберем, как реализовать перебор реквизитов документа, рассмотрим различия между метаданными и данными объекта, а также изучим способы работы со стандартными и общими реквизитами.
Для начала важно разграничить два понятия: метаданные и данные (значения). Метаданные описывают структуру — какие поля в принципе есть у документа данного типа. Данные — это конкретные значения (числа, строки, ссылки), которые записаны в эти поля в конкретном экземпляре документа.
Проанализируем базовый способ получения списка реквизитов через метаданные. Если мы хотим узнать имена всех реквизитов, созданных разработчиком в конфигураторе, нам нужно обратиться к коллекции Реквизиты объекта метаданных.
// Допустим, у нас есть ссылка на документ
МетаданныеДокумента = Ссылка.Метаданные();
Для каждого Реквизит Из МетаданныеДокумента.Реквизиты Цикл
ИмяРеквизита = Реквизит.Имя;
Сообщить("Найден реквизит: " + ИмяРеквизита);
КонецЦикла;
Рассмотрим важный нюанс: в коллекцию Метаданные.Реквизиты попадают только те поля, которые были добавлены вручную. Такие поля, как Дата, Номер, Проведен или Ссылка, являются системными. Выясним, как получить доступ к ним. Для этого в 1С предусмотрена отдельная коллекция — СтандартныеРеквизиты. Чтобы собрать полный список полей, нам потребуется два цикла или объединение коллекций.
// Перебор стандартных реквизитов
Для каждого СтандартныйРеквизит Из МетаданныеДокумента.СтандартныеРеквизиты Цикл
Сообщить("Стандартный реквизит: " + СтандартныйРеквизит.Имя);
КонецЦикла;
Обратите внимание: если ваша конфигурация использует Общие реквизиты (например, для разделения данных или хранения области данных), они не попадут ни в одну из вышеуказанных коллекций. Для их поиска необходимо перебирать Метаданные.ОбщиеРеквизиты и проверять свойство Состав, чтобы убедиться, что данный общий реквизит включен в наш документ.
Просто знать имя реквизита из метаданных недостаточно — обычно нам нужно получить его значение из конкретного объекта. Рассмотрим, как это сделать. В языке 1С реализован механизм обращения к свойствам объекта через квадратные скобки (индексация по строке). Это позволяет нам динамически обращаться к любому полю, имя которого мы получили в цикле по метаданным.
// Пример получения значений всех реквизитов конкретной ссылки
МетаданныеДокумента = Ссылка.Метаданные();
// Собираем все значения в структуру
Результат = Новый Структура;
// 1. Проход по обычным реквизитам
Для каждого МетРеквизит Из МетаданныеДокумента.Реквизиты Цикл
Имя = МетРеквизит.Имя;
Значение = Ссылка[Имя]; // Обращение по имени
Результат.Вставить(Имя, Значение);
КонецЦикла;
// 2. Проход по стандартным реквизитам
Для каждого МетРеквизит Из МетаданныеДокумента.СтандартныеРеквизиты Цикл
Имя = МетРеквизит.Имя;
Значение = Ссылка[Имя];
Результат.Вставить(Имя, Значение);
КонецЦикла;
Иногда задача стоит иначе: не перебрать все, а проверить, существует ли конкретный реквизит у объекта, чтобы избежать ошибки "Поле объекта не обнаружено". Рассмотрим несколько эффективных техник.
Способ 1: Использование коллекции метаданных
Мы можем попытаться найти реквизит в метаданных с помощью метода Найти(). Это наиболее корректный с точки зрения производительности способ при работе на сервере.
Функция ЕстьРеквизит(ОбъектМетаданных, ИмяРеквизита)
Возврат НЕ (ОбъектМетаданных.Реквизиты.Найти(ИмяРеквизита) = Неопределено
И ОбъектМетаданных.СтандартныеРеквизиты.Найти(ИмяРеквизита) = Неопределено);
КонецФункции
Способ 2: Трюк со структурой (без обращения к метаданным)
Существует элегантный способ проверить наличие свойства у любого объекта, используя метод ЗаполнитьЗначенияСвойств(). Разберем его логику: мы создаем структуру с нужным нам ключом и уникальным значением-маркером. Затем пытаемся заполнить структуру из объекта. Если значение маркера изменилось — значит, свойство у объекта существует.
Функция ЕстьРеквизитИлиСвойство(Объект, ИмяРеквизита)
Маркер = Новый УникальныйИдентификатор;
СтруктураПроверки = Новый Структура(ИмяРеквизита, Маркер);
ЗаполнитьЗначенияСвойств(СтруктураПроверки, Объект);
Возврат СтруктураПроверки[ИмяРеквизита] <> Маркер;
КонецФункции
Посмотрим на ситуацию, когда нам нужно получить данные сразу множества документов. В этом случае использование цикла с обращением к каждому объекту через Ссылка[Имя] будет крайне медленным, так как вызовет множество обращений к базе данных. Проанализируем вариант с использованием запроса.
Хотя в 1С использование конструкции ВЫБРАТЬ * считается плохим тоном в промышленной разработке, для разовых обработок или универсальных инструментов это допустимо. После выполнения запроса мы можем выгрузить результат в таблицу значений и перебрать её колонки.
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ * ИЗ Документ.АвансовыйОтчет ГДЕ Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", НужнаяСсылка);
ТЗ = Запрос.Выполнить().Выгрузить();
// Теперь мы можем перебрать колонки таблицы значений
Для каждого Колонка Из ТЗ.Колонки Цикл
Сообщить("Поле из запроса: " + Колонка.Имя + ", Значение: " + ТЗ[0][Колонка.Имя]);
КонецЦикла;
Если вы работаете в современных конфигурациях (УТ 11, ERP, БП 3.0), важно помнить про механизм Дополнительных реквизитов и сведений. Эти данные не являются реквизитами объекта в терминах конфигуратора. Они хранятся либо в табличной части ДополнительныеРеквизиты, либо во внешнем регистре сведений.
Для работы с ними недостаточно перебора Метаданные.Реквизиты. Рассмотрим, как программно найти такие данные:
ДополнительныеРеквизиты. Каждая строка там содержит "Свойство" (ссылка на план видов характеристик) и "Значение".СведенияООбъектах по фильтру владельца (вашего документа).Часто при переборе нам нужно не просто получить значение, а совершить действие только с определенными типами данных (например, найти все ссылки на документы). Выясним, как это реализовать через объект описания типов.
Для каждого МетРеквизит Из Ссылка.Метаданные().Реквизиты Цикл
ОписаниеТипа = МетРеквизит.Тип;
// Проверяем, может ли реквизит содержать ссылку
Если ОписаниеТипа.СодержитТип(Тип("ДокументСсылка.ПриходнаяНакладная")) Тогда
// Обрабатываем конкретную логику
КонецЕсли;
КонецЦикла;
Мы рассмотрели несколько способов обхода реквизитов документа. Подведем итог, какой метод выбрать в зависимости от ситуации:
Метаданные.Реквизиты и Метаданные.StandardAttributes.Структурой или поиск в метаданных.ЗаполнитьЗначенияСвойств(), которая сделает всю работу по сопоставлению имен за вас.Помните, что избыточное обращение к метаданным в высоконагруженных циклах может замедлить работу системы, поэтому старайтесь кэшировать список имен реквизитов, если планируете использовать его многократно для разных объектов одного типа.