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

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

  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("Условий не наложено")
    }