# Особенности работы в системе Global

Система Global предоставляет расширенные возможности для работы с данными через JEXL-скрипты. В статье описаны основные методы работы с переменными, выборками, данными и внешними сервисами.

## Наименование переменных для nullable типов и атрибутов

Правила именования переменных: `[Variable][a][t][Scope][Name][Suffix]`.

**Компоненты именования:**

- **[Variable]** — определяет тип данных:
  - n — число;
  - s — строка;
  - j — строка в формате JSON;
  - d — дата;
  - r, x — запись;
  - u, cur — курсор;
  - l, blob — бинарные данные;
  - с — символьные данные;
  - b — булево значение;
  - id — идентификатор;
  - gid — глобальный идентификатор.

- **[a]** — определяет, является ли переменная последовательностью;
- **[t]** — определяет пользовательский тип;
- **[Scope]** — область действия:
  - v — переменная процедуры;
  - p — параметр процедуры;
- **[Name]** — имя переменной;
- **[Suffix]** — суффикс:
  - _dz — системные атрибуты;
  - _z — проектные атрибуты.

**Примеры именования параметров:**
- dStart — дата начала в таблице;
- dvStart — дата начала переменной процедуры;
- dgMaxDate — максимальная дата переменной пакета;
- tvdaDate — тип коллекции дат в процедуре;
- davDate — коллекция дат в процедуре;
- uvStudents — курсор в процедуре.

## Основные методы и принципы работы

### Поиск методов класса

Для просмотра доступных методов любого класса выполните следующие шаги:

1. Перейдите в **Настройки системы** → **Обозреватель проектов**.
2. Введите имя класса (например, `Stm_OrderOut`).
3. Откройте файл с суффиксом `Api` (например, `Stm_OrderOut_Api`).
4. Перейдите на вкладку **Методы**.

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

### Работа с текущей выборкой

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

С помощью `selection.getVar` можно получить атрибуты текущей выборки:
```
var id = selection.getVar("атрибут");
var idvOrder = selection.getVar("id"); // "id" — название атрибута
```
### Загрузка строки через rop

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

Принцип работы:
1. Сначала проверяет наличие данных в кэше.
2. Если данных нет — загружает их из базы.
3. Предоставляет удобный доступ к полям строки.

Получить строку можно с помощью метода load. Полученное значение будет типа rop:
```
ropOrder = Stm_OrderOutApi.load(idvOrder);
```
### Доступ к атрибутам строки

**Обычные атрибуты** (колонки таблицы) доступны напрямую через точку:
```
var svNumDoc = ropOrder.sNumDoc; // Номер документа
var idvClient = ropOrder.idClient; // ID клиента
```
**Работа с JSON-атрибутами на примере Stm_OrderOut**

**Получение значений атрибутов:**

1. Получение всего JSON объекта:
```
var jvParams = ropOrder.jParams_dz; // Получаем полный JSON-объект параметров
```
2. Получение конкретного значения по имени атрибута:
```
var svStation = Stm_OrderOutApi.getObjAttrValue(ropOrder, 'station'); // Получаем значение атрибута 'station'
```
3. Получение значения по ID атрибута:
```
// Получаем ID класса из текущей выборки
var idvClass = selection.getVar('idClass'); // Получим id класса
// Находим ID атрибута по мнемокоду 'station' для указанного класса
var idvAttrStation = Btk_AttributeApi.findByMnemoCode(idvClass, 'station');
// Получаем значение атрибута для указанного заказа
var svStationById = Stm_OrderOutApi.getObjAttrValue(ropOrder, idvAttrStation);
```
**Установка значений атрибутов:**

1. Установка по имени атрибута — передать название класса и название атрибута:
```
Stm_OrderOutApi.setObjAttrValue(ropOrder, 'station', "Новая станция");
```
2. Установка по ID атрибута:
```
// Получаем ID класса из текущей выборки
var idvClass = selection.getVar('idClass');
// Находим ID атрибута по мнемокоду 'station' для указанного класса
var idvAttrStation = Btk_AttributeApi.findByMnemoCode(idvClass, 'station');
// Получаем значение атрибута для указанного заказа
var svStationById = Stm_OrderOutApi.getObjAttrValue(ropOrder, svStationById);
Stm_OrderOutApi.setObjAttrValue(ropOrder, svStationById, "Новое значение"); // Установка значения для атрибута с указанным ID
```
### Изменение значений

