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

Программист 1С v8.3 (Управляемые формы)
← На главную

При работе с документами в 1С часто возникает необходимость ограничить выбор элементов справочника в табличной части — для этого подойдёт расширение для настройки обязательности и условий контроля реквизитов. Например, в одной табличной части нам нужны только услуги, а в другой – только товары, при этом все они хранятся в одном иерархическом справочнике "Номенклатура". Давайте рассмотрим, как мы можем реализовать такой отбор, используя различные подходы, опираясь на лучшие практики и рекомендации.

Нашей задачей является создание двух табличных частей в документе: "Товары" и "Услуги". При добавлении строки в табличную часть "Услуги" необходимо, чтобы в списке выбора отображались только элементы, входящие в группу "Услуги" справочника "Номенклатура". И наоборот, при добавлении строки в табличную часть "Товары", в списке выбора должны быть доступны все элементы, кроме тех, что относятся к группе "Услуги". По сути, нам нужен отбор по родителю или по другому классифицирующему признаку — задача решается через инструмент централизованного управления логикой заполнения и контроля реквизитов.

Подход 1: Отбор по группе (родителю) справочника

Этот подход является наиболее очевидным, когда номенклатура уже классифицирована по иерархии групп — для удобства работы с такими списками есть подбор номенклатуры с дополнительными фильтрами и остатками. Мы будем использовать поле Родитель справочника Номенклатура для формирования отбора.

Как получить группу для отбора

Прежде чем установить отбор, нам потребуется ссылка на группу "Услуги". Есть несколько способов получить эту ссылку:

  1. "Зашить" код или наименование группы в конфигурации: Это самый простой, но наименее гибкий способ, как было предложено в сообщении 3. Если код или наименование группы изменится, придется менять код. В обучающих целях или для однопользовательских конфигураций это может быть приемлемо. Например:
    
    ГруппаУслуги = Справочники.Номенклатура.НайтиПоНаименованию("Услуги", Истина); // Истина - поиск по группам
    // Или
    // ГруппаУслуги = Справочники.Номенклатура.НайтиПоКоду("000000001");
    
  2. Использовать Константу: Более предпочтительный вариант, чем "зашивать" в код. Мы создаем константу, например, ГруппаНоменклатурыУслуги с типом СправочникСсылка.Номенклатура. Пользователь сам выбирает нужную группу в режиме Предприятия, и ссылка на неё хранится в константе. Это повышает гибкость, так как при изменении группы не нужно менять код, достаточно обновить константу. ГруппаУслуги = Константы.ГруппаНоменклатурыУслуги.Получить();
  3. Использовать предопределенный элемент справочника (если бы группа была предопределенной): Хотя группы обычно не являются предопределенными элементами, если бы вы создали специальный справочник для видов номенклатуры с предопределенными элементами, это был бы один из самых надежных способов.

Событие для установки отбора

Для установки отбора при выборе элемента в табличной части документа мы будем обрабатывать событие ОбработкаВыбора поля ввода, которое привязано к колонке с номенклатурой — это удобнее делать через инструмент для быстрого подбора по частям наименования и статусам. Это событие вызывается перед открытием стандартной формы выбора.

Предположим, у нас есть документ, и в его модуле формы мы хотим настроить отбор для колонки "Номенклатура" в табличных частях "Товары" и "Услуги".

Пример кода: Отбор по одной группе (для ТЧ "Услуги")

Сначала найдем нужную группу. В данном примере будем использовать константу ГруппаНоменклатурыУслуги.


&НаКлиенте
Процедура УслугиНоменклатураОбработкаВыбора(Элемент, ВыбранноеЗначение, СтандартнаяОбработка)
    // Отменяем стандартную обработку, чтобы вручную открыть форму выбора
    СтандартнаяОбработка = Ложь;

    // Получаем ссылку на группу "Услуги" из константы
    // Предполагаем, что константа Константы.ГруппаНоменклатурыУслуги уже создана и заполнена
    ГруппаУслуги = Константы.ГруппаНоменклатурыУслуги.Получить();

    // Проверяем, что группа найдена
    Если НЕ ГруппаУслуги.Пустая() Тогда
        // Создаем объект Отбор для формы выбора
        Отбор = Новый ОтборКомпоновкиДанных;

        // Создаем элемент отбора по полю "Родитель"
        ЭлементОтбора = Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
        ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Родитель");
        ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
        ЭлементОтбора.ПравоеЗначение = ГруппаУслуги;
        ЭлементОтбора.Использование = Истина;

        // Открываем форму выбора справочника "Номенклатура" с заданным отбором
        ФормаВыбора = Справочники.Номенклатура.ПолучитьФормуВыбора(,,Элемент);
        ФормаВыбора.Отбор = Отбор;
        ФормаВыбора.Открыть();
    Иначе
        ПоказатьПредупреждение(,"Не заполнена константа 'Группа номенклатуры Услуги'!");
    КонецЕсли;
КонецПроцедуры

Пример кода: Отбор "все, кроме группы" (для ТЧ "Товары")

