Создание выборки без класса#

Первичное создание файла выборки#

  1. В нужной дирректории проекта (имя_модуля/src/main/scala/ru/bitec/app/имя_модуля/...) создайте Scala класс, задайте ему имя в формате <имя модуля>_<имя выборки>Avi, например Mct_ExampleAvi.

    • Класс должен наследоваться от EntityAvi.

    • В файле должен присутствовать объект-компаньон, с его помощью в дальнейшем будут создаваться объекты целевых выборок в системе.

    • У класса должна присутствовать аннотация avm-файла, где указывается файл с разметкой выборок. Как создать avm-файл.

    Общий вид файла должен быть следующим:

    package ...
    
    import ...
    
    object Mct_ExampleAvi extends Mct_ExampleAvi
    
    @AvmFile(name = "Mct_Example.avm.xml")
    class Mct_ExampleAvi extends EntityAvi{
        Код выбороки
    }
    

Создание трейта и методы получения данных, выводимых трейтом#

  1. В классе создается трейт, который в дальнейшем будет использован для открытия экземпляра выборки.

    Этот трейт должен наследоваться от одного или более базовых трейтов из EntityAvi, или от созданных вами, например:

    trait List extends Default with super.List
    
    trait MyList extends List
    

    Для каждого трейта должна быть создана функция-компаньон, причем если имя вашего трейта совпадает с именем базового трейта из BaseAvi(наследуется в EntityAvi) - Card, List, Lookup, то функцию компаньон надо переопределять, а если имя иное, то функцию нужно создать:

    override def list(): List = {
        new List {
            override def meta = this
        }
    }
    
    def myList(): MyList = {
        new MyList {
            override def meta = this
        }
    }
    
    • Имя функции-компаньона начинается с прописной буквы, имя трейта с заглавной.

  2. Если трейт создается для отображения данных из базы, тогда необходимо определить метод получения данных для выборки. Для этого существует два подхода:

Реляционный запрос:#

  1. Базово определяется в методе selectStatement.

  2. Тело метода является обычным sql запросом, помещенным в scala-строку.

    override protected def selectStatement: String = {
        s"""
            |select
            |  st.id
            |  ,st.sCaption
            |  ,st.sCode
            |  ,ot.sDescription as otherDescription
            |from someTable st
            |join otherTable ot on st.someParam = ot.otherParam
            |""".stripMargin
    }
    
  3. Если итоговый набор данных требуется отфильтровать при помощи данных, переданных извне(обычно из фильтров и параметров выборок), и запрос имеет линейную структуру(нет вложенных запросов, временных таблиц и т.д., в которых требуется фильтрация по внешним данным), тогда:

    • Переопределяем метод onRefresh следующим образом:

      override protected def onRefresh: Recs = {
          prepareSelectStatement(s"&DefFltReferenceMacro and someStatement and ${someScalaCode}")
      }
      
    • DefFltReferenceMacro - стандартное имя макроса соответствующего трейта из разметки avm-файла. Вы можете задать любое другое удобное вам имя макроса.

    • someStatement - дополнительные условия в формате sql.

    • someScalaCode - любой Scala-код, результатом работы которого является булевое значение или некие данные, которые подставляются в логическое выражение sql с одной из сторон:

      s"....
      ... t.idState = ${getSelfVar("idState").asNLong}
      ...." 
      