Чтобы изменить значение в строке, используйте специальные методы-сеттеры. У каждого класса свои сеттеры (смотрите в методах API):
```
// Меняем номер документа
Stm_OrderOutApi.setsNumDoc(ropOrder, "НОВЫЙ-001");

// Меняем клиента
Stm_OrderOutApi.setidClient(ropOrder, 12345L);
```
### Поиск по мнемокоду

Часто нужно получить ID по коду (например, код способа доставки):
```
// Получаем ID способа доставки "ЖД"
var idvZHD = Bs_DeliveryMethodApi.findByMnemoCode('ZHD');
```
```{attention}
код чувствителен к регистру! 'ZHD' и 'zhd' — разные коды.
```
Пример полного скрипта:
```
// 1. Получаем ID из текущей выборки
var idvOrder = selection.getVar("id");

// 2. Загружаем строку заказа
var ropOrder = Stm_OrderOutApi.load(idvOrder);

// 3. Получаем атрибуты
var svNumDoc = ropOrder.sNumDoc; // Номер
var idvStationEndJson = Stm_OrderOutApi.getObjAttrValue(ropOrder, 'idStationEndJson');

// 4. Меняем номер
Stm_OrderOutApi.setsNumDoc(ropOrder, "НОВЫЙ-002");

// 5. Ищем ID способа доставки
var idvDelivery = Bs_DeliveryMethodApi.findByMnemoCode('ZHD');
```

### Доступ к универсальным характеристикам

Помимо объектных характеристик, к классам могут быть подключены универсальные характеристики (UC). Доступ к ним осуществляется через `ClassApi`:
- `ClassApi.getUniCharValue(rop, "CharName")` — получение по системному имени.
- `ClassApi.getUniCharValue(rop, idAttr)` — получение по ID атрибута.

```{note}
Значения UC также могут быть `null`. Перед использованием применяйте проверку `isNotNull()` или `.nvl()`.
```

## Преобразование типов данных

| Метод | Пример использования |
|-------|----------------------|
| `.asJLong()` | `"123".asJLong()` |
| `.toLong()` | `"456".toLong()` |
| `.toInteger()` | `"789".toInteger()` |
| `.toDouble()` | `"12.34".toDouble()` |
| `.toBigDecimal()` | `"123.45".toBigDecimal()` |
| `.asDate()` | `"2024-01-01".asDate()` |
| `.toDate("формат")` | `toDate("01.01.24", "dd.MM.yy")` |
| `.toString()` | `123.toString()` |
| `.asString()` | `321.asString()` |
| `.asList()` | `sql("SELECT...").asList()` |
| `.asSingle()` | `sql("SELECT...").asSingle()` |
| `.asScala()` | `[1,2,3].asScala()` |
| `.asJava()` | `scalaSeq.asJava()` |
| `.toJObject()` | `toJObject('{"id":1}')` |

## Работа с датами

| Метод | Описание | Пример |
|-------|----------|--------|
| `getFirstDayOfMonth` | Возвращает дату начала месяца от текущей даты | `var a = getFirstDayOfMonth();` `var b = getFirstDayOfMonth('27.01.2025 12:42:10');` |
| `getFirstDayOfPeriod` | Возвращает первый день указанного периода | `var a = getFirstDayOfPeriod(sysDate(), 'M');` `var b = getFirstDayOfPeriod('27.01.2025 12:42:10', 'Q');` |
| `getLastDayOfMonth` | Возвращает дату конца месяца от текущей даты | `var a = getLastDayOfMonth();` `var b = getLastDayOfMonth('27.01.2025 12:42:10');` |
| `getLastDayOfPeriod` | Возвращает последний день указанного периода | `var a = getLastDayOfPeriod(sysDate(), 'M');` `var b = getLastDayOfPeriod('27.01.2025 12:42:10', 'Q');` |
| `nanoTime` | Получить значение System.nanoTime() | `var a = nanoTime();` |
| `sysDate` | Возвращает текущую дату | `var a = sysDate();` |
| `toDate` | Переводит строку в дату по указанному формату | `var a = toDate("27.02.2020 12:42:10");` `var b = toDate("27.02.2020 12:42:10", "dd.MM.yyyy HH:mm:ss");` |
| `truncDate` | Получение даты на начало дня от переданной | `var a = truncDate(sysDate());` |
| `truncYear` | Получение даты на начало года от переданной | `var a = truncYear(sysDate());` |
| `-` | Вычитает из даты указанное количество дней | `var dYesterday = sysDate() - 1;` |
| `+` | Добавляет к дате указанное количество дней | `var dTomorrow = sysDate() + 1;` |

