Перейти к основному содержанию
`Ctrl`+`K`

# Универсальный фильтр

## Создание дополнительной группы фильтрации по классу

1. На операции `lazyInitFilter` инициализируем дополнительную группу.

       override def lazyInitFilter(): Unit = {
         if (!fltManager.isPopulatedRootGroup) {
           Btk_FltPkg().createRootGroupByClass(fltManager, "Bs_Goods", "ТМЦ")
         }
         super.lazyInitFilter()
       }

2. На `onApplyFiler` получаем текст фильтрации по группе.

       override def onApplyFilter(): Unit = {
         super.onApplyFilter()
         //проверяем, что группы инициализированы, и фильтр активен в выборке
         if (fltManager.isPopulatedRootGroup && fltManager.isActive) {
           val alias = "tt"
           val macros = Btk_FltPkg().generateMacroByGroup(fltManager, "Bs_Goods", alias)
           selection.setMacro("GdsMacro", macros.where)
         }
       }

3. В операции `onRefresh` используем установленный в `onApplyFiler` макрос:

   - Через метод `prepareSelectStatement`.

         override protected def onRefresh: Recs = {
         prepareSelectStatement("&GdsMacro")
         }

   - В тексте sql-запроса.

         override protected def onRefresh: Recs = {
         """
           select .....
             from .....
            where ....
              and &GdsMacro
         """
         }

## Создание дополнительной группы фильтрации с произвольными атрибутами

1. На операции `lazyInitFilter` инициализируем дополнительную группу.

       override def lazyInitFilter(): Unit = {
         if (!fltManager.isPopulatedRootGroup) {
            Btk_FltPkg().createCustomRootGroup(fltManager, "Some_CustomGroup", "Произвольная группа", (builder) => {
              builder.addRefObjectAttr("idGds", "Тмц", "Bs_Goods")
              builder.addBasicAttr("sCode", "Код", AttributeTypes.Varchar, false)
            })
         }
         super.lazyInitFilter()
       }

2. На `onApplyFiler` получаем текст фильтрации по группе.

       override def onApplyFilter(): Unit = {
         super.onApplyFilter()
         //проверяем, что группы инициализированы и фильтр активен в выборке
         if (fltManager.isPopulatedRootGroup && fltManager.isActive) {
           val macros = Btk_FltPkg().generateMacroByGroup(fltManager, "Some_CustomGroup", "tt")
           selection.setMacro("SomeMacro", macros.where)
         }
       }

3. В операции `onRefresh` используем установленный в `onApplyFiler` макрос:

   - Через метод `prepareSelectStatement`.

         override protected def onRefresh: Recs = {
         prepareSelectStatement("&SomeMacro")
         }

   - В тексте sql-запроса.

         override protected def onRefresh: Recs = {
         """
           select .....
             from .....
            where ....
              and &SomeMacro
         """
         }

## Добавление JSON-атрибуту признака раскрываемости в дереве атрибутов и формирование атрибутов, отображающихся при его раскрытии

    def afterBuildFltEntityMeta(fltMetaEntity: FltMetaEntity, fltManager: FltManager): Unit = {
      //проверка, что переданный класс - класс, для которого нужно добавить логику
      if (fltMetaEntity.name == "Bs_Goods") {
        //определяем, что json-атрибут доступен по умолчанию.
        val typeSizeAttrOpt = fltMetaEntity.attrMap.get("jTypeSizeAttrs".toLowerCase)
        if (typeSizeAttrOpt.isDefined) {
          val typeSizeAttr = typeSizeAttrOpt.get
          //ставим признак, что он может быть раскрыт в дереве
          Btk_FltMetaAttributePkg().setCanExpand(typeSizeAttr, true)

          //формируем атрибуты-потомки
          for (rvx <- new OQuery(Gds_TypeSizeCharacteristicAta.Type) {
          }) {
            //создаем значимые атрибуты, которые хранятся в json-контейнере
            val fltAttr = Btk_FltMetaAttributePkg().buildBasicJObjectAttr(
              systemName = rvx.get(_.sCode),
              caption = rvx.get(_.sCaption),
              dbType = AttributeTypes.Number,
              isBoolean = false,
              jsonColumnName = "jTypeSizeAttrs"
            )

            //устанавливаем созданному атрибуту потомка.
            Btk_FltMetaAttributePkg().setParentTree(fltAttr, typeSizeAttr)

            //добавляем атрибут в общий перечень атрибутов.
            fltMetaEntity.attrMap(fltAttr.systemName.toLowerCase) = fltAttr
          }
        }
      }
    }

