Добрый день, уважаемые коллеги!
Недавно столкнулся с необходимостью поддержки множественного выбора в дополнительных реквизитах номенклатуры. К сожалению 1С:Управление торговлей 11.4 "из коробки" не поддерживает этот функционал. Поиск в Интернетах привел к статье @bad_wag Создание множественного дополнительного реквизита. Управление торговлей 11, однако предложенное им решение поддерживает единственный дополнительный реквизит с множественным выбором. Это было не совсем то, что нужно. Точнее совсем не то. Мне требовалась поддержка любого количества дополнительных реквизитов с множественным выбором и пришлось пилить самому.
Для желающих повторить это решение - описываю подробно, а для тех кому лень - в конце статьи есть ссылка для скачивания готового расширения конфигурации.
Если требуется поддержка множественных дополнительных реквизитов в других (кроме Номенклатуры) формах - это делается по аналогии с формой Номенклатуры. Те кому совсем лень - обращайтесь, доработаю за соответствующее вознаграждение.
Итак, поехали!
Включение признака множественного выбора для доп. реквизита
Для того, чтобы пользователь мог задавать реквизиты, для которых он хочет выбирать несколько значений, необходим признак, который будет отличать такие дополнительные реквизиты от других. К сожалению, механизм расширений в 1С на момент написания статьи не поддерживает добавление реквизитов в План видов характеристик, поэтому для хранения значений признака я создал регистр:
В модуле менеджера Плана видов характеристик Дополнительные реквизиты и сведения создал функцию, для более удобного обращения к регистру:
Функция МРек_МножественныйВыбор(Свойство, МножественныйВыбор=Неопределено) Экспорт
Если МножественныйВыбор = Неопределено Тогда
Отбор = Новый Структура("Свойство", Свойство);
Ресурсы = РегистрыСведений.МРек_ФлагиМножественногоВыбора.Получить(Отбор);
Возврат Ресурсы.МножественныйВыборРазрешен;
Иначе
Запись = РегистрыСведений.МРек_ФлагиМножественногоВыбора.СоздатьМенеджерЗаписи();
Запись.Свойство = Свойство;
Запись.Прочитать();
СтароеЗначение = ?(Запись.Выбран(), Запись.МножественныйВыборРазрешен, Ложь);
Запись.Свойство = Свойство;
Запись.МножественныйВыборРазрешен = МножественныйВыбор;
Запись.Записать();
Возврат СтароеЗначение;
КонецЕсли;
КонецФункции
Для отражения значения признака на форме, в ПриСозданииНаСервере создаю реквизит и элемент формы и присваиваю значение из регистра:
&НаСервере
Процедура МРек_ПриСозданииНаСервереПосле(Отказ, СтандартнаяОбработка)
НовыйРеквизит = Новый РеквизитФормы("МРек_МножественныйВыбор", Новый ОписаниеТипов("Булево"),, "Множественный выбор в формах");
ДобавляемыеРеквизиты = Новый Массив();
ДобавляемыеРеквизиты.Добавить(НовыйРеквизит);
ИзменитьРеквизиты(ДобавляемыеРеквизиты);
Элемент = Элементы.Добавить("МРек_МножественныйВыбор", Тип("ПолеФормы"), Элементы.Найти("СтраницаЗначения"));
Элемент.ПутьКДанным = "МРек_МножественныйВыбор";
Элемент.Вид = ВидПоляФормы.ПолеФлажка;
Элемент.ПоложениеЗаголовка = ПоложениеЗаголовкаЭлементаФормы.Право;
ЭтаФорма.МРек_МножественныйВыбор = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.МРек_МножественныйВыбор(Объект.Ссылка);
КонецПроцедуры
Для записи измененного признака обрабатываю событие ПослеЗаписиНаСервере:
&НаСервере
Процедура МРек_ПослеЗаписиНаСервереПосле(ТекущийОбъект, ПараметрыЗаписи)
ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.МРек_МножественныйВыбор(Объект.Ссылка, ЭтаФорма.МРек_МножественныйВыбор);
КонецПроцедуры
Т.к. было принятно решение, что множественный выбор будет поддерживаться только для типа Дополнительные значения (с иерархией и без) то нужно управлять доступностью элемента формы при изменении типа и при открытии формы:
&НаКлиенте
Процедура МРек_ТипЗначенияПриИзмененииПосле(Элемент=Неопределено)
Если Объект.ТипЗначения.Типы().Количество() <> 1
Или Не (
Объект.ТипЗначения.СодержитТип(Тип("СправочникСсылка.ЗначенияСвойствОбъектов"))
Или Объект.ТипЗначения.СодержитТип(Тип("СправочникСсылка.ЗначенияСвойствОбъектовИерархия"))
) Тогда
Элементы.МРек_МножественныйВыбор.Доступность = Ложь;
МРек_МножественныйВыбор = Ложь;
Иначе
Элементы.МРек_МножественныйВыбор.Доступность = Истина;
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура МРек_ПриОткрытииПосле(Отказ)
МРек_ТипЗначенияПриИзмененииПосле();
КонецПроцедуры
С этого момента в настройках дополнительных реквизитов можно устанавливать признак множественного выбора. Осталось научиться правильно отображать их на формах и управлять ими.
Продолжаем:
Доработка модуля УправлениеСвойствамиСлужебный
Функция ЗначенияСвойств(ДополнительныеСвойстваОбъекта, Наборы, ЭтоДополнительноеСведение) возвращает таблицу со значениями свойств объекта, однако, если в табличной части ДополнительныеРеквизиты хранится несколько строк для одного свойства - функция вернет только последнее значение. Это необходимо исправить.
Я выбрал возможно не совсем оптимальных с точки зрения производительности путь, но зато наименее затрагивающий исходный текст функции
1. Добавляем к результату запроса колонку для хранения признака, что значение уже устанавливалось:
ОписаниеСвойств = Запрос.Выполнить().Выгрузить();
ОписаниеСвойств.Индексы.Добавить("Свойство");
ОписаниеСвойств.Колонки.Добавить("Значение");
#Вставка
ОписаниеСвойств.Колонки.Добавить("ЗначениеУстановлено", Новый ОписаниеТипов("Булево"));
#КонецВставки
2. В цикле по дополнительным свойствам объекта если значение уже устанавливалось - новое значение дописываем в массив (предварительно создав):
// Заполнение значений свойств.
Для Каждого Строка Из ДополнительныеСвойстваОбъекта Цикл
...
Иначе
#Удаление
ОписаниеСвойства.Значение = Строка.Значение;
#КонецУдаления
#Вставка
Если ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.МРек_МножественныйВыбор(Строка.Свойство)
И ОписаниеСвойства.ЗначениеУстановлено Тогда
Если ТипЗнч(ОписаниеСвойства.Значение) = Тип("Массив") Тогда
ОписаниеСвойства.Значение.Добавить(Строка.Значение);
Иначе
СтароеЗначение = ОписаниеСвойства.Значение;
ОписаниеСвойства.Значение = Новый Массив;
ОписаниеСвойства.Значение.Добавить(СтароеЗначение);
ОписаниеСвойства.Значение.Добавить(Строка.Значение);
КонецЕсли;
Иначе
ОписаниеСвойства.Значение = Строка.Значение;
ОписаниеСвойства.ЗначениеУстановлено = Истина;
КонецЕсли;
#КонецВставки
КонецЕсли;
...
КонецЦикла;
3. Сразу после этого цикла все значения являющиеся массивами преобразуем в соответствующее количество строк:
...
КонецЦикла;
#Вставка
ВременноеОС = ОписаниеСвойств.СкопироватьКолонки();
Для Каждого Строка Из ОписаниеСвойств Цикл
Если ТипЗнч(Строка.Значение) = Тип("Массив") Тогда
Для Каждого Элемент Из Строка.Значение Цикл
НоваяСтрока = ВременноеОС.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, Строка,, "Значение");
НоваяСтрока.Значение = Элемент;
КонецЦикла;
Иначе
НоваяСтрока = ВременноеОС.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, Строка);
КонецЕсли;
КонецЦикла;
ОписаниеСвойств = ВременноеОС;
#КонецВставки
Возврат ОписаниеСвойств;
КонецФункции
Эти доработки порождают новую проблему. При отображении свойств (дополнительных реквизитов) на форме используются уникальные идентификаторы наборов и свойств, но теперь они у нас могут повторяться, поэтому нам требуется:
Доработка модуля УправлениеСвойствами
Процедура ЗаполнитьДополнительныеРеквизитыВФорме(Форма, Объект, ПоляНадписей, СкрытьУдаленные) создает реквизиты и элементы формы для отображения дополнительных реквизитов (свойств) объекта. Для обеспечения уникальности при отображении множественных значений одного свойства я использовал нумератор в цикле создания реквизитов. Кроме того для дальнейшего удобства управления множественные реквизиты размещаются в группах (имя группы использует уникальные идентификаторы набора и свойства без нумератора, а для его хранения я добавляю колонку к таблице Описание свойств:
&ИзменениеИКонтроль("ЗаполнитьДополнительныеРеквизитыВФорме")
Процедура МРек_ЗаполнитьДополнительныеРеквизитыВФорме(Форма, Объект, ПоляНадписей, СкрытьУдаленные)
...
ОписаниеСвойств.Колонки.Добавить("ИмяРеквизитаЗначение");
ОписаниеСвойств.Колонки.Добавить("СтрокаСсылочногоТипа");
ОписаниеСвойств.Колонки.Добавить("ИмяСсылочногоРеквизитаЗначение");
ОписаниеСвойств.Колонки.Добавить("ИмяУникальнаяЧасть");
ОписаниеСвойств.Колонки.Добавить("ДополнительноеЗначение");
ОписаниеСвойств.Колонки.Добавить("Булево");
#Вставка
ОписаниеСвойств.Колонки.Добавить("ИмяГрупповаяЧасть"); // Для элементов множественного выбора
#КонецВставки
...
// Создание реквизитов.
ДобавляемыеРеквизиты = Новый Массив();
#Вставка
МРек_Счетчик = 1000;
#КонецВставки
Для каждого ОписаниеСвойства Из ОписаниеСвойств Цикл
...
#Удаление
ОписаниеСвойства.ИмяУникальнаяЧасть =
СтрЗаменить(ВРег(Строка(ОписаниеСвойства.Набор.УникальныйИдентификатор())), "-", "x")
+ "_"
+ СтрЗаменить(ВРег(Строка(ОписаниеСвойства.Свойство.УникальныйИдентификатор())), "-", "x");
#КонецУдаления
#Вставка
ОписаниеСвойства.ИмяГрупповаяЧасть =
СтрЗаменить(ВРег(Строка(ОписаниеСвойства.Набор.УникальныйИдентификатор())), "-", "x")
+ "_"
+ СтрЗаменить(ВРег(Строка(ОписаниеСвойства.Свойство.УникальныйИдентификатор())), "-", "x");
ОписаниеСвойства.ИмяУникальнаяЧасть =
ОписаниеСвойства.ИмяГрупповаяЧасть
+ "_"
+ Прав(МРек_Счетчик, 3);
МРек_Счетчик = МРек_Счетчик + 1;
#КонецВставки
...
Далее в цикле создания элементов формы анализирую признак множественного выбора и создаю необходимые группы, элементы управления и команды добавления дополнительного значения для реквизита:
// Создание элементов формы.
Для Каждого ОписаниеСвойства Из ОписаниеСвойств Цикл
...
Иначе
Родитель = ЭлементРазмещения;
КонецЕсли;
#Вставка
МРек_МножественныйВыбор = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.МРек_МножественныйВыбор(ОписаниеСвойства.Свойство);
Если МРек_МножественныйВыбор = Истина Тогда
ИмяГруппыМножественногоВыбора = "ГруппаМножественногоВыбора_" + ОписаниеСвойства.ИмяГрупповаяЧасть;
ИмяГруппыПолей = "ГруппаПолей_" + ОписаниеСвойства.ИмяГрупповаяЧасть;
ГруппаМножественногоВыбора = Форма.Элементы.Найти(ИмяГруппыМножественногоВыбора);
Если Форма.Элементы.Найти(ИмяГруппыМножественногоВыбора) = Неопределено Тогда
ГруппаМножественногоВыбора = Форма.Элементы.Добавить(ИмяГруппыМножественногоВыбора, Тип("ГруппаФормы"), Родитель);
ГруппаМножественногоВыбора.Вид = ВидГруппыФормы.ОбычнаяГруппа;
ГруппаМножественногоВыбора.Отображение = ОтображениеОбычнойГруппы.Нет;
ГруппаМножественногоВыбора.ОтображатьЗаголовок = Истина;
ГруппаМножественногоВыбора.Группировка = ГруппировкаПодчиненныхЭлементовФормы.ГоризонтальнаяЕслиВозможно;
ГруппаМножественногоВыбора.Заголовок = ОписаниеСвойства.Наименование;
ГруппаПолей = Форма.Элементы.Добавить(ИмяГруппыПолей, Тип("ГруппаФормы"), ГруппаМножественногоВыбора);
ГруппаПолей.Вид = ВидГруппыФормы.ОбычнаяГруппа;
ГруппаПолей.Отображение = ОтображениеОбычнойГруппы.Нет;
ГруппаПолей.ОтображатьЗаголовок = Ложь;
ГруппаПолей.Группировка = ГруппировкаПодчиненныхЭлементовФормы.ГоризонтальнаяЕслиВозможно;
ИмяГруппыДобавления = "ГруппаДобавления_" + ОписаниеСвойства.ИмяГрупповаяЧасть;
ИмяКоманды = "Команда_" + ОписаниеСвойства.ИмяГрупповаяЧасть;
ИмяКнопки = "Добавить_" + ОписаниеСвойства.ИмяГрупповаяЧасть;
ГруппаДобавления = Форма.Элементы.Добавить(ИмяГруппыДобавления, Тип("ГруппаФормы"), ГруппаМножественногоВыбора);
ГруппаДобавления.Вид = ВидГруппыФормы.ОбычнаяГруппа;
ГруппаДобавления.Отображение = ОтображениеОбычнойГруппы.Нет;
ГруппаДобавления.ОтображатьЗаголовок = Ложь;
ГруппаДобавления.Группировка = ГруппировкаПодчиненныхЭлементовФормы.ГоризонтальнаяЕслиВозможно;
Команда = Форма.Команды.Добавить(ИмяКоманды);
Команда.Действие = "МРек_ДобавитьЗначениеМножественногоРеквизита";
Команда.Заголовок = "Добавить значение";
Команда.Картинка = БиблиотекаКартинок.ДобавитьЭлементСписка;
Команда.Отображение = ОтображениеКнопки.Картинка;
Кнопка = Форма.Элементы.Добавить(ИмяКнопки, Тип("КнопкаФормы"), ГруппаДобавления);
Кнопка.Заголовок = "Добавить значение";
Кнопка.ИмяКоманды = ИмяКоманды;
Кнопка.ОтображениеФигуры = ОтображениеФигурыКнопки.ПриАктивности;
Иначе
ГруппаПолей = Форма.Элементы.Найти(ИмяГруппыПолей);
КонецЕсли;
Родитель = ГруппаПолей;
КонецЕсли;
#КонецВставки
...
Перед концом цикла для множественных реквизитов устанавливаю видимость кнопки очиски (используется для удаления элемента, если он не последний, хотя достаточно не заполнять элемент, т.к. все лишние пустые элементы будут удалены при сохранении)
...
#Вставка
Если МРек_МножественныйВыбор = Истина Тогда
Элемент.КнопкаОчистки = Истина;
Элемент.КнопкаОткрытия = Ложь;
Элемент.ПоложениеЗаголовка = ПоложениеЗаголовкаЭлементаФормы.Нет;
Элемент.УстановитьДействие("Очистка", "МРек_УдалитьДополнительныйРеквизит");
КонецЕсли;
#КонецВставки
КонецЦикла;
Также доработки требует Процедура УдалитьСтарыеРеквизитыИЭлементы(Форма). В цикле удаления необходимо удалить созданные команды:
Процедура МРек_УдалитьСтарыеРеквизитыИЭлементы(Форма)
УдаляемыеРеквизиты = Новый Массив;
Для каждого ОписаниеСвойства Из Форма.Свойства_ОписаниеДополнительныхРеквизитов Цикл
...
#Вставка
ГрупповаяЧасть = Лев(УникальнаяЧасть, СтрДлина(УникальнаяЧасть)-4);
Команда = Форма.Команды.Найти("Команда_"+ГрупповаяЧасть);
Если Команда <> Неопределено Тогда
Форма.Команды.Удалить(Команда);
КонецЕсли;
#КонецВставки
КонецЦикла;
...
КонецПроцедуры
Дополнительно в модуль добавлена Процедура МРек_УдалениеРеквизитаИСвязанныхЭлементов(Форма, ИмяРеквизита) для удаления реквизита и элементов при нажатии на [х]:
Процедура МРек_УдалениеРеквизитаИСвязанныхЭлементов(Форма, ИмяРеквизита) Экспорт
СтрокаОписания = Форма.Свойства_ОписаниеДополнительныхРеквизитов.НайтиСтроки(Новый Структура("ИмяРеквизитаЗначение", ИмяРеквизита))[0];
Форма.Свойства_ОписаниеДополнительныхРеквизитов.Удалить(СтрокаОписания);
МассивУдаляемых = Новый Массив();
МассивУдаляемых.Добавить(ИмяРеквизита);
Форма.Элементы.Удалить(Форма.Элементы[ИмяРеквизита]);
Форма.ИзменитьРеквизиты(, МассивУдаляемых);
КонецПроцедуры
Доработка модуля УправлениеСвойствамиКлиент
Собственно сам модул не нуждается в доработках, но я разместил в нем пару функций для уменьшения объема кода в формах элементов в которых планируется поддерживать множественные реквизиты.
Доработка справочника Номенклатура
(По аналогии можно доработать любые другие объекты, где должны использоваться множественные дополнительные реквизиты)
Модуль формы элемента
Модуль менеджера
Для отображения неизменяемой карточки товара при открытии элемента номенклатуры используется табличный документ, который создается в модуле менеджера справочника функцией ТабличныйДокументКарточкиНоменклатуры(Номенклатура, НастройкиВидимостиИЗаголовков, ДляПечати)
Для корректного перехода к дополнительным реквизитам при двойном клике его также необходимо доработать введя счетчик реквизитов аналогично модулю УправлениеСвойствами:
...
#Область ДополнительныеРеквизиты
Запрос = Новый Запрос;
Запрос.Текст =
...
#Вставка
МРек_Счетчик = 1000;
#КонецВставки
Для Каждого ДопРеквизит Из ДополнительныеРеквизиты Цикл
...
ИмяЭлементаДопРеквизита = "ДополнительныйРеквизитЗначение_"
+ СтрЗаменить(ВРег(Строка(ДопРеквизит.НаборСвойств.УникальныйИдентификатор())), "-", "x")
+ "_"
#Удаление
+ СтрЗаменить(ВРег(Строка(ДопРеквизит.Свойство.УникальныйИдентификатор())), "-", "x");
#КонецУдаления
#Вставка
+ СтрЗаменить(ВРег(Строка(ДопРеквизит.Свойство.УникальныйИдентификатор())), "-", "x")
+ "_"
+ Прав(МРек_Счетчик, 3);
МРек_Счетчик = МРек_Счетчик + 1;
#КонецВставки
...
Заключение
В результате всего вышеизложенно получаем на форме элемента справочника Номенклатура следующую картину:
Указанные доработки позволяют использовать любое количество дополнительных реквизитов объектов поддерживающих множественный выбор. Получение значений множественного выбора возможно с помощью стандартной функции БСП УправлениеСвойствами.ЗначенияСвойств(...), куда в качестве четвертого параметра можно передать строку с именем дополнительного реквизита. Функция возвращает массив содержащий значения запрошенного (или запрошенных) дополнительных реквизитов и/или сведений.
Пользуйтесь и делитесь впечатлениями.
Обновление 15.10.2023 года
Внесены изменения в модули, заимствованные в режиме "Изменение и контроль" для совместимости с версией конфигурации "Управление торговлей 11.5.14.69"