## Работа в контексте выборки

Контекст выборки (JexlSelScript) расширяет контекст бизнес-логики возможностью работы с выборками и пользовательским интерфейсом. Используется в тех случаях, когда необходимо получить данные из полей для пользовательского ввода.

| Метод | Описание | Пример использования |
|-------|----------|---------------------|
| `varExists(name)` | Проверяет существование переменной в текущей и мастер-выборках | `if (varExists("client_id")) { ... }` |
| `selfVarExists(name)` | Проверяет существование переменной только в текущей выборке | `if (selection.selfVarExists("temp_value")) { ... }` |
| `getVar(name)` | Получает значение переменной из текущей или мастер-выборки | `var id = selection.getVar("doc_id").asLong();` |
| `getSelfVar(name)` | Получает значение только из текущей выборки | `var code = selection.getSelfVar("product_code");` |
| `setVar(name, value)` | Устанавливает значение переменной (ищет в иерархии выборок) | `selection.setVar("status", "approved");` |
| `setSelfVar(name, value)` | Устанавливает значение только в текущей выборке | `selection.setSelfVar("comment", "Не заполнено");` |
| `addVar(name, value, FieldType)` | Добавляет новую переменную в текущую выборку | `selection.addVar("id", 1, FieldType.ftFloat());` |
| `newForm()` | Создает новую форму | `Act_DocEdit.newForm().params(...).open();` |
| `.form()` | Возвращает форму текущего контекста | `selection.form();` |
| `.opers('Операция')` | Возвращает указанную операцию для дальнейшего выполнения | `selection.opers("CREATESTMORDERIN");` |
| `.execute()` | Выполняет ранее полученную операцию | `selection.opers("CREATESTMORDERIN").execute();` |

### Работа с формой выборки

**Создание и настройка формы:**
```
var res = Btk_ClassAvi // Класс, предоставляющий форму выбора
.list() // Инициализация формы списка (табличное отображение)
.newForm() // Создание новой формы
.params({ // Передача параметров в форму в виде мапы
    "one": 1,
    "two": 2,
    "three": 3,
    "more": "many more"
})
.locates({"id": 146800}) // Выбор строки
.results(["id", "idClass"]) // Какие поля вернуть после выбора
.openLookup(); // Открытие окна
```
| Метод | Описание |
|-------|----------|
| `.list()` | Форма будет отображаться как таблица (список записей). Возможные отображения можно посмотреть в обозревателе проекта в классе с расширением Avi |
| `.newForm()` | Создает новую формы |
| `.params()` | Передает параметры в форму (могут использоваться для фильтрации/логики) |
| `.locates()` | Установка строки на активную |
| `.results()` | Задает поля, которые вернутся после выбора |
| `.openLookup()` | Открывает окно выбора |
| `.findSelection('Отображение')` | Ищет и возвращает **выборку**, содержащую указанное отображение |
| `.baseRep()` | Возвращает ссылку на объект, к которому был вызван метод |

Пример: Поиск и выполнение операций в связанной выборке