Примечание

Каждый из таких атрибутов формирует условие, которое получает значение из json-контейнера.

## Добавление значимому атрибуту признака ссылочности и признака раскрываемости в дереве атрибутов

    def afterBuildFltEntityMeta(fltMetaEntity: FltMetaEntity, fltManager: FltManager): Unit = {
      // Проверка, что переданный класс - класс, для которого нужно добавить логику.
      if(fltMetaEntity.name == "Bs_Goods") {
          // Вызываем из пакета `Btk_FltMetaAttributePkg` метод для создания значимого атрибута `buildBasicAttr`.
          val fltAttr = Btk_FltMetaAttributePkg().buildBasicAttr(
            systemName = "screateuser_dz",              // systemName — системное имя атрибута. Уникально в пределах одного класса,
            caption = "Создавший пользователь",         // caption — отображаемое наименование,
            dbType = AttributeTypes.Varchar,            // dbType — тип данных,
            isBoolean = false)                          // isBoolean — булевый атрибут.

          // Вызываем из пакета `Btk_FltMetaAttributePkg` метод для установки параметров преобразования атрибута в ссылочный `setExpandableParams`.
          Btk_FltMetaAttributePkg().setExpandableParams(
            fltAttr = fltAttr,                          // fltAttr — атрибут фильтра, который преобразуется в ссылочный,
            selfAttrName = "screateuser_dz",            // selfAttrName — имя поля в текущем объекте (self), используемое в выражении selfJoinExpression,
            selfJoinExpression = "lower(${column})",    // selfJoinExpression — выражение, которое формирует правую часть условия JOIN, используя поле текущего объекта,
            childAttrName = "susername",                // childAttrName — имя поля в дочернем объекте (child), используемое в выражении childJoinExpression,
            childJoinExpression = "lower(${column})",   // childJoinExpression — выражение, которое формирует левую часть условия JOIN, используя поле дочернего объекта,
            refClass = "Btk_User")                      // refClass — имя класса (таблицы), к которой делается JOIN.
          // Здесь формируется join по атрибутам с приведением значений к нижнему регистру (через lower).
          // ${column} — подставляет алиас таблицы и имя поля, которое указываем в `selfAttrName` или `childAttrName`
          // в зависимости от выражения `selfJoinExpression` подставляется `selfAttrName`, а к `childJoinExpression` подставляется `childAttrName`.
          // В результате получается запрос вида:
          //  join Btk_User t94 on lower(t94.susername) = lower(t93.screateuser_dz)

          // Ставим признак, что атрибут может быть раскрыт в дереве.
          Btk_FltMetaAttributePkg().setCanExpand(fltAttr, true)

          // Добавляем атрибут в общий перечень атрибутов.
          fltMetaEntity.attrMap(fltAttr.systemName.toLowerCase) = fltAttr
      }
    }

## Скрытие группы «Отбор»

Для скрытия группы «Отбор» необходимо в операции инициализации фильтра `lazyInitFilter` установить флаг `ru.bitec.app.btk.flt.FltAviManager#isAvailableMainGroup` в значение `False`.

Пример кода:

    override def lazyInitFilter(): Unit = {
      if (!fltManager.isPopulatedRootGroup) {
        fltManager.isAvailableMainGroup = false
      }
      super.lazyInitFilter()
    }

## Как переопределить параметры события

Для передачи параметров в выборку сеттера ссылочного атрибута необходимо переопределить обработку событий в прикладной выборке, а именно функцию `EntityAviDefault#handleFltEvents`. Параметры, которые могут быть переопределены, представлены изменяемыми коллекциями для удобства работы:

- `setterParams: mutable.Map[String, Any]` - список параметров, которые передаются в выборку клиентского сеттера без изменений.

