Мне приходится активно перемещаться между различными конфигурациями, занимаясь их поддержкой и развитием. Встречаются разные случаи, но есть и много общего. Дополнительные реквизиты и сведения, наряду с использованием расширений, являются мощным инструментом сохранения типовых конфигураций в виде, пригодном для частого обновления. Однако, на практике, почти всегда встречается одно и то же. Расширения часто либо не используются по причине "потом не разобраться", либо наблюдается другая крайность - на каждую задачу свое расширение. Это тема отдельной статьи. Сейчас говорим о дополнительных реквизитах и сведениях.
Буду говорить о себе, потому что только о себе можно сказать наверняка. Почему, зная об этом механизме, я продолжал создавать реквизиты в конфигурации? Скажу прямо. Потому, что такова реальность. К сожалению, производством качественного и структурированного кода удается заняться далеко не всегда. Основную часть времени занимаемся судорожным производством *овнокода за очень ограниченный промежуток времени.
Со стороны заказчика требования по сохранению конфигурации в явном виде поступают не часто. Временами в постоянном цейтноте возникает даже некое злорадное чувство. Типа - получи, что хотел.
Как в таких условиях сохранить чувство собственного достоинства, уважение к себе и не навредить? Для себя выход вижу только один. Эффективно использовать БСП и создавать свои библиотеки там, где БСП "недорабатывает".
Теперь, к делу. Много раз внимательно читал модули БСП, предназначенные для работы с дополнительными реквизитами и сведениями. Возможно, у меня плохо со зрением, но впечатление сложилось вполне определенное. Много корректного кода. Но реального набора функций, позволяющих программно работать со свойствами так же удобно, как с реквизитами объекта нет. ИМХО - прослеживается ориентация на интерактивные средства. Все направлено на то, чтобы наглядно представить наборы свойств на форме, создав иллюзию у пользователя, что он работает с обычными реквизитами.
Само по себе - это замечательно. Только, на мой взгляд, забыли о программистах, от которых собственно и зависит - будут ли активно использоваться свойства или нет. Возможно, предполагалось, что программисты способны позаботиться о себе сами. Ну, значит так тому и быть.
Практика, как известно - критерий истины. В данном случае, критерий прост. Если в момент выбора "Что использовать?" новый реквизит в конфе или дополнительное свойство, ты выбираешь свойство, значит средства работы со свойствами, которыми ты располагаешь, достаточно удобны. Если создаешь реквизит объекта, то тебе надо задуматься над своим инструментарием.
Однажды мне надоело выбирать, и я написал маленькую библиотечку. С тех пор всегда ее пользую и выбираю свойства не задумываясь. В этом нет никакого открытия Америки. Примеров подобных функций в сети много. Возможно, более корректных. Это тоже говорит о том, что тема актуальна и пишу я не зря. Надо просто собраться с духом и поместить эти функции в модуль, который всегда под рукой. И тогда сразу кое-что изменится в подходах к расширению возможностей конфигурации. Привожу свои варианты функций из практических соображений. Пользуюсь ими давно. Никогда не подводили. Уже привык им доверять.
Сам я больше тяготею к использованию дополнительных сведений. Причем, без наборов. В основном использую их для хранения данных порождаемых программой. Поэтому работа с дополнительными реквизитами представлена в библиотеке беднее. Дописывать свежее не стал. Предлагаю только проверенный код. Каждый может доработать под свои нужды сам. Основная цель была не накормить, а научить ловить рыбу. Сорри.
Все функции приведены и описаны в тексте ниже:
1. Собственно - свойство. Суть этой функции в том, чтобы не только получить свойство по имени, но и создать его при отсутствии. Для создания нужно явно указать тип. Можно было создать строку по умолчанию, но остановился на этом варианте. Для большего контроля. Удобство удобством, но и случайное создание свойства из-за ошибки в имени ни к чему.
// Универсальная функция. Получает ссылку на свойство по имени. В случае отсутствия, если задан тип, создает свойство.
// ИмяСвойства - (Строка) Для поиска и создания, при отсутствии.
// ТипЗначения - (Описание типов) Для создания нового свойства, при отсутствии.
// НаборСвойств - (СправочникСсылка.НаборыДополнительныхРеквизитовИСведений) Для включения в набор.
// НаименованиеСвойства - (Строка) Для назначения или изменения наименования и заголовка.
Функция ПолучитьСоздатьСвойство(ИмяСвойства, ТипЗначения = Неопределено, Комментарий = "", ЭтоДополнительноеСведение = Истина,
НаборСвойств = Неопределено, НаименованиеСвойства = "") Экспорт
Свойство = Неопределено;
СоздатьСвойство = Ложь;
Запрос = Новый Запрос();
Запрос.УстановитьПараметр("ИмяСвойства", ИмяСвойства);
Запрос.Текст =
"ВЫБРАТЬ
| ДополнительныеРеквизитыИСведения.Ссылка КАК Ссылка
|ИЗ
| ПланВидовХарактеристик.ДополнительныеРеквизитыИСведения КАК ДополнительныеРеквизитыИСведения
|ГДЕ
| ДополнительныеРеквизитыИСведения.Имя = &ИмяСвойства
| И НЕ ДополнительныеРеквизитыИСведения.ПометкаУдаления";
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Свойство = Выборка.Ссылка;
Иначе
Если Не ТипЗначения = Неопределено Тогда
СоздатьСвойство = Истина;
КонецЕсли;
КонецЕсли;
Если СоздатьСвойство Или ЗначениеЗаполнено(НаименованиеСвойства) И Не Свойство.Наименование = НаименованиеСвойства Тогда
Попытка
Если СоздатьСвойство Тогда
СвойствоОбъект = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.СоздатьЭлемент();
Иначе
СвойствоОбъект = Свойство.ПолучитьОбъект();
КонецЕсли;
СвойствоОбъект.Наименование = ?(ЗначениеЗаполнено(НаименованиеСвойства), НаименованиеСвойства, ИмяСвойства);
СвойствоОбъект.Имя = ИмяСвойства;
СвойствоОбъект.Доступен = Истина;
СвойствоОбъект.ТипЗначения = ТипЗначения;
СвойствоОбъект.ЭтоДополнительноеСведение = ЭтоДополнительноеСведение;
СвойствоОбъект.Комментарий = Комментарий;
СвойствоОбъект.Заголовок = СвойствоОбъект.Наименование;
СвойствоОбъект.ДополнительныеЗначенияИспользуются = Истина;
СвойствоОбъект.Виден = Истина;
СвойствоОбъект.НаборСвойств = НаборСвойств;
СвойствоОбъект.Записать();
Свойство = СвойствоОбъект.Ссылка;
Если СоздатьСвойство И ЗначениеЗаполнено(НаборСвойств) Тогда
ИмяТабчасти = ?(ЭтоДополнительноеСведение, "ДополнительныеСведения", "ДополнительныеРеквизиты");
ИмяРеквизитаКоличества = ?(ЭтоДополнительноеСведение, "КоличествоСведений", "КоличествоРеквизитов");
Если НаборСвойств[ИмяТабчасти].Найти(Свойство, "Свойство") = Неопределено Тогда
НаборОбъект = НаборСвойств.ПолучитьОбъект();
ТабЧасть = НаборОбъект[ИмяТабчасти];
НоваяСтрока = ТабЧасть.Добавить();
НоваяСтрока.Свойство = Свойство;
НаборОбъект[ИмяРеквизитаКоличества] = ТабЧасть.Количество();
НаборОбъект.Записать();
КонецЕсли;
КонецЕсли;
Исключение
КонецПопытки;
КонецЕсли;
Возврат Свойство;
КонецФункции
2. Работа со значением свойства.
&НаСервере
Процедура ПримерыВызова(ТаблицаОбъектов) Экспорт
//Для пакетной обработки или создания свойства
Свойство = ПолучитьСоздатьСвойство("УИД_НашегоДокумента", Новый ОписаниеТипов("Строка", , , , Новый КвалификаторыСтроки(36)));
Для Каждого СтрокаТЗ Из ТаблицаОбъектов Цикл
УИД = ПолучитьУстановитьЗначениеСвойства(Свойство, СтрокаТЗ.СсылкаНаОбъект);
НовыйУИД = СтрЗаменить(УИД, "1", "2");
Результат = ПолучитьУстановитьЗначениеСвойства(Свойство, СтрокаТЗ.СсылкаНаОбъект, НовыйУИД);
КонецЦикла;
//Для разового вызова
Если ТаблицаОбъектов.Количество() > 0 Тогда
УИД = ПолучитьУстановитьЗначениеСвойства("УИД_НашегоДокумента", ТаблицаОбъектов[0].СсылкаНаОбъект);
НовыйУИД = СтрЗаменить(УИД, "1", "2");
Результат = ПолучитьУстановитьЗначениеСвойства("УИД_НашегоДокумента", ТаблицаОбъектов[0].СсылкаНаОбъект, НовыйУИД);
КонецЕсли;
КонецПроцедуры
&НаСервере
// Универсальная функция. Получает значение свойства. При заполненном параметре ЗначениеСвойства может свойство установить.
// Свойство - Либо свойство (ПланВидовХарактеристик.ДополнительныеРеквизитыИСведения), либо Имя (строка)
// СсылкаНаОбъект - Объект в регистре сведений "ДополнительныеСведения"
// ЗначениеСвойства - Значение, передаваемое для установки.
Функция ПолучитьУстановитьЗначениеСвойства(Свойство_Или_Имя, СсылкаНаОбъект, ЗначениеСвойства = Неопределено) Экспорт
Ответ = Неопределено;
Если ТипЗнч(Свойство_Или_Имя) = Тип("Строка") Тогда
Свойство = ПолучитьСоздатьСвойство(Свойство_Или_Имя);
Иначе
Свойство = Свойство_Или_Имя;
КонецЕсли;
Если Не Свойство = Неопределено Тогда
Попытка
МЗ = РегистрыСведений.ДополнительныеСведения.СоздатьМенеджерЗаписи();
МЗ.Объект = СсылкаНаОбъект;
МЗ.Свойство = Свойство;
МЗ.Прочитать();
Если Не МЗ.Выбран() Или (Не ЗначениеСвойства = Неопределено И Не МЗ.Значение = ЗначениеСвойства) Тогда
Если Не ЗначениеСвойства = Неопределено Тогда
МЗ.Объект = СсылкаНаОбъект;
МЗ.Свойство = Свойство;
МЗ.Значение = ЗначениеСвойства;
МЗ.Записать();
КонецЕсли;
КонецЕсли;
Если ЗначениеЗаполнено(МЗ.Значение) Тогда
Ответ = МЗ.Значение;
КонецЕсли;
Исключение
КонецПопытки;
КонецЕсли;
Возврат Ответ;
КонецФункции
3. Локализация объекта по уникальному значению свойства.
&НаСервере
Процедура ПримерПолученияОбъекта(УИД) Экспорт
НужныйОбъект = ПолучитьОбъектПоЗначениюСвойства("УИД_НашегоДокумента", УИД, "Документ.ЗаказКлиента");
КонецПроцедуры
&НаСервере
// Универсальная функция. Получает объект определенного типа по уникальному значению свойства.
// Свойство - Либо свойство (ПланВидовХарактеристик.ДополнительныеРеквизитыИСведения), либо Имя (строка)
// ЗначениеСвойства - значение, передаваемое для поиска объекта.
// ИмяМетаданные - Имя метаданных ("Документ.ЗаказКлиента"). Сильно ускоряет запрос.
Функция ПолучитьОбъектПоЗначениюСвойства(Свойство_Или_Имя, ЗначениеСвойства, ИмяМетаданные) Экспорт
Ответ = Неопределено;
Если ТипЗнч(Свойство_Или_Имя) = Тип("Строка") Тогда
Свойство = ПолучитьСоздатьСвойство(Свойство_Или_Имя);
Иначе
Свойство = Свойство_Или_Имя;
КонецЕсли;
Если Не Свойство = Неопределено Тогда
Запрос = Новый Запрос();
Запрос.Текст =
"ВЫБРАТЬ
| ДополнительныеСведения.Объект КАК Объект,
| ДополнительныеСведения.Свойство КАК Свойство,
| ДополнительныеСведения.Значение КАК Значение
|ИЗ
| РегистрСведений.ДополнительныеСведения КАК ДополнительныеСведения
|ГДЕ
| ДополнительныеСведения.Значение = &ЗначениеСвойства
| И ДополнительныеСведения.Свойство = &Свойство
| И ДополнительныеСведения.Объект ССЫЛКА Справочник.Номенклатура";
Запрос.Текст = СтрЗаменить(Запрос.Текст, "Справочник.Номенклатура", ИмяМетаданные);
Запрос.УстановитьПараметр("Свойство", Свойство);
Запрос.УстановитьПараметр("ЗначениеСвойства", ЗначениеСвойства);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Ответ = Выборка.Объект;
КонецЕсли;
КонецЕсли;
Возврат Ответ;
КонецФункции
4. Создание дополнительного реквизита в наборе.
// Создает в базе дополнительный реквизит в наборе, по значению переданных на вход параметров.
// При отсутствии создает набор по имени
// ИмяСвойства - (Строка) Для поиска (создания при отсутствии) свойства.
// ТипЗначения - (Описание типов) Для создания нового свойства, при отсутствии.
// НаименованиеНабора - (Строка) Для поиска (создания при отсутствии) набора свойств
// и для включения дополнительного реквизита в набор.
// ИмяПредопределенногоНабора - Для проявления вновь созданного набора в списке.
// Для полноценного использования (видимости в форме)
// его придется сделать предопределенным.
// МассивЗначений - Массив строковых значений, если хотим сделать перечисление
// на основе справочника "ЗначенияСвойствОбъектов".
// НаименованиеСвойства - (Строка) (если хотим, чтобы наименование и заголовок реквизита отличались от имени)
Процедура СоздатьДопРеквизит(ИмяСвойства, ТипЗначения, НаименованиеНабора, ИмяПредопределенногоНабора = "",
МассивЗначений = Неопределено, НаименованиеСвойства = "") Экспорт
Набор = Справочники.НаборыДополнительныхРеквизитовИСведений.НайтиПоНаименованию(НаименованиеНабора);
Если Не ЗначениеЗаполнено(Набор) Тогда
НаборОбъект = Справочники.НаборыДополнительныхРеквизитовИСведений.СоздатьЭлемент();
НаборОбъект.Наименование = НаименованиеНабора;
НаборОбъект.ИмяПредопределенногоНабора = ИмяПредопределенногоНабора;
НаборОбъект.Используется = Истина;
НаборОбъект.Записать();
Набор = НаборОбъект.Ссылка;
КонецЕсли;
Если ЗначениеЗаполнено(ИмяПредопределенногоНабора) И Не Набор.ИмяПредопределенногоНабора = ИмяПредопределенногоНабора Тогда
НаборДополнительныхРеквизитов = Набор.ПолучитьОбъект();
НаборДополнительныхРеквизитов.ИмяПредопределенногоНабора = ИмяПредопределенногоНабора;
НаборДополнительныхРеквизитов.Записать();
КонецЕсли;
Свойство = ПолучитьСоздатьСвойство(ИмяСвойства, ТипЗначения, , Ложь, Набор, НаименованиеСвойства);
Если Не Свойство = Неопределено И Не МассивЗначений = Неопределено Тогда
ТаблицаНужныхЗначений = Новый ТаблицаЗначений();
ТаблицаНужныхЗначений.Колонки.Добавить("Наименование", Новый ОписаниеТипов("Строка", , , , Новый КвалификаторыСтроки(100)));
Для Сч = 1 По МассивЗначений.Количество() Цикл
ТаблицаНужныхЗначений.Добавить();
КонецЦикла;
ТаблицаНужныхЗначений.ЗагрузитьКолонку(МассивЗначений, "Наименование");
ЗапросЗначения = Новый Запрос();
ЗапросЗначения.УстановитьПараметр("Владелец", Свойство);
ЗапросЗначения.УстановитьПараметр("ТаблицаНужныхЗначений", ТаблицаНужныхЗначений);
ЗапросЗначения.Текст = "ВЫБРАТЬ
| ТаблицаНужныхЗначений.Наименование КАК Наименование
|ПОМЕСТИТЬ ТаблицаНужныхЗначений
|ИЗ
| &ТаблицаНужныхЗначений КАК ТаблицаНужныхЗначений
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ТаблицаНужныхЗначений.Наименование КАК Наименование
|ИЗ
| ТаблицаНужныхЗначений КАК ТаблицаНужныхЗначений
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
| ПО ТаблицаНужныхЗначений.Наименование = ЗначенияСвойствОбъектов.Наименование
| И (ЗначенияСвойствОбъектов.Владелец = &Владелец)
|ГДЕ
| ЗначенияСвойствОбъектов.Наименование ЕСТЬ NULL";
Результат = ЗапросЗначения.Выполнить();
Если Не Результат.Пустой() Тогда
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
НовоеЗначение = Справочники.ЗначенияСвойствОбъектов.СоздатьЭлемент();
НовоеЗначение.Владелец = Свойство;
НовоеЗначение.Наименование = Выборка.Наименование;
НовоеЗначение.Записать();
КонецЦикла;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
5. Напоследок, пример использования. Создание необходимых свойств (при отсутствии) при запуске обработки. Пример из моей публикации по СДЭК.
&НаСервере
Процедура СоздатьНеобходимыеДопСвойстваПриИхОтсутствии() Экспорт
ТипЗначения = Новый ОписаниеТипов("Строка", , , , Новый КвалификаторыСтроки(36));
ПолучитьСоздатьСвойство("СДЭК_КодПВЗ", ТипЗначения, , , , "Код ПВЗ СДЭК");
ПолучитьСоздатьСвойство("СДЭК_КодТарифа", ТипЗначения, , , , "Код тарифа СДЭК");
ПолучитьСоздатьСвойство("СДЭК_УИД", ТипЗначения, , , , "УИД от СДЭК");
ПолучитьСоздатьСвойство("СДЭК_УИД_ШК", ТипЗначения, , , , "УИД ШК от СДЭК");
ПолучитьСоздатьСвойство("СДЭК_ИД", ТипЗначения, , , , "Account");
ПолучитьСоздатьСвойство("СДЭК_ApiKey", ТипЗначения, , , , "Secure password");
ПолучитьСоздатьСвойство("СДЭК_Сайт", ТипЗначения, , , , "Сайт");
ПолучитьСоздатьСвойство("СДЭК_ВерсияAPI", ТипЗначения, , , , "Версия");
ТипЗначения = Новый ОписаниеТипов("Строка", , , , Новый КвалификаторыСтроки(500));
ПолучитьСоздатьСвойство("СДЭК_ОшибкиЗапроса", ТипЗначения, , , , "Сообщение СДЭК");
ПолучитьСоздатьСвойство("СДЭК_URL_ШК", ТипЗначения, , , , "URL ШК СДЭК");
ТипЗначения = Новый ОписаниеТипов("Булево");
ПолучитьСоздатьСвойство("Это_СДЭК", ТипЗначения, , , , "Признак СДЭК");
ТипЗначения = Новый ОписаниеТипов("Число");
ПолучитьСоздатьСвойство("СДЭК_Порт", ТипЗначения, , , , "Порт");
ПолучитьСоздатьСвойство("СДЭК_Вес", ТипЗначения, , , , "Вес");
ПолучитьСоздатьСвойство("СДЭК_Глубина", ТипЗначения, , , , "Глубина");
ПолучитьСоздатьСвойство("СДЭК_Ширина", ТипЗначения, , , , "Ширина");
ПолучитьСоздатьСвойство("СДЭК_Высота", ТипЗначения, , , , "Высота");
КонецПроцедуры
Ну вот и все. Спасибо за внимание. Надеюсь, что публикация будет полезной и кого-нибудь на что-нибудь сподвигнет. Удачи.