Демонстрирует, как найти выборку, связанную с конкретным документом, и выполнить в ней операцию.
```
var sel = selection.form() // получает форму по выделенному элементу
.findSelection(Stm_OrderOutAvi.Card() // Ищет и возвращает **выборку**, содержащую указанное отображение
.baseRep()); // возвращает базовое представление объекта (ссылку)

if (sel != null) { // если выборка найдена, то выполняет действие
    sel.opers("CREATESTMORDERIN").execute(); // выполняет переданную операцию
}
```
Для работы с текущей выборкой можно использовать сокращенный синтаксис:
```
selection.opers("CREATESTMORDERIN").execute();
```
**Обработка результата:**
```
if (res.getLookupResult() == LookupResult.ok()) {
    // Пользователь выбрал значение и нажал "OK"
    for(var i: 1 .. res.size()) { // Цикл по всем выбранным записям
        dialogs.showMessage("id " + res.getData(i, 0) + " idClass " + res.getData(i, 1));
    }
} else {
    // Пользователь закрыл форму или нажал "Отмена"
    dialogs.showMessage("Значение не было выбрано");
}
```
- res.getLookupResult() — проверяет, как закрыли форму:
  - LookupResult.ok() — нажали "Выбрать";
  - LookupResult.cancel() — нажали "Отмена" или закрыли окно.

- res.size() — количество выбранных записей (если форма поддерживает множественный выбор).

- res.getData(i, 0) — доступ к данным:
  - i — номер строки (начинается с 1);
  - 0 — индекс поля (соответствует порядку в .results(["id", "idClass"])).

Пример работы с мастер-выборками:
```
// Получаем значение из атрибута "id" выборки, приводим его к типу NString и пишем в переменную idTree
var idTree = getVar("id").asNString();

// Получаем атрибут DGLOBALENDDATE из мастер-выборки
var dEndDate = getVar("super$DGLOBALENDDATE").asNDate();

// Вызываем метод из пакета с передачей параметров
// Обратите внимание, для обращения к Api или пакетам используются их короткие имена (без скобок)
var fltCond = Act_UniversalReportPkg.getUniFilterCondByIdTree(idTree, dEndDate);

// Вызывается ещё один метод, возвращающий объект scala-класса immutable.Map[NString, Any]
var filters = Act_UniversalReportPkg.getFilterValues(idTree);

/* Определение Map-ы внутри jexl-скрипта. Отметим, что Map внутри jexl и объект scala-класса
Map (неважно mutable или immutable) - это разные объекты. Наиболее важным
фактом является то, что передать scala-объект Map, полученный в предыдущей
строке,
в пакетный метод, принимающий scala-объект Map, в jexl-скрипте напрямую нельзя,
именно поэтому приходится получать значения из переменной filters,
перезаписывать
их в jexl-Map param и потом передавать param в scala-метод. */

var param = {
    "flt_idDepOwner" : filters['flt_idDepOwner'],
    "flt_idAcc" : filters['flt_idAcc'],
    "flt_dFrom" : filters['flt_dFrom'],
    "flt_dTo" : filters['flt_dTo'],
    "flt_idAdjustMethod" : filters['flt_idAdjustMethod'],
    "flt_idAccCor" : filters['flt_idAccCor'],
    "uniFilterCondition_dz": fltCond
};

// Открываем новую форму с переданными параметрами
Act_TransAvi.defList().newForm().params(param).open();
```
**Мастер-выборки:**
- Доступ через префикс `super$`.
- Пример: `getVar("super$PARENT_ID")`.

**Типизация:**
- Используйте `.asString()`, `.asDate()` для явного преобразования.
- Для чисел: `.asBigDecimal()`, `.asJLong()`.

### Дополнительные методы работы с формой выборки

Открывает мастер пользовательского ввода по указанному идентификатору формы:
```
Btk_WizardLib().launch(getSelfVar("id").asNLong); // В качестве параметра передается id формы.
```
## Работа с SQL

Для работы с SQL используются команды:

1. Для запросов на чтение:
```
sql(sqlText:String)
```
2. Для запросов на запись:
```
tsql(sqlText:String).execute()
```
**Методы обработки для sql:**

| Метод | Описание | Пример |
|-------|----------|--------|
| `.asList()` | Вернуть список записей | `sql("SELECT * FROM users").asList()` |
| `.asSingle()` | Вернуть одну запись | `sql("SELECT * FROM users WHERE id=1").asSingle()` |

**Методы обработки для tSql:**

| Метод | Описание | Пример |
|-------|----------|--------|
| `.execute()` | Выполнить запрос | `tsql("DELETE FROM temp").execute()` |