- `availableValues: mutable.ArrayBuffer[Any]` - список допустимых id или gid, определяющий возможные значения атрибута фильтра. Ограничивает список, открывающийся «по трём точкам».

    override def handleFltEvents(event: FltEvent): Unit = {
      event match {
        // событие перед установкой значения ссылочного атрибута из интерфейса
        case event: FltBeforeClientSetRefObjectEvent =>
          event.setterParams.update("FLT_IDDEPOWNER", 47606.nl)
          // добавление к полученному от атрибута списку разрешённых идентификаторов дополнительных значений
          event.availableValues += 132021.nl
          // очистка и составление НОВОГО списка идентификаторов
          event.availableValues.clear()
          event.availableValues ++= Array(1.nl, 2.nl)
        case _ =>
      }
    }

## Настройка поля выбора в прикладной выборке с типом сравнения «Как в универсальном фильтре»

В описанном примере будет создан выбор из справочника `ТМЦ`, и настройки фильтрации будут хранится в поле `jData`.

### Настройка выборки

1. Создайте в классе поле с типом `jsonb` для хранения настроек.

2. В `Avi` объявите структуру, которая будет дополнять запрос в `onRefresh` дополнительными полями.

         /**
       * Класс с дополнительными полями
       * @param sCompareKindName Имя типа сравнения. Невидимый
       * @param sCompareKindCaption Наименование типа сравнения
       * @param sFilterValue Значение фильтра
       * @param sFilterValueEditor Тип редактора для значения фильтра. Невидимый.
       * @param bFilterValueRo Признак, что значение фильтра не доступно для редактирования. Невидимый.
       */
       case class Row(
                      var sCompareKindName: NString,
                      var sCompareKindCaption: NString,
                      var sFilterValue: NString,
                      var sFilterValueEditor: NString,
                      var bFilterValueRo: Boolean
                    )

