К статье
Практический материал

Разработка и Конфигурация · 12 мин

Как обойти все реквизиты документа в 1С

Как программно обойти все реквизиты документа 1С через метаданные: перебор шапки, табличных частей, стандартных реквизитов. Готовый код и типичные ошибки.

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

Ключевая идея: состав реквизитов хранится не в самом документе, а в его описании конфигурации — объекте Метаданные.Документы.ИмяДокумента. У него есть коллекции Реквизиты, СтандартныеРеквизиты, ТабличныеЧасти и ОбщиеРеквизиты, которые и нужно обойти. Такой подход не ломается при добавлении новых полей в конфигурацию — обработка подхватит их автоматически.

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

Получение объекта метаданных документа

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

// Вариант 1: по имени документа

МетаданныеДокумента = Метаданные.Документы["РеализацияТоваровУслуг"];

// Вариант 2: от объекта или ссылки

МетаданныеДокумента = ДокументСсылка.Метаданные();

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

Из объекта метаданных доступны: Реквизиты — явно созданные реквизиты шапки, СтандартныеРеквизиты — системные поля (Ссылка, Номер, Дата, Проведен и другие), ТабличныеЧасти — список табличных частей с их собственными реквизитами, ОбщиеРеквизиты — общие реквизиты конфигурации, у которых в состав входит этот документ.

Обход реквизитов шапки документа

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

Процедура ОбойтиРеквизитыШапки(ДокументОбъект)

МетаданныеДок = ДокументОбъект.Метаданные();

Для Каждого Реквизит Из МетаданныеДок.Реквизиты Цикл

ИмяРеквизита = Реквизит.Имя;

СинонимРеквизита = Реквизит.Синоним;

ТипРеквизита = Реквизит.Тип;

ЗначениеРеквизита = ДокументОбъект[ИмяРеквизита];

Сообщить(СтрШаблон("%1 (%2) = %3",

СинонимРеквизита, ИмяРеквизита, ЗначениеРеквизита));

КонецЦикла;

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

Обращение ДокументОбъект[ИмяРеквизита] работает и для ссылки на документ: платформа выполнит неявный запрос к базе и вернёт значение. Однако в цикле по многим документам это порождает отдельное обращение к СУБД на каждый реквизит — о производительности подробнее ниже.

Если нужны и стандартные реквизиты (Номер, Дата, Проведен), добавьте аналогичный цикл по МетаданныеДок.СтандартныеРеквизиты. Учтите: часть стандартных реквизитов, например «Ссылка» и «ПометкаУдаления», не всегда нужна в выгрузках, поэтому их обычно фильтруют по имени.

bsl
Для Каждого Реквизит Из Метаданные.Документы[ИмяДокумента].Реквизиты Цикл
    Значение = ДокументОбъект[Реквизит.Имя];
КонецЦикла;

Обход табличных частей и их реквизитов

Табличные части обходятся двумя вложенными циклами: внешний — по коллекции ТабличныеЧасти метаданных, внутренний — по строкам конкретной табличной части объекта, а внутри строки — по реквизитам этой табличной части.

Для Каждого ТабЧасть Из МетаданныеДок.ТабличныеЧасти Цикл

Сообщить("Табличная часть: " + ТабЧасть.Имя);

Для Каждого СтрокаТЧ Из ДокументОбъект[ТабЧасть.Имя] Цикл

Для Каждого РеквизитТЧ Из ТабЧасть.Реквизиты Цикл

Значение = СтрокаТЧ[РеквизитТЧ.Имя];

Сообщить(" " + РеквизитТЧ.Имя + " = " + Значение);

КонецЦикла;

КонецЦикла;

КонецЦикла;

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

Если табличная часть пустая, внутренний цикл просто не выполнится — отдельной проверки на пустоту делать не обязательно, но для отчётности полезно вывести признак «табличная часть не заполнена».

Обход по ссылке без получения объекта

Когда документов много, вызывать ПолучитьОбъект() для каждого расточительно: объект целиком загружается в память, включая все табличные части. Если нужно только прочитать значения, эффективнее запрос.

Идея: по метаданным динамически строится текст запроса, где перечисляются все реквизиты шапки, а затем запрос выполняется пакетно по выборке документов. Для табличных частей формируются отдельные запросы к виртуальным таблицам вида Документ.ИмяДокумента.ИмяТабличнойЧасти.

ТекстПолей = "";

Для Каждого Реквизит Из МетаданныеДок.Реквизиты Цикл

ТекстПолей = ТекстПолей + "Док." + Реквизит.Имя + "," + Символы.ПС;

КонецЦикла;

ТекстЗапроса = "ВЫБРАТЬ " + ТекстПолей + " Док.Ссылка

|ИЗ Документ." + МетаданныеДок.Имя + " КАК Док

|ГДЕ Док.Ссылка = &Ссылка";

Результат такого запроса удобно обходить через Выборка.Следующий(), а имена колонок совпадают с именами реквизитов — значит, обработка результата тоже может быть универсальной через РезультатЗапроса.Колонки.

Способы обхода реквизитов

Через объект (ПолучитьОбъект)

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

Через ссылку и обращение по имени