**Примеры:**

Простые запросы:
```
// Чтение
var activeUsers = sql("SELECT name FROM users WHERE is_active=true").asList();

// Запись
tsql("UPDATE orders SET status='done' WHERE id=42").execute();
```
Обработка результатов:
```
// Вывод имен пользователей
sql("SELECT name FROM users").foreach(row -> {
    logInfo("User: " + row.name);
});

// Подсчет суммы
var total = 0;
sql("SELECT amount FROM payments").foreach(p -> {
    total = total + p.amount;
});
```
Параметризация:
```
var productId = selection.getVar('id');
var product = sql("SELECT * FROM products WHERE id=${productId}").asSingle();

var productId = selection.getVar('id');
var sSqlText = ''; // объявляем переменную, в которой будем собирать sql-выражение
sSqlText = "SELECT * FROM products WHERE id="; //
sSqlText += toString(productId);
sSqlText += "\r\n order by id";
var product = sql(sSqlText).asSingle();

var productId = selection.getVar('id');
var product = sql(`
    SELECT *
    FROM products
    WHERE id=${productId}`)
.asSingle();
```

### Особенности работы с JexlSqlRow

Результат `sql().asList()` возвращает список объектов типа `JexlSqlRow`. 

Доступ к данным осуществляется следующими способами:
- `row.fieldName` — по имени колонки из `SELECT`;
- `row.getValue("fieldName")` — безопасный доступ (рекомендуется при динамических именах);
- `row.containsKey("fieldName")` — проверка наличия поля перед обращением;

```{note}
 Поля в `JexlSqlRow` формируются строго из запроса. Обращение к несуществующему полю или к полю со значением `NULL` через точечную нотацию вызовет ошибку выполнения скрипта.
 ```

### Поведение NULL при интерполяции
JEXL-интерполяция `${variable}` преобразует значение `null` в пустую строку `""`, а не в SQL-`NULL`. Выражение `WHERE id=${nullField}` сгенерирует `WHERE id=`, что вызовет синтаксическую ошибку PostgreSQL.

Перед подстановкой в SQL всегда проверяйте значение на `null`:
```jexl
if (row.fieldName.isNotNull) {
sql("SELECT * FROM table WHERE col=${row.fieldName}").asList()
```
Либо используйте параметризованный запрос через `on(Symbol("param") -> value)`.
```

## Дополнительные методы

| Метод | Описание | Пример использования |
|-------|----------|---------------------|
| `lpad(str, len, ch)` | Дополнение строки слева | `lpad("5", 3, "0") → "005"` |
| `rpad(str, len, ch)` | Дополнение строки справа | `rpad("5", 3, "0") → "500"` |
| `flush()` | Синхронизация с БД | `flush()` |
| `commit()` | Подтверждение транзакции | `commit()` |
| `nvl(a, b)` | Возвращает a, если не null, иначе b | `nvl(var, "default")` |
| `isNull(x)` | Проверка на null | `isNull(obj)` |
| `isNotNull(x)` | Проверяет, что переданное значение не null | `isNotNull(obj)` |
| `pgArrayToNLongList` | Конвертация массива в список | `pgArrayToNLongList(arr)` |
| `parseId(gid: Object): Long` | Получение идентификатора объекта из NGid-а Работает для строкового или NGid-значения | `var sGid = "14323/44334"; var id = parseId(sGid);` |
| `parseIdClass(gid: Object): Long` | Получение идентификатора класса из NGid-а Работает для строкового или NGid-значения | `var sGid = "14323/44334"; var idClass = parseIdClass(sGid);` |
| `toJRops(rops: Traversable[Rop]): JexlToJRops` | Обход записей, возвращенных объектным запросом, например byParent Выполняет запрос на чтение к базе данных и позволяет обойти записи | `var l = toJRops(Btk_SomeEntityApi.byParent(rop)).asList(); for(r:l){ logInfo(r.id); logInfo(r.sSystemName); }` |
| `toJObject(json: String): JexlToJObject` | Парсинг json-объекта | `var jData = toJObject('{"id": 1}'); logInfo(jData.getLong("id"));` |

## Работа с BTS процедурами

**Переход в раздел процедур:**

Для того чтобы открыть перечень BTS-процедур, перейдите в **Настройки системы** → **Настройки и сервисы** → **Процедуры**. Здесь можно просмотреть различные варианты существующих процедур. Можно перейти в карточку любой процедуры и посмотреть, какие действия там описаны.

Выберите в фильтре **Тип: Процедура для сбыта** и создайте новую тестовую процедуру, нажав кнопку **Создать**. Введите наименование и код процедуры.

**Написание JEXL-кода:**

Предположим, мы хотим рассчитать некоторую формулу на основе входящих параметров. Чтобы процедура вернула значение, объявим раздел с результатом и вернем соответствующее значение. Объявим входные параметры и расчетную формулу:
```
var nvParam1 = npInParam1;
var nvParam2 = npInParam2;
var nvParam3 = npInParam3;
var nvSum = (npInParam1 + npInParam2) * npInParam3;

