# Урок 2. Сессии, Модель данных
## Цель курса
Целью курса является познакомить слушателей с возможностями Global Framework. В рамках этого курса будет создано приложение по управлению библиотекой.
![Library](/img/library.png)
## Сессия
Сессия приложения создается на поток прикладной бизнес логики и предоставляет доступ к сессии базы данных, EclipseLink кэшу, серверу приложения.
А так же содержит необходимые инъекции зависимости для работы прикладной бизнес логики.
Сессия приложения создается:
- на каждую mdi форму открытую в приложении
- на rest запрос к серверу приложения
```{note}
Для ускорение rest запросов возможна настройка пула сессий
```
Для изучения смотри: [Руководство разработчика: Сессия-приложения](books/AppGuide/020_common/050_сессия_приложения.md#Сессия-приложения)
## Бизнес объект
Бизнес-объект (БО) - объединение нескольких классов и их коллекций в
группу для более удобного манипулирования ими при работе с кэшем и
конфигурировании вспомогательных сервисов.
Бизнес объект позволяет:
- Массово загружать данные в транзакционный кэш \
Для бизнес объекта можно указать стратегию загрузки данных
существенно уменьшающую количество запросов в базу данных.
Так как запросы пойдут не по каждому объекту а
по каждому классу бизнес объекта.
- Настраивать права доступа \
По бизнес объекту создается административный объект на котором
можно массово выдать привилегии для всех классов бизнес объекта
- Управлять электронной подписью \
Можно настроить правила подписи всего бизнес объекта включая
не только шапку но и все вложенные коллекции.
- Настраивать интеграцию и репликацию
Для изучения смотри: [Руководство разработчика: Бизнес-объект
](books/AppGuide/030_class/060_класс.md#Бизнес-объект)
## Общие сведения о классах
Определяет правила хранения и обработки таблицы базы данных.
Класс позволяет существенно ускорить разработку бизнес логики ориентированную на работу с данными.
Программисту достаточно объявить перечень атрибутов класса чтобы за счет кодо-генерации получить набор готовых сервисов.
Перечень генерируемых элементов:
- *Доменная автономная бизнес логика*(`Dpi`) \
Содержит код для автономной бизнес логики
- *Каркас прикладной автономной логики*(`Api`) \
`scala` класс с окончанием `Api`, в котором
пишется автономная бизнес логика для работы с классом.
Наследуется от `Dpi`
- *Доменная интерактивная бизнес логика*(`Dvi`)
- *Каркас прикладной интерактивной логики*(`Avi`) \
`scala` класс с окончанием `Avi`, в котором пишется
интерактивная бизнес логика.
Наследуется от `Dvi`
- *Доменная разметка выборки*(`dvm.xml`) \
Содержит сгенерированную по умолчанию разметку выборки.
- *Каркас прикладной декларации пользовательского интерфейса*(`Avm`) \
`xml` файл с расширением `avm.xml`, в котором пишется
разметка выборки
- Интеграция с `Orm`
- `Pojo` объект для хранения данных в кэше
- `Aro` объект интеграции pojo в фреймворк
### Типы данных
В система имеет специализированный набот простых и объектных типов
для удобной обработки данных. Объектные типы при необходимости интегрированны
в контекст сессии что позволяет обеспечить высокую произоводительность
системы за счет минимизации операций сериализации\десириализации.
Основные типы данных, используемые для атрибутов класса:
- Целое число
- Дробное число
- Строка
- Дата
- Ссылка (на объект заданного класса)
- Ссылка на класс
- Переменная ссылка (на объект произвольного класса)
- Глобальный идентификатор gid
- Json контейнер
#### Простые типы
Простыми типами являются: Число, строка, дата
| Тип | postgresql | odm | Рекомендация по использованию |
|-|-|-|-|
| Строка фиксированной длинны | varchar | Varchar | Текст до 4000 символов |
| Строка переменной длинны | text | Text | Текст до 15 мегабайт |
| Число | number | Number | Целое или дробное число |
| Дата | date | Date | Дата, дата и время |
#### Ссылочные типы
Данные классов могут быть связаны между собой.
Для организации связей между классами используются специальные типы.
##### Ссылка на класс
Хранит ссылку на класс. Обычно используется совместно с
типом `переменная ссылка` для хранения класса, объект которого
содержится в переменной ссылке.
##### Глобальный идентификатор gid
Gid является уникальным идентификатором в рамках системы. Переменная
ссылка на gid является альтернативой системе переменной ссылочности из
двух атрибутов (ссылка на класс + переменная ссылка на объект). Для
организации переменной ссылки через gid используется один атрибут.
#### Json контейнер
Json контейнер – это расширение объекта класса NoSQL нотацией в
реляционной СУБД. Контейнер не имеет жесткой, заранее определенной схемы
и основан на множестве пар «ключ‑значение». Это позволяет использовать
его как динамическое расширение объекта. Для добавления новых данных в
контейнер не требуется перекомпиляция кода или изменение структуры СУБД.
Для создания нового класса в модуле необходимо:
1. Cоздать файл спецификации класса odm. Данный файл удобнее всего создать по шаблону в Install IntelliJ IDEA (как добавить такой шаблон в ide показано в 1вом уроке).
```{attention}
Шаблон по умолчанию содержит коллекцию без имени и включенную группировку. Если в группировке нет необходимости её нужно удалить. Так же необходимо определить или удалить шаблоны для коллекций, в противном случае при генирации кода могут возникнуть ошибки.
```
1. Определить атрибуты будущего класса и подключить коллекции если они имеются.
1. Сгенерировать код по файлу спецификации
1. Собрать проект
1. Сгенерировать таблицы
1. Добавить orm класса в файл src/main/resources/orm/all.xml
```xml
org.eclipse.persistence.jpa.PersistenceProvider
...
ru/bitec/app/lbr/Lbr_ClassName.orm.xml
...
```
### Работа c провайдерами строк
Провайдер строки - Rop используется для работы со строкой данных (Aro),
загруженных в рабочее пространство, обеспечивая гарантию того, что при
доступе к строке данная строка будет находиться в рабочем пространстве.
Метод получения rop:
```scala
thisApi().load(идентификатор.asNLong)
```
Примеры сеттеров в файле выборки:
```scala
val rop = thisRop
thisApi().setidContras(rop,getVar("super$id").asNLong)
```
Работа с rop в API:
```scala
for (ropGrade <- new OQuery(entityAta.Type){
where (t.idGdsGrade === idpGdsGrade)
}) {
setidGdsGrade(ropGrade, None.nl)
}
```
Для изучения смотри: [Руководство разработчика: Класс
](books/AppGuide/030_class/060_класс.md#Класс)
## Общие сведения о выборках
Выборка определяет правило получения, отображение данных и обеспечивает взаимодействие с пользователем.
Выборки содержат основную часть интерактивной бизнес логики.
Выборка определяет:
- Способ получения данных
- Способ отображения данных пользователю
- Бизнес логику обработки пользовательских действий
Выборка может создаваться от класса с использованием кодо-генерации или вручную.
Пользовательский интерфейс приложения является совокупностью экземпляров отображений выборок.
Для изучения смотри: [Руководство разработчика: Выборка
](books/AppGuide/040_selection/080_выборка.md#Выборка)
## Взаимодействие с базой данных
### Объектные запросы
Кроссплатформенные запросы, которые выполняются на уровне объектов
класса.
При выполнении запроса идет обращение к базе данных, за исключением
случаев кеширования.
Пример запроса
```scala
new OQuery(Bs_GoodsAta.Type) {
where(t.sSystemName === spMnemoCode)
}
```
#### Методы
- `where` \
Условие запроса.
- `orderBy` \
Выражение для сортировки резуьтата
- `batchAll` \
Массовая загрузка объектов. Возвращает строки с
прогруженными записями всех коллекций этого класса.
- `forUpdate` \
Выполнение запроса с блокированием вернувшихся записей
- `forUpdateNoWait` \
Выполнение запроса с блокирование вернувшихся
записей, без ожидания разблокирования, если записи уже заблокированы
другой сессией.
- `tryCacheQueryResults` \
Попытаться закешировать результат запроса.
Смотри пункт «Кеширование»
- `unique` \
Говорит, что запрос возвращает одну уникальную запись.
Позволяет использовать cache-index’ы, указанные в orm класса
#### Кеширование объектных запросов
Кеширование запросов работает только для классов с разделяемым режимом
кеширования(`Shared`).
##### Кэширование по полю
Кеширование через cache-index’ы указанные в orm класса. Такой запрос
должен возвращать одну строку и дополняется командой `unique()`.
Например для атрибутов мнемокода класса в orm формируется запись:
```xml
SSYSTEMNAME
```
Запрос выглядит следующим образом:
```scala
new OQuery(entityAta.Type) {
unique()
where(t.sSystemName*=== spMnemoCode)
}
```
##### Кэширование объектных запросов
Кэширование объектных запросов возможно по требованию в случаи если класс
настроен для сохранения в разделяемом кэше.
Чтобы включить кэширования запроса:
1. Добавьте в запрос опцию `tryCacheQueryResults()`. \
Результат такого запроса будет кэширован,
если транзакция не находится в режиме редактирования разделяемых
объектов.
Пример запроса:
```scala
new OQuery(entityAta.Type) {
tryCacheQueryResults ()
where(t.sSystemName === spMnemoCode)
}
```
#### Транзакционный индекс
Позволяет получить перечень строк по значению индексируемого атрибута. Индекс подгружает данные из базы данных по мере обращения к ключам индекса, а так же отслеживает транзакционные изменения для получение согласованного набора строк. Это позволяет получить согласованный доступ к множеству строк по ключу, даже если индексируемое значение строки меняется в рамках этой транзакции.
Пример объявления:
```scala
lazy val idxidParent = TxIndex(Btk_GroupAta.Type)(_.idParentGroup)
```
Методы
- `byKey` \
Посетитель по ключу индекса.
- `refreshByKey` \
Посетитель по ключу индекса c обновлением из базы данных.
- `queryKeys` \
Кеширование ключей индекса.
- `forPartition` \
Открывает секцию для массового обновления индекса.
Используется для прозрачного массового обновления после очистки транзакционного кэша.
Секции могут быть вложенными друг в друга, в таком случае ключи суммируются.
#### byParent
Метод есть у классов коллекций, возвращает обходчик записей отфильтрованных по идентификатору предка
### Реляционные запросы
Для обработки реляционных запросов в основном используется методы на
базе [anorm](http://playframework.github.io/anorm/).
Для более удобного использования в контекст бизнес логики добавлены
дополнительные функции.
#### ASQL
Выполнение запроса на чтение
Создаёт объект AnormSql для выполнения запроса к базе без модификации данных.
Если в текущей сессии начата транзакция, SQL-выражение выполняется в ней.
Если транзакция в сессии не начата, запрос выполнится в автономной sql-транзакции.
Пример:
```scala
val idTrigger = 1
ASQL"SELECT t.sCaption as sHeadline FROM Btk_JobSchedule t WHERE t.id = $idTrigger".as(nStr("sHeadline").*).headOption
```
#### ATSQL
Выполнение запроса с изменением данных или блокировками
Создаёт объект AnormSql с возможностью модификации данных.
Если в текущей сессии начата транзакция, SQL-выражение выполняется в ней.
Если в текущей сессии транзакция не начата, она будет начата.
Пример:
```scala
ATSQL("alter table bs_barcode add id int8;").execute()
```
#### ASelect
Выполнение запросов на чтение\запись с большим кол-вом колонок.
Пример:
```scala
for (rv <- new ASelect {
val nParentLevel = asInt("nParentLevel")
val gidParent = asString("gidParent")
val gidChild = asString("gidChild")
val idParent = asLong("idParent")
SQL"""
select nParentLevel,gidParent,gidChild,idParent,idChild from table
"""
}) {
println(rv.nParentLevel())
//запрос поля без его предварительного объявления
println(rv.get("idChild").asNLong())
}
```
## Практика
## Создание классов для модуля библиотека
Задача: Создать классы:
- Справочник: `Lbr_Publisher`
- Справочник: `Lbr_Author`
- Справочник: `Lbr_Book`
### Справочник `Lbr_Publisher` - Издатель
| Системное имя | Наименование | Тип данных атрибута | Тип атрибута | Примечание |
|---------------|---------------|---------------------|--------------|------------|
| gid | gid | Varchar | Basic | |
| sSystemName | Системное имя | Varchar | Basic | Мнемокод |
| sCaption | Наименование | Varchar | Basic | Заголовок |
| sDescription | Описание | Varchar | Basic |
```{tip}
Создайте класс по адресу
`lbr/src/main/resources/ru/bitec/app/lbr/Lbr_Publisher.odm.xml`
где lbr наименование модуля
```
### Справочник `Lbr_Author` - Автор
В классе включить группировку
| Системное имя | Наименование | Тип данных атрибута | Тип атрибута | Обязательный | Примечание |
|---------------|--------------|---------------------|--------------|--------------|----------------------------------------------------------|
| gid | gid | Varchar | Basic | | |
| sCode | Код | Varchar | Basic | V | Мнемокод |
| sLastName | Фамилия | Varchar | Basic | V | |
| sFirstName | Имя | Varchar | Basic | V | |
| sMiddleName | Отчество | Varchar | Basic | V | |
| sFIO | ФИО | Varchar | Basic | | Заголовок |
```{tip}
Создайте класс по адресу
`lbr/src/main/resources/ru/bitec/app/lbr/Lbr_Author.odm.xml`
где lbr наименование модуля
```
### Справочник `Lbr_Book` - Книга
| Системное имя | Наименование | Тип данных атрибута | Тип атрибута | Примечание |
|---------------|----------------|---------------------|--------------|-------------------------------|
| gid | gid | Varchar | Basic | |
| sISBN | ISBN | Varchar | Basic | Мнемокод |
| sCaption | Наименование | Varchar | Basic | Заголовок |
| idPublisher | Издатель | Long | refObject | Ссылка на класс Lbr_Publisher |
| idAuthor | Автор | Long | refObject | Ссылка на класс Lbr_Author |
| nYear | Год издания | Number | basic | |
| nColPage | Кол-во страниц | Number | basic | |
| sDesc | Описание | Varchar | basic |
```{tip}
Создайте класс по адресу
`lbr/src/main/resources/ru/bitec/app/lbr/Lbr_Book.odm.xml`
где lbr наименование модуля
```
## Создание выборки основного меню приложения
Создайте создайте основную выборку приложения `Библиотека`
1. Создайте файл `src/main/resources/ru/bitec/app/lbr/Lbr_MainMenu.avm.xml`
```xml
```
2. Создайте файл `src/main/scala/ru/bitec/app/lbr/Lbr_MainMenuAvi.scala`
```scala
package ru.bitec.app.lbr
import ru.bitec.app.bs._
import ru.bitec.app.btk._
import ru.bitec.app.gtk.gl.avi.Visibilities
import ru.bitec.gtk.core.AvmFile
@AvmFile(name = "Lbr_MainMenu.avm.xml")
object Lbr_MainMenuAvi extends Bs_ApplicationAvi {
override def default(): Default = {
new Default {
override def meta = this
}
}
trait Default extends super.Default {
override def onLoadMeta(): Unit = {
super.onLoadMeta()
}
//========== Справочники ======================
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible, imageCollection="Btk_Application", imageIndex=6,
visibleOnMainMenu = Visibilities.Visible, caption = "Справочники", order = 10)
def mm_ReferenceRoot(): Unit = {}
//-------Подразделения-----
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "Подразделения", order = 10, headOperation = "mm_ReferenceRoot")
def mm_DepartmentRoot(): Unit = {}
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "Подразделения", order = 5, headOperation = "mm_DepartmentRoot")
def mm_Department(): Unit = {
Bs_DepartmentAvi.tree().newForm().open()
}
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "Организации", order = 10, headOperation = "mm_DepartmentRoot")
def mm_Bs_DepOwner(): Unit = {
Bs_DepOwnerAvi.list().newForm().open()
}
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "Виды подразделений", order = 20, headOperation = "mm_DepartmentRoot")
def mm_Bs_DepartmentSort(): Unit = {
Bs_DepartmentSortAvi.list().newForm().open()
}
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "-", order = 20, headOperation = "mm_ReferenceRoot")
def mm_Delimiter1(): Unit = {}
// Контрагенты
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "Контрагенты", order = 30, headOperation = "mm_ReferenceRoot")
def mm_Bs_ContrasRoot(): Unit = {}
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "Контрагенты", order = 10, headOperation = "mm_Bs_ContrasRoot")
def mm_Bs_Contras(): Unit = {
Bs_ContrasAvi.mainList().newForm().open()
}
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "Типы контрагентов", order = 20, headOperation = "mm_Bs_ContrasRoot")
def mm_Bs_ContrasType(): Unit = {
Btk_ObjectTypeAvi.list().newForm().params(Map(
"FLT_SREFCLASSNAME" -> "Bs_Contras",
"FLT_SREFCLASSNAME.readOnly" -> true
)).open()
}
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "-", order = 30, headOperation = "mm_ReferenceRoot")
def mm_Delimiter11(): Unit = {}
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "Физические лица", order = 50, headOperation = "mm_ReferenceRoot")
def mm_Bs_Person(): Unit = {
Bs_PersonAvi.list().newForm().open()
}
//----------------------------------
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "-", order = 60, headOperation = "mm_ReferenceRoot")
def mm_Delimiter12(): Unit = {}
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "Авторы", order = 80, headOperation = "mm_ReferenceRoot")
def mm_Lbr_Autor(): Unit = {
Lbr_AuthorAvi.list().newForm().open()
}
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "Издательства", order = 90, headOperation = "mm_ReferenceRoot")
def mm_Lbr_Publisher(): Unit = {
Lbr_PublisherAvi.list().newForm().open()
}
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "Книги", order = 110, headOperation = "mm_ReferenceRoot")
def mm_Lbr_Book(): Unit = {
Lbr_BookAvi.list().newForm().open()
}
//========== Документы ======================
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible, imageCollection="Btk_Application", imageIndex=7,
visibleOnMainMenu = Visibilities.Visible, caption = "Документы", order = 20)
def mm_DocumentRoot(): Unit = {}
//========== Отчеты ======================
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible, imageCollection="Btk_Application", imageIndex=8,
visibleOnMainMenu = Visibilities.Visible, caption = "Отчеты", order = 50)
def mm_LbrReportRoot(): Unit = {}
//========== Настройки ======================
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible, imageCollection="Btk_Application", imageIndex=9,
visibleOnMainMenu = Visibilities.Visible, caption = "Настройки", order = 60)
def mm_SettingRoot(): Unit = {}
//========== Настройки документации ======================
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "Настройки документации", order = 10, headOperation = "mm_SettingRoot")
def mm_DocSetting(): Unit = {}
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "Типы объектов", order = 10, headOperation = "mm_DocSetting")
def mm_Btk_ObjectType(): Unit = {
Btk_ObjectTypeAvi.list().newForm().open()
}
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "Настройка детализации типов объектов", order = 15, headOperation = "mm_DocSetting")
def mm_Btk_ObjectTypeDet(): Unit = {
Btk_ObjectTypeDetAvi.list().newForm().open()
}
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "Жизненные циклы", order = 40, headOperation = "mm_DocSetting")
def mm_Btk_LifeCycle(): Unit = {
Btk_LifeCycleAvi.list().newForm().open()
}
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "Закладки", order = 60, headOperation = "mm_DocSetting")
def mm_Btk_Tab(): Unit = {
Btk_TabAvi.list().newForm().open()
}
//========== Печатные формы ==============================
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "Печатные формы", order = 90, headOperation = "mm_SettingRoot")
def mm_Rpt_Report(): Unit = {
val vRpt_ReportAvi = Btk_Lib().getAviBySimpleName("Rpt_ReportAvi")
if (vRpt_ReportAvi != null)
vRpt_ReportAvi.list().newForm().open()
}
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "-", order = 120, headOperation = "mm_SettingRoot")
def mm_Delimiter60(): Unit = {}
//============ Дополнительно =========================
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "Дополнительно", order = 130, headOperation = "mm_SettingRoot")
def mm_AdditionalSettings(): Unit = {}
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "Групировка объектов", order = 130, headOperation = "mm_AdditionalSettings")
def mm_Btk_Group(): Unit = {
Btk_GroupAvi.list().newForm().open()
}
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "Состояния классов", order = 150, headOperation = "mm_AdditionalSettings")
def mm_Btk_ClassList(): Unit = {
Btk_ClassListAvi.list().newForm().open()
}
//========== Менеджер заданий ======================
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "-", order = 140, headOperation = "mm_SettingRoot")
def mm_Delimiter140(): Unit = {}
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "Менеджер заданий", order = 150, headOperation = "mm_SettingRoot")
def mm_Btk_Job(): Unit = {
Btk_JobGroupAvi.tree().newForm().open()
//Btk_JobAvi.tree().newForm().open()
}
//========== Тестирование и отладка ======================
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "-", order = 9998, headOperation = "mm_SettingRoot")
def mm_Delimiter9998(): Unit = {}
@Oper(visibleOnContextMenu = Visibilities.Invisible, visibleOnToolbar = Visibilities.Invisible,
visibleOnMainMenu = Visibilities.Visible, caption = "Тесты и отладка", order = 9999, headOperation = "mm_SettingRoot")
def mm_Testing(): Unit = {}
} //---------
}
```
3. Создайте файл `src/main/resources/META-INF/applications.xml`
```xml
```