Для табличной части "Товары" нам нужно отобрать все, кроме элементов из группы "Услуги". Здесь мы используем вид сравнения НеРавно.


&НаКлиенте
Процедура ТоварыНоменклатураОбработкаВыбора(Элемент, ВыбранноеЗначение, СтандартнаяОбработка)
    СтандартнаяОбработка = Ложь;

    ГруппаУслуги = Константы.ГруппаНоменклатурыУслуги.Получить();

    Если НЕ ГруппаУслуги.Пустая() Тогда
        Отбор = Новый ОтборКомпоновкиДанных;

        ЭлементОтбора = Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
        ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Родитель");
        ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.НеРавно; // Отбираем все, кроме этой группы
        ЭлементОтбора.ПравоеЗначение = ГруппаУслуги;
        ЭлементОтбора.Использование = Истина;

        ФормаВыбора = Справочники.Номенклатура.ПолучитьФормуВыбора(,,Элемент);
        ФормаВыбора.Отбор = Отбор;
        ФормаВыбора.Открыть();
    Иначе
        ПоказатьПредупреждение(,"Не заполнена константа 'Группа номенклатуры Услуги'!");
    КонецЕсли;
КонецПроцедуры

Важный момент: Отбор по родителю с видом сравнения "НеРавно" может не полностью удовлетворять условию "все, кроме тех, которые находятся в группе 'Услуги'". Если иерархия многоуровневая, и "Услуги" имеют вложенные подгруппы, отбор по Родитель НеРавно ГруппаУслуги исключит только непосредственных потомков этой группы, но не её подгруппы или элементы, находящиеся глубоко в иерархии "Услуги". Для более точного исключения всей ветки иерархии, возможно, потребуется более сложный запрос или использование дополнительного реквизита, как мы рассмотрим далее.

Подход 2: Отбор по дополнительному реквизиту ("Вид номенклатуры")

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

Преимущества этого подхода:

  1. Гибкость: Классификация не привязана к месту в иерархии. Элемент может быть в любой группе, но его "вид" останется неизменным.
  2. Надежность: Менее подвержен ошибкам пользователей при перемещении элементов.
  3. Масштабируемость: Легко добавить новые виды номенклатуры, не меняя структуру иерархии.

Создание реквизита "ВидНоменклатуры"

Мы можем добавить в справочник Номенклатура новый реквизит ВидНоменклатуры. Тип этого реквизита может быть:

  1. Перечисление: Создаем новое перечисление ВидыНоменклатуры со значениями, например, "Товар", "Услуга", "Материал" и т.д. Это просто и эффективно для небольшого количества фиксированных видов.
  2. Справочник: Создаем новый справочник ВидыНоменклатуры, в котором будут храниться элементы "Товар", "Услуга" и т.д. Можно сделать его иерархическим или использовать предопределенные элементы. Этот вариант более гибкий, если список видов часто меняется или требует дополнительных свойств.

Давайте рассмотрим примеры для обоих вариантов.

Пример кода: Отбор по Перечислению (для ТЧ "Услуги")

Предположим, у нас есть перечисление ВидыНоменклатуры со значением Услуга. В справочнике Номенклатура добавлен реквизит ВидНоменклатуры с типом ПеречислениеСсылка.ВидыНоменклатуры.


&НаКлиенте
Процедура УслугиНоменклатураОбработкаВыбора(Элемент, ВыбранноеЗначение, СтандартнаяОбработка)
    СтандартнаяОбработка = Ложь;

    Отбор = Новый ОтборКомпоновкиДанных;

    ЭлементОтбора = Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
    ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("ВидНоменклатуры"); // Имя нашего реквизита
    ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
    ЭлементОтбора.ПравоеЗначение = Перечисления.ВидыНоменклатуры.Услуга; // Значение перечисления
    ЭлементОтбора.Использование = Истина;

    ФормаВыбора = Справочники.Номенклатура.ПолучитьФормуВыбора(,,Элемент);
    ФормаВыбора.Отбор = Отбор;
    ФормаВыбора.Открыть();
КонецПроцедуры

Пример кода: Отбор по Справочнику "ВидыНоменклатуры" (для ТЧ "Товары")

Предположим, у нас есть справочник ВидыНоменклатуры с предопределенным элементом "Услуга". В справочнике Номенклатура добавлен реквизит ВидНоменклатуры с типом СправочникСсылка.ВидыНоменклатуры.


&НаКлиенте
Процедура ТоварыНоменклатураОбработкаВыбора(Элемент, ВыбранноеЗначение, СтандартнаяОбработка)
    СтандартнаяОбработка = Ложь;

    // Получаем ссылку на предопределенный элемент справочника
    ВидУслуга = Справочники.ВидыНоменклатуры.Услуга;

    Отбор = Новый ОтборКомпоновкиДанных;

    ЭлементОтбора = Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
    ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("ВидНоменклатуры");
    ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.НеРавно; // Отбираем все, кроме услуг
    ЭлементОтбора.ПравоеЗначение = ВидУслуга;
    ЭлементОтбора.Использование = Истина;

    ФормаВыбора = Справочники.Номенклатура.ПолучитьФормуВыбора(,,Элемент);
    ФормаВыбора.Отбор = Отбор;
    ФормаВыбора.Открыть();