// Для того чтобы процедура вернула значение, добавим возврат результата:
return nvSum;
```
Сохраните код.

**Вызов процедуры через JEXL-редактор:**

Перейдите в отдельный редактор JEXL. Объявите служебную функцию для типизации данных. Данная функция будет возвращать тип данных scala.Tuple2, состоящий из двух переменных.
```
var tuple2 = (v1, v2) -> { return new(`scala.Tuple2`, v1, v2); };
```
Заведите массив параметров, в котором запишите входящие значения. В нем будет объявлен массив Scala (это делается за счет указания последнего элемента с троеточием). Первые три элемента будут объявлены с помощью функции tuple2. В каждом таком элементе массива будет указано название параметра и его значение.
```
var tuple2Params = asScala([
    tuple2("npInParam1", 10l),
    tuple2("npInParam2", 125l),
    tuple2("npInParam3", 1000l),
    ...
]);
```
Объявите переменную, в которую при помощи API-функции findByMnemoCode поместите идентификатор ранее созданной процедуры:
```
val idvBtsProcedure = Bts_ProcedureApi.findByMnemoCode(`Sale_Test1`);
```
Далее вызовите конструкцию для расчета значения BTS-процедуры и запишите результат в переменную. Для этого используйте API-метод Bts_ProcedureLib, передав в него:
- идентификатор процедуры;
- массив параметров;
- дополнительный контекст выборки для расчета.
```
var nvSumCalc = Bts_ProcedureLib.execById(idvBtsProcedure, tuple2Params, selection.coreRep());
```
Метод `Bts_ProcedureLib.execById` ожидает параметры в формате, который понимает Scala, поэтому мы используем кортеж. Кортежи здесь — это "мост" между JEXL и Scala, обеспечивающий четкую, безопасную и удобную передачу параметров.

Верните результат процедуры и выведите его на экран.
```
dialogs.showMessage(toString(nvSumCalc));
```
Аналогичным образом можно создавать и более сложные BTS-процедуры.

## Работа с мониторингом сессий сервера приложений

Система автоматически ведет метаинформацию о сессиях всех пользователей. Доступ к этой информации организован следующим образом:

**Настройки системы** → **Сервис** → **Инструменты** → **Мониторинг сессий сервера приложений**.

В списке отображаются все активные сессии с указанием последнего выполненного действия для каждого пользователя.

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

Получение текущего действия:
```
session.applicationInfo().action();
```
Обновление информации о действии:
```
session.applicationInfo().action_$eq("Значение текущего действия");
```
Пример использования:
```
for (ropWW: ropaIntWar) {
    session.applicationInfo().action_$eq(```Phosagro_SB0205_NEW - ВП - ${toString(ropWW.gid)}```);
    Stk_InternalWarrantApi.setdExecDateOut(ropWW, cDate);
    session.applicationInfo().action_$eq(```Phosagro_SB0205_NEW - выполняем ВП - ${toString(ropWW.gid)}```);
    Stk_InternalWarrantApi.setidStateOut(ropWW, idvStateIntWar);
    session.applicationInfo().action_$eq(```Phosagro_SB0205_NEW - Создаем РСО```);
    var ropWarOut = Stk_WarrantOutApi.createWarrantOutByInternalWarrant(ropWW);
    // Заполняем даты исполнения
    Stk_WarrantOutApi.setdDocDate(ropWarOut, cDate);
    Stk_WarrantOutApi.setdExecDate(ropWarOut, cDate);
    // Выполняем РСО
    session.applicationInfo().action_$eq(```Phosagro_SB0205_NEW - выполняем ВП - ${toString(ropWarOut.gid)}```);
    Stk_WarrantOutApi.setidState(ropWarOut, idvStateWarr);
};
```
**Метод setUniCharValue** используется для установки значения универсальной характеристики (универсального атрибута) объекта в системе.
```
setUniCharValue(rop: ApiRop, idpUniChar: Long, pValue: Any): Unit
```
| Параметр | Тип (Scala) | Описание |
|----------|-------------|----------|
| rop | Rop | Контекст выполнения операции (объект, к которому применяется изменение) |
| idpUniChar | Long | Идентификатор универсальной характеристики |
| pValue | Any | Новое значение характеристики (может быть Long, String и др) |

Пример использования:
```
// 1. Получаем GID значения "." из справочника
val gidChr = sql("""
    SELECT r.gid
    FROM Btk_UniversalReference r
    JOIN btk_objecttype b ON r.idobjecttype = b.id
    WHERE r.sSystemname = '.'
    AND b.scode = 'ur_O2C_sort_type'
""").asSingle();

