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