3. В `Avi` в операции `onRefresh` добавьте вывод дополнительных полей, необходимых для работы фильтра. Воспользуйтесь методом: `ru.bitec.app.btk.flt.selected.attribute.standalone.Btk_FltStandaloneLib#getDisplaySettings`.

       override def onRefresh: Recs = {
         val arrBuffer: ArrayBuffer[(Btktst_FltStandaloneApi#ApiRop, Row)] = ArrayBuffer()

         //делаем запрос объектов из БД
         for (rop <- new OQuery(Btktst_FltStandaloneAta.Type) {
         }) {
           //создаем атрибут, ссылочный на ТМЦ, и загружаем в него текущие настройки из БД
           val standaloneAttribute = FltStandaloneAttribute.refAttr(
             sRefClass = "Bs_Goods",
             sData = rop.get(_.jData)
           )

           //получаем настройки отображения атрибута в выборке
           val displaySettings = Btk_FltStandaloneLib().getDisplaySettings(standaloneAttribute)

           //добавляем дополнительные поля в результат onRefresh
           arrBuffer += ((
             rop,
             Row(
               sCompareKindName = displaySettings.sCompareKindName,
               sCompareKindCaption = displaySettings.sCompareKindCaption,
               sFilterValue = displaySettings.sFilterValue,
               sFilterValueEditor = displaySettings.sFilterValueEditor,
               bFilterValueRo = displaySettings.bFilterValueRo
             )
           ))
         }

         arrBuffer.toVector
       }

4. В `Avi` в операции `onRefreshItem` также добавьте вывод дополнительных полей.

       override def onRefreshItem: Recs = {
         val rop = thisRop()

         //создаем атрибут, ссылочный на ТМЦ, и загружаем в него текущие настройки из БД
         val standaloneAttribute = FltStandaloneAttribute.refAttr(
           sRefClass = "Bs_Goods",
           sData = rop.get(_.jData)
         )

         //получаем настройки отображения атрибута в выборке
         val displaySettings = Btk_FltStandaloneLib().getDisplaySettings(standaloneAttribute)
         (
           rop,
           Row(
             sCompareKindName = displaySettings.sCompareKindName,
             sCompareKindCaption = displaySettings.sCompareKindCaption,
             sFilterValue = displaySettings.sFilterValue,
             sFilterValueEditor = displaySettings.sFilterValueEditor,
             bFilterValueRo = displaySettings.bFilterValueRo
           )
         )
       }

5. В `Avi` в операции `checkWorkability` добавьте блокировку поля ввода значения.

       override def checkWorkability(): Unit = {
         super.checkWorkability()
         selection.attrs("sFilterValue").isReadOnly = selection.getSelfVar("bFilterValueRo").asBoolean()
       }

6. Добавьте сеттеры. Важно добавить их с признаками вызова `refreshAfter` и `cwaAfter`.

       @Setter(refreshAfter = true, cwaAfter = true)
       def setsCompareKindName(event: SetterEvent): Unit = {
         val res = Btk_FltStandaloneLib().setCompareKindName(
           FltStandaloneAttribute.refAttr("Bs_Goods", thisRop().get(_.jData)),
           event
         )
         //если были внесены изменения в настройку, то сохраняем ее в БД
         if (res.hasChange) {
           thisApi().setjData(thisRop(), res.attributeValue.buildJson().toNString)
         }
       }

       @Setter(refreshAfter = true, cwaAfter = true)
       def setsCompareKindCaption(event: SetterEvent): Unit = {

       }

       @Setter(refreshAfter = true, cwaAfter = true)
       def setsFilterValue(event: SetterEvent): Unit = {
         val res = Btk_FltStandaloneLib().setFilterValue(
           FltStandaloneAttribute.refAttr("Bs_Goods", thisRop().get(_.jData)),
           event
         )
         //если были внесены изменения в настройку, то сохраняем ее в БД
         if (res.hasChange) {
           thisApi().setjData(thisRop(), res.attributeValue.buildJson().toNString)
         }
        }

7. В `avm.xml` добавьте настройку атрибутов.

       <attributes>
           <attr name="sCompareKindName" isVisible="false"/>
           <attr name="sCompareKindCaption" isVisible="true" order="100" caption="Вид сравнения">
               <editor>
                   <lookup changeableAttr="sCompareKindName" isLookupLazyLoad="true" lookupKeyAttr="sName"
                           lookupListAttr="sCaption" lookupQuery="gtk-Btk_FltAttributeCompareKindAvi#Lookup_ByParams"
                           isResetButtonVisible="true">
                       <params>
                           <param name="names" value="Equal;InEqual;InValueSet;InList;InterValMnemoCode" dataType="String"/>
                       </params>
                   </lookup>
               </editor>
           </attr>
           <attr name="sFilterValue" isVisible="true" order="110" caption="Значение">
               <editor editorTypeAttr="sFilterValueEditor"/>
           </attr>
           <attr name="sFilterValueEditor" isVisible="false"/>
           <attr name="bFilterValueRo" isVisible="false"/>
       </attributes>

   Примечание

   Доступные типы сравнения указываются в теге `<params>` через символ `;`. Перечень системных имен указан в `ru.bitec.app.btk.flt.selected.attribute.FltAttributeCompareKind`.

### Использование в бизнес-логике

1. Пример получения `where-условия` по сохраненной настройке:

       val rop = thisRop()

       //создаем атрибут, ссылочный на ТМЦ, и загружаем в него текущие настройки из БД
       val standaloneAttribute = FltStandaloneAttribute.refAttr(
         sRefClass = "Bs_Goods",
         sData = rop.get(_.jData)
       )

       //сформировать макрос, где все условия будут накладываться на колоноку t.id
       val macros = Btk_FltStandalonePkg().generateMacro(standaloneAttribute, "t.id")

       if (macros.hasFilter) {
         dialogs.showMessage(macros.where)
       } else {
         dialogs.showMessage("Условий не наложено")
       }

2. Пример получения перечня значений по сохраненной настройке:

       val rop = thisRop()

       //создаем атрибут, ссылочный на ТМЦ, и загружаем в него текущие настройки из БД
       val standaloneAttribute = FltStandaloneAttribute.refAttr(
         sRefClass = "Bs_Goods",
         sData = rop.get(_.jData)
       )

       //получить перечень Gid-ов, удовлетворяющих условию
       val valuesOpt = Btk_FltStandalonePkg().getRefAttrValues(standaloneAttribute)

       if (valuesOpt.isDefined) {
         dialogs.showMessage(
           s"""Кол-во: ${valuesOpt.get.size}
               |Первые 20 значений: ${valuesOpt.get.take(20).mkString(";")}
               |""".stripMargin
         )
       } else {
         dialogs.showMessage("Условий не наложено")
       }