КонецПроцедуры

Пример кода: Отбор по нескольким видам (использование ФиксированногоМассива)

Что делать, если нам нужно отобрать номенклатуру, которая относится к нескольким видам (например, "Товар" и "Материал"), но исключить "Услугу"? Или, как в сообщении 9 и 10, отобрать по двум видам перечисления? Для этого используем СписокЗначений или ФиксированныйМассив.

Предположим, нам нужно в ТЧ "Товары" видеть только "Товары" и "Материалы".


&НаКлиенте
Процедура ТоварыНоменклатураОбработкаВыбора(Элемент, ВыбранноеЗначение, СтандартнаяОбработка)
    СтандартнаяОбработка = Ложь;

    // Создаем список значений для отбора
    СписокРазрешенныхВидов = Новый СписокЗначений;
    СписокРазрешенныхВидов.Добавить(Перечисления.ВидыНоменклатуры.Товар);
    СписокРазрешенныхВидов.Добавить(Перечисления.ВидыНоменклатуры.Материал);
    // Для справочника "ВидыНоменклатуры" это будет:
    // СписокРазрешенныхВидов.Добавить(Справочники.ВидыНоменклатуры.Товар);
    // СписокРазрешенныхВидов.Добавить(Справочники.ВидыНоменклатуры.Материал);


    Отбор = Новый ОтборКомпоновкиДанных;

    ЭлементОтбора = Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
    ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("ВидНоменклатуры");
    ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.ВСписке; // Указываем, что значение должно быть в списке
    ЭлементОтбора.ПравоеЗначение = СписокРазрешенныхВидов; // Передаем список
    ЭлементОтбора.Использование = Истина;

    ФормаВыбора = Справочники.Номенклатура.ПолучитьФормуВыбора(,,Элемент);
    ФормаВыбора.Отбор = Отбор;
    ФормаВыбора.Открыть();
КонецПроцедуры

Автор сообщения 12 упоминал, что решил проблему через "фиксированный массив". Это как раз относится к подобной ситуации, когда нужно передать набор значений для отбора. ФиксированныйМассив — это оптимизированный для производительности вариант массива, который часто используется в программных отборах.

Общие принципы реализации отбора

Для закрепления понимания, давайте еще раз выделим ключевые моменты, которые мы использовали для установки отбора в форме выбора:

  1. Событие ОбработкаВыбора: Это клиентское событие элемента формы (например, поля ввода в колонке табличной части), которое вызывается непосредственно перед открытием стандартной формы выбора. Это идеальное место для вмешательства в процесс выбора.
  2. СтандартнаяОбработка = Ложь: Внутри события ОбработкаВыбора мы обязательно устанавливаем параметр СтандартнаяОбработка в значение Ложь. Это предотвращает автоматическое открытие формы выбора без наших параметров.
  3. Объект ОтборКомпоновкиДанных: Мы создаем новый объект ОтборКомпоновкиДанных. Он позволяет программно сформировать структуру отбора, аналогичную той, что используется в отчетах или динамических списках.
  4. Элементы отбора: В коллекцию Отбор.Элементы мы добавляем объекты ЭлементОтбораКомпоновкиДанных. Каждый такой элемент описывает одно условие отбора:
    • ЛевоеЗначение: Указывает на поле справочника, по которому будет производиться отбор (например, Новый ПолеКомпоновкиДанных("Родитель") или Новый ПолеКомпоновкиДанных("ВидНоменклатуры")).
    • ВидСравнения: Определяет тип сравнения (например, ВидСравненияКомпоновкиДанных.Равно, .НеРавно, .ВСписке).
    • ПравоеЗначение: Указывает значение или список значений, с которым сравнивается поле.
    • Использование = Истина: Активирует элемент отбора.
  5. Открытие формы выбора: После формирования объекта Отбор, мы получаем стандартную форму выбора справочника (например, Справочники.Номенклатура.ПолучитьФормуВыбора(,,Элемент)), передаем ей наш объект Отбор и открываем форму методом ФормаВыбора.Открыть().

Выбор наилучшего подхода

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

Мы настоятельно рекомендуем использовать подход с дополнительным реквизитом ВидНоменклатуры (будь то Перечисление или Справочник). Этот метод обеспечивает большую гибкость, надежность и устойчивость к изменениям в иерархии справочника. Он соответствует принципам построения типовых конфигураций 1С, как было справедливо отмечено в сообщении 8.

Начните с добавления реквизита ВидНоменклатуры в ваш справочник Номенклатура, заполните его для существующих элементов и затем реализуйте отбор в событиях ОбработкаВыбора, используя ОтборКомпоновкиДанных. При необходимости отбора по нескольким значениям, используйте СписокЗначений или ФиксированныйМассив с ВидСравненияКомпоновкиДанных.ВСписке.

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

← На главную