Простой код, но каждое обращение — отдельное чтение из базы. Приемлемо для разовых операций.

Динамический запрос

Оптимально для массовой обработки: один запрос на все поля. Минус: сложнее код и нельзя менять данные.

Отбор и фильтрация реквизитов при обходе

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

Проверка типа делается через Реквизит.Тип.СодержитТип(Тип("СправочникСсылка.Номенклатура")) или через анализ Реквизит.Тип.Типы(). Проверка заполненности значения — стандартной функцией ЗначениеЗаполнено(ДокументОбъект[Реквизит.Имя]).

Для исключения реквизитов по имени удобно завести соответствие или массив исключений: «Удалить…», «КлючСтроки», служебные поля обмена. В типовых конфигурациях на БСП также встречаются реквизиты с префиксом «Удалить», оставшиеся после перехода между версиями, — они не используются, но физически присутствуют в структуре.

Порядок построения универсального обхода
  1. 1
    Получите объект метаданных: ДокументСсылка.Метаданные()
  2. 2
    Переберите коллекцию Реквизиты и прочитайте значения через ДокументОбъект[Имя]
  3. 3
    Добавьте при необходимости цикл по СтандартныеРеквизиты и ОбщиеРеквизиты
  4. 4
    Переберите ТабличныеЧасти: для каждой — строки и реквизиты строк
  5. 5
    Отфильтруйте служебные и незаполненные поля по правилам вашей задачи
  6. 6
    Проверьте результат на документе с пустой и заполненной табличной частью

Типичные ошибки и ограничения

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

Вторая группа ошибок связана с общими реквизитами: они не входят в коллекцию Реквизиты документа, и про них забывают. Если в конфигурации используются общие реквизиты (например, «Организация» или «Комментарий» в некоторых конфигурациях), их нужно обходить отдельно через Метаданные.ОбщиеРеквизиты с проверкой состава.

Третья ошибка — обращение к реквизитам у нового, ещё не записанного объекта, полученного не через ПолучитьОбъект(), а через неинициализированную переменную. Убедитесь, что переменная содержит либо объект, либо ссылку, прежде чем обращаться к полям по индексу.

Типичные ошибки
01
Чтение значения по имени реквизита, которого нет у данного вида документа — возникает ошибка обращения к полю; сверяйте метаданные именно этого объекта
02
Игнорирование общих реквизитов — часть полей теряется в выгрузке, потому что они не входят в коллекцию Реквизиты
03
Обход по ссылке с обращением к каждому полю в цикле по тысячам документов — резкое падение производительности; используйте запрос
04
Забывание про СтандартныеРеквизиты — в выгрузке отсутствуют Номер, Дата и Проведен

Отдельно стоит упомянуть безопасность динамического построения запросов: имена реквизитов берутся из метаданных, а не из пользовательского ввода, поэтому инъекции здесь исключены. Но если имя документа приходит извне (например, из параметра обработки), проверяйте его существование: Метаданные.Документы.Найти(ИмяДокумента) вернёт Неопределено для несуществующего имени.

Проверка результата обхода

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

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

Контрольные точки

Как проверить результат

Количество реквизитов в обходе
Норма

Совпадает с числом реквизитов в конфигураторе (с учётом стандартных и общих)

Тревожный признак

Меньше — пропущены стандартные или общие реквизиты

Значения полей
Норма

Совпадают с данными на форме документа

Тревожный признак

Пустые значения у заполненных полей — читаете не тот объект

Табличные части
Норма

Число строк и колонок совпадает с формой

Тревожный признак

Часть колонок отсутствует — не обойдены реквизиты табличной части

Частые вопросы

Как обойти реквизиты всех документов конфигурации сразу?

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

Можно ли обойти реквизиты, не зная имени документа заранее?

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

Почему в переборе нет реквизитов «Номер» и «Дата»?

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

Как получить синоним реквизита для вывода пользователю?

У каждого объекта описания реквизита есть свойство Синоним. Если синоним не заполнен в конфигурации, платформа формирует представление автоматически из имени, поэтому для надёжности можно использовать конструкцию: если синоним пуст — выводить имя.

Как обойти только заполненные реквизиты документа?

Внутри цикла по реквизитам добавьте проверку ЗначениеЗаполнено(ДокументОбъект[Реквизит.Имя]) и пропускайте пустые. Учтите, что для ссылочных типов «пустое» значение — это пустая ссылка соответствующего типа, а для чисел — ноль.

Работает ли обход реквизитов на клиенте в управляемом приложении?

На тонком и веб-клиенте объект метаданных документа недоступен — обращение к Метаданные вызовет ошибку. Обход выполняйте в серверной процедуре (модуль объекта, общий серверный модуль или серверный метод формы), а на клиент возвращайте готовую структуру или массив структур с именами и значениями.

Итог

Что важно запомнить

Обход реквизитов строится на метаданных: Реквизиты, СтандартныеРеквизиты, ОбщиеРеквизиты и ТабличныеЧасти. Значения читаются по имени через ДокументОбъект[Имя]. Для массовой обработки используйте динамический запрос вместо ПолучитьОбъект(), а на клиенте управляемого приложения метаданные недоступны — обход выполняется на сервере.