# Создание выборки без класса ## Первичное создание файла выборки 1. В нужной дирректории проекта (`имя_модуля/src/main/scala/ru/bitec/app/имя_модуля/...`) создайте Scala класс, задайте ему имя в формате `<имя модуля>_<имя выборки>Avi`, например `Mct_ExampleAvi`. ![](img/create_example_avi.jpg) - Класс должен наследоваться от `EntityAvi`. - В файле должен присутствовать объект-компаньон, с его помощью в дальнейшем будут создаваться объекты целевых выборок в системе. - У класса должна присутствовать аннотация avm-файла, где указывается файл с разметкой выборок. [Как создать avm-файл](020_как_создать_авм_файл_для_выборки_без_класса). Общий вид файла должен быть следующим: ```{code} package ... import ... object Mct_ExampleAvi extends Mct_ExampleAvi @AvmFile(name = "Mct_Example.avm.xml") class Mct_ExampleAvi extends EntityAvi{ Код выбороки } ``` ## Создание трейта и методы получения данных, выводимых трейтом 1. В классе создается трейт, который в дальнейшем будет использован для открытия экземпляра выборки. Этот трейт должен наследоваться от одного или более базовых трейтов из `EntityAvi`, или от созданных вами, например: ```{code} trait List extends Default with super.List trait MyList extends List ``` Для каждого трейта должна быть создана функция-компаньон, причем если имя вашего трейта совпадает с именем базового трейта из `BaseAvi`(наследуется в `EntityAvi`) - `Card`, `List`, `Lookup`, то функцию компаньон надо переопределять, а если имя иное, то функцию нужно создать: ```{code} override def list(): List = { new List { override def meta = this } } def myList(): MyList = { new MyList { override def meta = this } } ``` - Имя функции-компаньона начинается с прописной буквы, имя трейта с заглавной. 1. Если трейт создается для отображения данных из базы, тогда необходимо определить метод получения данных для выборки. Для этого существует два подхода: ### Реляционный запрос: 1. Базово определяется в методе `selectStatement`. 1. Тело метода является обычным sql запросом, помещенным в scala-строку. ```{code} 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 } ``` 1. Если итоговый набор данных требуется отфильтровать при помощи данных, ***`переданных извне`***(обычно из фильтров и параметров выборок), и запрос имеет линейную структуру(нет вложенных запросов, временных таблиц и т.д., в которых требуется фильтрация ***`по внешним данным`***), тогда: - Переопределяем метод `onRefresh` следующим образом: ```{code} override protected def onRefresh: Recs = { prepareSelectStatement(s"&DefFltReferenceMacro and someStatement and ${someScalaCode}") } ``` - `DefFltReferenceMacro` - стандартное [имя макроса соответствующего трейта из разметки avm-файла](020_как_создать_авм_файл_для_выборки_без_класса.md#тег-macros). Вы можете задать любое другое удобное вам имя макроса. - `someStatement` - дополнительные условия в формате sql. - `someScalaCode` - любой Scala-код, результатом работы которого является ***`булевое значение`*** или некие данные, которые подставляются в логическое выражение sql с одной из сторон: ```{code} s".... ... t.idState = ${getSelfVar("idState").asNLong} ...." ``` ### Объектный запрос: 1. Базово определяется в методе `onRefresh`. ```{note} Метод onRefresh возвращает как строку в виде sql-запроса, так и набор Scala-объектов, определяющих набор данных выборки ``` 1. В теле метода создается некоторая коллекция однотипных объектов: ```{code} 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`: ```{code} 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. 1. Если нужно получить данные обработанные методами api: 1. В текущем трейте или его предке создается case class с атрибутами, которые вам требуется добавить на выборки: ```{code} case class AdditionalInfo( var someIdParameter: NLong, var someIdParameterHL: NString, var someStrParameter: NString, var someDateParameter: NDate, var someBoolParameter: NNumber, var someNumParameter: NNumber ) ``` - Обычно класс называется AdditionalInfo, но вы можете выбрать и иное имя. - `someIdParameter` - ссылочный атрибут, добавляется для полноты данных выборки, [обычно является скрытым](020_как_создать_авм_файл_для_выборки_без_класса.md#тег-representation-attributes) (смотри пояснение свойства `editorType`, настройки `changeableAttr` ), пользователю не виден. - `someIdParameterHL`[или `MC`] - заголовок ссылочного атрибута (`MC` для кодов/мнемокодов), данные ссылочного атрибута, отображаемые пользователю. - `someStrParameter` - иные строковые данные. - `someDateParameter` - дата. - `someBoolParameter` - булевые данные, обычно на выборку передаются в виде данных в формате NNumber: 0 - ложное значение, остальные - истина. Используются в атрибутах с редактором [check](020_как_создать_авм_файл_для_выборки_без_класса.md#тег-representation-attributes) (смотри пояснение свойства `editorType`). - `someNumParameter` - иные числовые данные. 1. После создается метод `getAdditionalInfo(rop)`: ```{code} def getAdditionalInfo(rop: ***_SomeClassApi#ApiRop): AdditionalInfo = { //Получение и обработка данных AdditionalInfo( someIdParameter = ... someIdParameterHL = ... ....................... someNumParameter = ... ) } ``` 1. Дополнительные данные добавляются в кортеж к изначальным данным выборки: ```{code} 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` выглядит следующим образом: ```{code} override protected def onRefresh: Recs = { "select 1 as id" } ``` Тк у выборки всегда должен присутствовать атрибут `id` для корректной работы 1. У выборки в avm-разметке [создаем фильтры](020_как_создать_авм_файл_для_выборки_без_класса.md#тег-filter), которые будет заполнять пользователи. После введения данных пользователем существует несколько вариантов их обработки: - Пользователь закрывает выборку ![](img/ok_button.jpg) 1. Если введенные пользователем данные не требуется возвращать в метод, вызвавший открытие текущей формы, то на закрытие выборки по согласию пользователя срабатывает метод `closeFormOk()`, соответственно в его переопределении в вашем трейте можно обработать введенные данные. 1. Если данные требуется вернуть в метод, вызвавщий открытие текущей формы, то на закрытие выборки переопределяем метод `beforeCloseForm`: ```{code} override def beforeCloseForm(event: BeforeCloseFormEvent): Unit = { event.lookupResultObject = someObjectWithUserData super.beforeCloseForm(event) } ``` - Где `someObjectWithUserData` - некая структура, хранящая пользовательские данные. Может быть List-ом, кортежем, case class-ом и т.д. на ваш выбор. - Получить такие данные можно в методе, вызвавшем открытие выборки, определив тип возвращаемых данных: ```{code} val data = Mct_ExampleAvi.myList().newForm().params(Map(...)).results(List(attributesName)).openLookup() val res = data.getResultObject.asInstanceOf[someObjectWithUserData] ``` ```{note} Здесь `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("...")` и обрабатывать любым удобным вам способом. ## Комбинированный подход - Вы можете использовать комбинацию предыдущих пунктов в любом удобном вам виде - [выводить](010_как_создать_выборку_без_класса.md#создание-трейта-и-методы-получения-данных-выводимых-трейтом) какие-либо требуемые вам данные и [получать](010_как_создать_выборку_без_класса.md#создание-трейта-для-получения-данных-от-пользователя) от пользователя введенные им данные.