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

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

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

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("...")` и обрабатывать любым удобным вам способом.

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

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