Stm_OrderOutApi.setUniCharValue(rop, 'O2C_sort_type', gidChr.gid);
```
## Работа с печатью

Пользователь может:
- Сформировать отчёт по его системному имени.
- Запустить JEXL-скрипт печатной формы, привязанной к типу объекта.

Создание отчета происходит с помощью следующего метода:
```
Rpt_Lib.createReportExJexl(
    reportName: String,
    reportVersionDate: Date,
    postBuildAction: PostBuildAction,
    propertyMap: Map[String, Any],
    idpObjectTypePrintForm: NLong = None.nl
): Unit
```
| Параметр | Тип (Scala) | Описание |
|----------|-------------|----------|
| idpObjectTypePrintForm | Long | id ПФ на типе объекта. Может быть null |
| reportName | String | Имя отчета |
| reportVersionDate | Date | Дата |
| postBuildAction | PostBuildAction | Действие, которое необходимо произвести после заполнения отчёта |
| propertyMap | Map[String, Any] | Карта входящих параметров |

Пример использования:
```
var idvOrderClass = selection.getVar("idClass");
var idvOrder = selection.getVar("id");

var NLong = function(number) { // локальная функция для преобразования Long в NLong
    return new ("ru.bitec.app.gtk.lang.NLong", number);
};

var spReportName = "Stm_InvoiceOut";
var dpReportVersionDate = sysDate();
var vPostBuildAction = session
    .sbtClassLoader()
    .loadClass('ru.bitec.app.gtk.gl.postbuildaction.PostBuildAction')
    .design();

var propertyMap = {
    "idSrcObject" : NLong(idvOrder),
    "idSrcClass" : NLong(idvOrderClass)
};

var idpObjectTypePrintForm = null;

Rpt_Lib.createReportExJexl(
    spReportName,
    dpReportVersionDate,
    vPostBuildAction,
    asScala(propertyMap),
    idpObjectTypePrintForm
);
```
**Тип PostBuildAction:**

PostBuildAction — это перечисление (enum) из класса ru.bitec.app.gtk.gl.postbuildaction.PostBuildAction, определяющее действия после построения отчета.

**Доступные значения:**
- `.show()` — загружает отчет на клиент;
- `.save()` — сохраняет отчет;
- `.print()` — загружает отчет на клиент и, если в конфигурации системы включена опция печати (Configuration.Printing.enableNetworkPrinting), печатает отчет на локальном (для сервера) принтере.

**Примечание:**
Для `.print()` поведение зависит от конфигурации сервера. Если печать не настроена, отчет просто загружается на клиент.

**Особенности:**
- Для работы с ID используется тип NLong (внутренний класс системы).
- asScala() преобразует Java-коллекции в Scala-совместимые.
- Если idpObjectTypePrintForm = null — отчёт строится по reportName.