Объектный запрос:#

  1. Базово определяется в методе onRefresh.

    Примечание

    Метод onRefresh возвращает как строку в виде sql-запроса, так и набор Scala-объектов, определяющих набор данных выборки

  2. В теле метода создается некоторая коллекция однотипных объектов:

    override protected def onRefresh: Recs = {
        //***_SomeClassApi().load(id)
        //***_SomeClassApi().byParent(parentRop[или parentId])[.toList[.filter[Not][.orderBy]]]
        //someTXIndex().byKey(TXIndexKey)[.toList[.filter[Not][.orderBy]]]
        new OQuery(***_SomeClassAta.Type) {
            where(t.someClassAttr === someScalaVAlue [and[or] ....])
        }
    }
    
    • При помощи метода load - если требуется получить единственный объект (обычно используется в карточных выборках).

    • При помощи метода byParent - если класс является коллекцией, с передачей в него id или rop родительского объекта.

    • При помощи транзакционного индекса по переданному ключу - если класс не является коллекцией, или если нужно получить элементы коллекции не в разрезе предка, а в разрезе иного(-ых) атрибута(-ов) класса.

    • При помощи запроса OQuery.

    Последние три подхода выбирайте на ваше усмотрение, в зависмиости от обстоятельств. Полученный в итоге набор rop класса можно финально отфильтровать при помощи методов filter[Not], отсортировать при помощи orderBy.

    В отличие от реляционного запроса, предыдущие четыре подхода формируют набор данных в формате списка rop определенного класса и содержат только данные этого класса. Если требуется добавить данные, не принадлежащие классу, то:

    1. Если нужно получить данные без обработки методами api, тогда можете пеоерпределить метод onRefreshExt:

      override protected def onRefreshExt: String = {
          s"""
          with t as ( select
              :idParent as idParent
              :gid/*@NString*/ as gid
              .....
          )
          SELECT
              t1.sHeadline as idParentHL
              ,.....
          FROM t
          join parent_table t1 on t.idParent = t1.id....
          """
      }
      
      • Во временной таблице t указываем, какие данные забираем из основного набора данных. Этот запрос выполняется для каждой отдельной rop-ы. Далее при помощи обычного sql запроса вытягиваем дополнительные данные.

      • Для gid атрибутов указываем аннотацию /*@NString*/, что бы парсер корректно передал данные с типом NGid.

    2. Если нужно получить данные обработанные методами api:

      1. В текущем трейте или его предке создается case class с атрибутами, которые вам требуется добавить на выборки:

        case class AdditionalInfo(
                var someIdParameter: NLong,
                var someIdParameterHL: NString,
                var someStrParameter: NString,
                var someDateParameter: NDate,
                var someBoolParameter: NNumber,
                var someNumParameter: NNumber
                )
        
        • Обычно класс называется AdditionalInfo, но вы можете выбрать и иное имя.

        • someIdParameter - ссылочный атрибут, добавляется для полноты данных выборки, обычно является скрытым (смотри пояснение свойства editorType, настройки changeableAttr ), пользователю не виден.

        • someIdParameterHL[или MC] - заголовок ссылочного атрибута (MC для кодов/мнемокодов), данные ссылочного атрибута, отображаемые пользователю.

        • someStrParameter - иные строковые данные.

        • someDateParameter - дата.

        • someBoolParameter - булевые данные, обычно на выборку передаются в виде данных в формате NNumber: 0 - ложное значение, остальные - истина. Используются в атрибутах с редактором check (смотри пояснение свойства editorType).

        • someNumParameter - иные числовые данные.

      2. После создается метод getAdditionalInfo(rop):

        def getAdditionalInfo(rop: ***_SomeClassApi#ApiRop): AdditionalInfo = {
            //Получение и обработка данных
            AdditionalInfo(
                someIdParameter = ...
                someIdParameterHL = ...
                .......................
                someNumParameter = ...
            )
        }
        
      3. Дополнительные данные добавляются в кортеж к изначальным данным выборки:

        override protected def onRefresh: Recs = {
            val rop = ***_SomeClassApi().load(id)
            (rop, getAdditionalInfo(rop))
        }
        или
        override protected def onRefresh: Recs = {
            ***_SomeClassApi().byParent(parentRop)//Или любые другие из трех перечисленных методов получения набора данных класса
                .map(rop => (rop, getAdditionalInfo(rop)))
        }
        

Создание трейта для получения данных от пользователя#

Иногда требуется создать выборку, единственной целью которой является получение данных от пользователя. В таком случае:

  1. Метод onRefresh выглядит следующим образом:

    override protected def onRefresh: Recs = {
        "select 1 as id"
    }
    

    Тк у выборки всегда должен присутствовать атрибут id для корректной работы

  2. У выборки в avm-разметке создаем фильтры, которые будет заполнять пользователи.

После введения данных пользователем существует несколько вариантов их обработки:

  • Пользователь закрывает выборку

    1. Если введенные пользователем данные не требуется возвращать в метод, вызвавший открытие текущей формы, то на закрытие выборки по согласию пользователя срабатывает метод closeFormOk(), соответственно в его переопределении в вашем трейте можно обработать введенные данные.

    2. Если данные требуется вернуть в метод, вызвавщий открытие текущей формы, то на закрытие выборки переопределяем метод beforeCloseForm:

      override def beforeCloseForm(event: BeforeCloseFormEvent): Unit = {
          event.lookupResultObject = someObjectWithUserData
          super.beforeCloseForm(event)
      }
      
      • Где someObjectWithUserData - некая структура, хранящая пользовательские данные. Может быть List-ом, кортежем, case class-ом и т.д. на ваш выбор.

      • Получить такие данные можно в методе, вызвавшем открытие выборки, определив тип возвращаемых данных:

        val data = Mct_ExampleAvi.myList().newForm().params(Map(...)).results(List(attributesName)).openLookup() 
        val res = data.getResultObject.asInstanceOf[someObjectWithUserData]
        

        Примечание

        Здесь someObjectWithUserData - либо тот же самый тип, если есть возможность типизировать возвращаемое значение тем же классом, либо новая структура, схожая по строению, если, например, в lookupResultObject сохранен объект case class-а, недоступного в месте вызова выборки, тогда достаточно типизировать этот объект кортежем(или, например, листом, если в lookupResultObject записан лист), у которого вложенные типы следуют в том же порядке, что и у case class-а:

        saved case class example(NLong, Boolean, List[NString]) -> Tuple3(NLong, Boolean, List[NString])

        saved List[(case class example, NLong, …)] -> List[(Tuple3(NLong, Boolean, List[NString]), NLong, …)]

  • Пользователь вызывает кастомную операцию на текущей выборке:

    • В этом случае требуется создать данную операцию, если ее еще нет, в текущем трейте или его предках, либо же переопределить такую операцию и забирать введенные пользователем данные напрямую из выборки при помощи методов getVar("...")/getSelfVar("...") и обрабатывать любым удобным вам способом.

Комбинированный подход#

  • Вы можете использовать комбинацию предыдущих пунктов в любом удобном вам виде - выводить какие-либо требуемые вам данные и получать от пользователя введенные им данные.