E.Soft Process Automation Platform (E.PAP)

Руководство разработчика

Оглавление

.

# Введение

В данном руководстве мы рассмотрим работу разработчика с платформой ”E.Soft Process Automation Platform (E.PAP)”.

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

.

# Описатели информационных объектов автоматизированной системы

Объекты в хранилище данных будем описывать при помощи метаданных следующим образом. В системе принято за правило описывать каждую сущность в отдельном файле .xml.

Для создания нового объекта необходимо вызвать метод создания нового объекта Create new metadata.

Описание сущности состоит из следующих блоков:
  • Заголовок (первая строка);
  • Блока <metadata>, внутри которого содержатся метаданные;
  • Одного или нескольких блоков <meta> содержащих описание метаданных.

Блок <meta> состоит из тега <meta> с атрибутом typeId=”entity-name”, где значение атрибута соответствует имени сущности.

NB: в целом для описания любой сущности в системе используются следующие параметры, определяющие уникальную сущность:

  • _typeId;
  • _id;
  • versionId (данный параметр является обязательным только для сущностей, в которых используется версионность).
Внутри тега <meta> может содержаться тег <extends>, внутри которого в теге <ancestor> указывается сущности, поля которых автоматически будут включены в описываемую сущность. Следует отметить, что разработчику предоставляется возможность переопределять отдельные поля, позаимствованные у родительских сущностей: для этого необходимо скопировать описание поля из родительской сущности внутрь тега <fields> дочерней сущности и изменить атрибуты по своему усмотрению.

Описание полей сущности содержится внутри тега <fields>.

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

  • text-field – текстовые поля;
  • value-field – служебные поля;
  • array-field – поле с массивом;
  • big-decimal-field – вещественные числа любой разрядности
  • bigint-field – целые числа любой разрядности
  • boolean-field – булево поле;
  • date-field – поле, содержащее дату;
  • date-time-field – поле, содержащее дату и время;
  • daterange-field – поле для диапазона дат;
  • document-field – поле, содержащее связь с другой сущностью;
  • double-field – поле, содержащее дробное значение;
  • embedded-document-field – поле, позволяющее сохранить все поля из одной сущности в другую сущность (например, формирование строки адреса на основе справочника ФИАС);
  • formula-bigdecimal-field – заданное формулой вещественное число любой разрядности;
  • formula-boolean-field – заданное формулой булево поле;
  • formula-date-field – заданное формулой поле, содержащее дату;
  • formula-date-time-field – заданное формулой поле, содержащее дату и время;
  • formula-double-field– заданное формулой поле, содержащее дробное значение;
  • formula-long-field– заданное формулой целочисленное поле;
  • formula-ref-array-field– заданное формулой поле, хранящее ссылку в виде массива;
  • formula-text-field– заданное формулой текстовое поле;
  • formula-typed-array-field– заданное формулой хранящее ссылку в виде типизированного массива;
  • formula-xml-field– заданное формулой поле для хранения xml;
  • long-field – целочисленное поле, используется, преимущественно, для идентификаторов;
  • numrange-field – поле для числового диапазона;
  • ref-array-field – поле, позволяющее хранить ссылку в виде массива;
  • substitute-text-field – поле, позволяющее использовать разные поля данной сущности в зависимости от условия;
  • typed-array-field – поле для хранения массива чисел или массива строк;
  • xml-field – поле для хранения xml.

Остановимся на некоторых типах более подробно.

.

# value-field

Обычно определяется в начале каждой сущности. Для данного поля доступен для назначения набор атрибутов alias, таких как “_table”, “_typeId”, “_id”, “_deleted” и др. Атрибутом, обязательным для назначения является параметр “_table”, остальные могут быть назначены при необходимости. Список значений alias, доступных для назначения в теге <value-field>, является фиксированным.

Также в каждом теге, описывающем поле value-field, необходимо обязательно добавлять атрибут service=”true”, указывающий, что данное поле является служебным.

В случае если для поля не были указаны иные атрибуты, кроме “_table”, это означает, что в данной таблице присутствуют сущности ТОЛЬКО указанного типа (нет необходимости различать сущности разного типа). В этом случае при загрузке каждой отдельной записи из таблицы будет считаться, что её _typeId равен тому, который указан в названии сущности.

.

# text-field

Описание текстового поля имеет следующий синтаксис:

<text-field alias=”textField” common=”true|false” description=“descriptionField”>text_field</text-field>
где
alias – имя, по которому в дальнейшем будет выполняться обращение к полю,
common=”true” означает, что для данного поля в таблице существует отдельная колонка
(common=”false” (значение по умолчанию) означает, что значение для этого поля хранится наряду со значениями других полей в поле, для которого определен <value-field> с alias=”_data” (поле для хранения xml)),
description - описание поля, которое может быть использовано для дальнейшего документирования,
text_field – имя колонки в таблице (если common=”true”) или название поля в xml (если common=”false”), в которой или котором хранится значение данного поля.
Если для value-field поля задано значение alias=”_data”, то данный тег указывает, в каком поле таблицы хранятся второстепенные данные для сущности в формате xml. В данную колонку целесообразно выносить параметры, которые должны загружаться при отображении сущности, но по которым не предусматривается возможность поиска. В это поле таблицы будут сохраняться поля сущности, для которых задано значение атрибута common=”false”.
.

# boolean-field, date-field, date-time-field, long-field, double-field, xml-field, big-decimal-field, bigint-field

Примитивные поля, которые могут содержать примитивные значения.
Для каждого из перечисленных тегов предусмотрено использование атрибутов alias и common.
.

# ref-array-field

Поле позволяет сохранить в базу массив ссылок на экземпляры других сущностей (одно или несколько значений, или ни одного). Каждое значение, передаваемое в базу, включает три параметра: _typeId, _id, _versionId, которые могут отображаться в указанном для поля сущности поле таблицы (если атрибут common=”true”) или внутри соответствующего тега xml в поле, указанном для хранения xml.
.

# typed-array-field

Поле представляет собой массив, предназначенный для хранения примитивных значений. Помимо атрибутов alias и common, для данного поля предусмотрен атрибут type, который может принимать одно из значений “big-decimal”, “boolean”, “long”, “text”, “double”, “document”, которое определяет тип объектов, хранящихся внутри поля.
.

# document-field

Поле содержит связь/и данной сущности с другими сущностями. Тег содержит следующие атрибуты:
<document-field alias=”fieldAlias” typeId=”user”>
где
alias - имя, по которому в дальнейшем будет выполняться обращение к полю,
typeId – имя сущности, ссылка на которую будет описана в данном поле.
В данном теге размещается вложенный тег, который указывает на тип используемой связи (m2m (многие ко многим), m2o (многие к одному), o2m (один ко многим), mapped, o2o (один к одному)).
.

# formula-bigdecimal-field, formula-boolean-field, formula-date-field, formula-date-time-field, formula-double-field, formula-long-field, formula-ref-array-field, formula-text-field, formula-typed-array-field, formula-xml-field

Поля, содержащие вычисляемые значения, в том числе подзапросы или вызовы функций. Синтаксис, например, имеет следующий вид:
<formula-long-field alias="needProcess" lazy="true" description="Вычисляемое поле. Наличие необработанных объектов">         
  <formula>
     (SELECT 1
      FROM table1 d
      JOIN table2 t ON d.id = t.doc_id WHERE d.status_id = 1 AND t.upload_id = ${alias}.id            
    )
 
</formula> </formula-long-field>
.

# substitute-text-field

Позволяет использовать разные поля данной сущности в зависимости от условия;

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

<meta typeId="subst-entity" versionId="0" actual="true">
  <fields>
      <value-field alias="_schema">tmp</value-field>
      <value-field alias="_table">subst_entities</value-field>
      <value-field alias="_id">id</value-field>
      <text-field alias="docNum" common="true">doc_num</text-field>
 
      <text-field alias="valueRu" common="true" lazy="true">value_ru</text-field>
      <text-field alias="valueEn" common="true" lazy="true">value_en</text-field>
      <text-field alias="valueDef" common="true" lazy="true">value_def</text-field>
 
      <substitute-text-field alias="substField" lazy="true">
          <condition language="js">context.getLocale()</condition>
          <if value="ru">valueRu</if>
          <if value="en">valueEn</if>
          <default>valueDef</default>
      </substitute-text-field>
  </fields>
</meta>
.

# Связь «Многие к одному» (m2o)

Для создания связи типа «многие к одному» (many-to-one, m2o) необходимо выбрать значение m2o. В этом случае внутрь тега <m2o> добавляется тег <field>, в котором описывается между каким полем текущей сущности и каким полем внешней сущности устанавливается связь. Например: <field sourceAlias="usrId" destinationAlias="_id"/>, где атрибут указывает на sourceAlias – поле текущей сущности, для которого устанавливается связь в полем внешней сущности, указанным в атрибуте destinationAlias.
.

# Связь «Один ко многим» (o2m)

Для создания связи типа «один ко многим» (one-to-many, o2m) нужно сначала создать связь m2o в одной из связываемых сущностей со ссылкой на другую сущность. Затем во второй связываемой сущности в теге <document-field> необходимо выбрать тип связи <o2m>, а в теге field отобразить атрибуты sourceAlias и destinationAlias в обратном порядке по сравнению со связью в первой связываемой сущности. Т.е., значение атрибута destinationAlias поля document-field первой сущности используется в качестве значения атрибута sourceAlias второй сущности и наоборот, значение атрибута sourceAlias для document-field первой сущности используется в качестве значения атрибута destinationAlias второй сущности.
ЛИБО перейти во вторую связываемую сущность и добавить в нее поле вида document-field, в котором добавить связь mapped. В созданном теге <mapped> следует указать атрибуты relationship=”M2O” (тип связи из исходного документа в текущий) и mappedBy=”usr” (имя связи m2o, для которой данная связь будет обратной, o2m).
.

# Связь «Многие ко многим» (m2m)

При создании связи «многие ко многим» (many-to-many, m2m) в теге <m2m> необходимо указать значение атрибута joinTable (joinTable=”table_name”), указав таким образом имя промежуточной таблицы (реально существующей таблицы в БД).

Далее внутри тега <sources> необходимо в теге <field> указать название поля сущности, из которой устанавливается связь (alias) и колонку промежуточной таблицы (column), используемую для формирования связи с одной из исходных таблиц (например, <field alias="usrId" column="column_one"/ >). Аналогично внутри тега <destinations> необходимо в теге <field> указать название поля сущности, к которой устанавливается связь (alias) и колонку таблицы (column), используемую для формирования связи со второй из исходных таблиц (например, <field alias="_id" column="column_second"/>). Важно, что обе указанные колонки (в <sources> и в <destinations>) должны быть реально существующими колонками промежуточной таблицы table_name.

NB: Здесь присутствует определённое расхождение в понятиях, так как в alias указывается название поля (не колонки), а в column – название колонки. Это сделано для упрощения: не требуется заводить отдельный маппинг промежуточной таблицы.

.

# Формирование запроса к базе данных

Для формирования запросов к сформированной базе сущностей необходимо познакомиться со следующими базовыми объектами:
storage – объект, представляющий собой базу данных;
query() – метод объекта storage, позволяющий сформировать запрос к определенной сущности;
Например, storage.query(“user”) – сформировать запрос к сущности user.
use() – показывает, какое или какие поля другой сущности (отличающейся от сущности из query) нужно извлечь;
add() – позволяет добавлять ограничения (restrictions либо проекции (projections) для отображения результата запроса;
например, .add(restrictions.id(0)) – ограничить перечень пользователей пользователем, чей id = 0.
list() – команда вернуть список результатов выполнения запроса;
get(x) – получить объект х списка результатов выполнения запроса;
getField(fieldName) – для указанного объекта получить поле fieldName;
count() – показать число полученных результатов;
one() – показать первый результат;
fields() = get(x).getField()
.

# Ограничения на отображение результатов – restrictions

restrictions
public interface Restrictions {
  public Restriction eq(String field, @Nullable Object value);
  public Restriction same(String field, String otherField);
  public Restriction ge(String field, Object value);
  public Restriction gt(String field, Object value);
  public Restriction le(String field, Object value);
  public Restriction lt(String field, Object value);
  public Restriction ne(String field, Object value);
  public Restriction or(Restriction... restrictions);
  /** convenient method or for execute from js */
  public Restriction or(Restriction restriction1, Restriction restriction2);
  public Restriction and(Restriction... restrictions);
  public Restriction commaSeparated(Restriction... restrictions);
  public Restriction in(String field, Object... values);
  public Restriction in(String[] fields, IEntityQuery query);
  public Restriction isNull(String field);
  public Restriction isNotNull(String field);
  /** This method is analogue in and you have to use it instead in from js */
  public Restriction present(String field, Object... values);
  public Restriction present(String field, Set values);
  public Restriction present(String[] fields, IEntityQuery query);
  public Restriction id(Object value);
  public Restriction id(String alias, Object value);
  public Restriction versionId(Object value);
  public Restriction versionId(String alias, Object value);
  public Restriction typeId(Object value);
  public Restriction typeId(String alias, Object value);
  public Restriction actual(boolean value);
  public Restriction actual(String alias, boolean value);
  public Restriction deleted(boolean value);
  public Restriction deleted(String alias, boolean value);
  public Restriction not(Restriction restriction);
  public Restriction like(String field, String pattern);
  public Restriction like(String alias, String field, String pattern);
  public Restriction ilike(String field, String pattern);
  public Restriction ilike(String alias, String field, String pattern);
  public Restriction field(String alias);
  public Restriction field(String alias, String field);
  public Restriction field(String alias, Field field);
  public Restriction field(String alias, String field, String resultAlias);
  public Restriction hasAccess(IUser user);
  public Restriction hasAccess(String alias, IUser user);
  public Restriction hasBit(String alias, int value);
  /* xml-related restrictions */
  public Restriction hasTag(String alias, String field, String xpath);
  public Restriction hasTag(String field, String xpath);
}
Использование ограничений на отображение результата Пример: получить ФИО всех пользователей, у которых фамилия начинается с «А»
storage.query("user").add(restrictions.like("surname", "А%")).list()
.

# Объединение таблиц

Возможны два варианта.

.

# Объединение с применением join

Синтаксис: storage.query("user").join("entity", "en", "STRICT/WEAK", restrictions.r()), где «entity» - имя сущности, с которой выполняется join, “en” – псевдоним для сущности, “STRICT”/”WEAK” – режим объединения (“STRICT” эквивалентен inner join, “WEAK” – outer join).
На создаваемое объединение могут накладываться ограничения (restrictions), как и на отображение результатов запросов.

Пример: Посчитать всех пользователей, которые имеют роль «Администратор» (roles._id = 1), которых зовут Анастасия, и всех пользователей по имени Алексей, у которых нет роли «Администратор».

В дальнейшем предоставляется возможность накладывать ограничения на результат объединения.
storage.query("user").join("roles", "r", "WEAK", restrictions.eq("r.name", "admin")).add(restrictions.or( restrictions.and([restrictions.eq("r._id", 1), restrictions.like("name", "%Анастасия%")]), restrictions.and([restrictions.isNull("r.name"), restrictions.like("name", "%Алексей%")]))).count();

.

# Объединение с использованием use()/strict()

Объединение с использованием функции use() эквивалентно outer join. Синтаксис данного вида объединения выглядит следующим образом:

storage.query("table1").use(["table2", restrictions.add("param", value)])
В дальнейшем предоставляется возможность накладывать ограничения на результат объединения Объединение с использованием функции strict() эквивалентно inner join. Синтаксис объединения такого рода выглядит следующим образом:
storage.query("table1").strict(["table2", restrictions.add("param", value)])
В дальнейшем предоставляется возможность накладывать ограничения на результат объединения Примеры: Подсчитать количество пользователей, которым назначены какие-либо роли:
storage.query("user").strict(["roles"]).count()
Отобразить роли, которые не назначены ни одному пользователю
storage.query("role").use(["users"]).add(restrictions.isNull("users._id")).list()

.

# Дополнительные функции для работы с запросами:

fetch() – работа с lazy полями.

Примеры работы:
Пример 1.
Запрос: storage.query("action").list(0, 10).get(0).isFieldSet("structure") Результат: done (result = false) Комментарий: проверка, установлено ли поле «structure», результат проверки – отрицательный
Пример 2.
Запрос: storage.query("action").list(0, 10).get(0).getField("structure") Результат: done (result = ru.esoft.dom.DocumentImpl@47c5bb6a) Комментарий: получение значения из поля «structure», значение в поле есть
Пример 3.
Запрос: storage.query("action").fetch("structure").list(0, 10).get(0).isFieldSet("structure") Результат: done (result = true) Комментарий: выполнение проверки, установлено ли поле «structure», после выполнения fetch(). Результат проверки – положительный.

Примеры запросов:
storage.query("user").add(restrictions.like("surname", "А%")).list()
Комментарий: выбрать такие экземпляры сущности «user», у которых фамилия (surname) начинается с «А» и вывести их список
storage.query("user").use(["roles", "roles.parent"]).add(restrictions.or(restrictions.eq("roles._id", 4), restrictions.eq("roles.parent._id", 37))).list()
Комментарий: выбрать такие экземпляры сущности «user», у которых есть роль с идентификатором «4» или роли, для которых роль с идентификатором «37» является родительской, и отобразить список.
storage.query("user").join("roles", "roles", "STRICT").join("roles.parent", "parent", "WEAK").add(restrictions.or(restrictions.eq("roles._id", 4), restrictions.eq("parent._id", 37))).list()
Комментарий: результат запроса аналогичен результату предыдущего запроса. Запрос требует выполнения следующих действий: объединить сущности «user» и «roles» inner join’ом (нам не нужны пользователи, у которых нет ролей) и присоединить к ним родительскую для ролей сущность «parent.roles» (при этом нам не нужно отсеивать роли, у которых нет родительских ролей), затем накладываем нужные нам ограничения (id роли должен быть равен 4 или id родительской роли должен быть равен 37) и отображаем список получившихся результатов.
storage.query("role").use(["users"]).add(restrictions.isNull("users._id")).list()
Комментарий: объединяем сущность «role» с сущностью «users», добавляем ограничение, требующее показать роли, для которых нет _id пользователя (т.е., роли, которые ни одному пользователю не назначены). Что и требовалось.
storage.query("user").add(restrictions.like("name", "Алексей")).list(0, 9)
Комментарий: запрашиваем сущность «user», накладываем ограничение, согласно которому нас интересуют только те экземпляры, в которых значение поля «name» подобно «Алексей», просим показать первые 10 экземпляров, соответствующих этим ограничениям.
storage.query("role").count()
Комментарий: запрашиваем общее количество ролей в системе – получаем сущность «role», подсчитываем количество экземпляров.
storage.query("role").use(["users"]).add(restrictions.like("users.name", "%Иван%")).count()
Комментарий: запрос выводит число ролей, которые назначены пользователям с именем Иван. Запрашивается сущность «role», которая объединяется outer join’ом с сущностью «users». На результат объединения налагается ограничение о том, что значение поля «name» в сущности «users» должно быть подобно строке «Иван». Функция count подсчитывает число таких ролей.
storage.query("user").add(restrictions.isNotNull("name")).sort("fio", "desc").one().getField("fio")
Комментарий: в результате выполнения запроса отображается значение поля «fio» для пользователя, чье fio будет последним по алфавиту в списке пользователей, предварительно отбрасывая экземпляры «user», у которых не заполнено поле «name». Как работает: запрос получает сущность «user», отбрасывает те экземпляры «user», у которых поле «name» пустое, сортирует полученный список по полю «fio» по убыванию, получает первый элемент списка, получает для него поле «fio».
storage.query("user").use(["roles"]).add(restrictions.or(restrictions.eq("roles._id", 1), restrictions.like("name", "%Анастасия%"))).list()
Комментарий: запрос получает сущность «user», объединяет ее с сущностью «roles» по outer join и налагает на результат объединения ограничения: либо в поле «roles._id» для экземпляра «user» должно быть значение 1, либо в поле «name» экземпляра «user» должно быть значение, подобное «Анастасия».
storage.query("user").join("roles", "r", "WEAK", restrictions.eq("r.name", "admin")).add(restrictions.or( restrictions.and([restrictions.eq("r._id", 1),restrictions.like("name", "%Анастасия%")]), restrictions.and([restrictions.isNull("r.name"),restrictions.like("name", "%Алексей%")]))).count()
Комментарий: объединяем сущность «user» с сущностью «roles» outer join’ом (назначаем для «roles» псевдоним «r» с ограничением, что название роли равно «admin»). В результате у всех экземпляров «roles», вошедших в объединение либо будет роль «admin» (_id роли = 1), либо поле «r.name» будет пустым. После этого накладываем ограничения на результат объединения: нам нужны экземпляры, в которых либо имя пользователя подобно Анастасия и есть роль с r._id = 1, либо поле «r.name» пустое, а имя этого пользователя – Алексей. Запрос возвращает количество таких пользователей.
storage.query("user").join("roles", "r", "WEAK", restrictions.eq("r.name", "admin")).add(restrictions.or( restrictions.and([restrictions.eq("r._id", 1),restrictions.like("name", "%Анастасия%")]), restrictions.and([restrictions.isNull("r.name"),restrictions.like("name", "%Алексей%")]))).fields(“fio”)
Комментарий: аналогично предыдущему, только в результате запроса выводится перечень ФИО сотрудников, соответствующих запросу
.

# Описатели маршрутов обработки информационных объектов:

Workflow – маршрут движения объектов и/или сущностей, описанных в автоматизированной системе. В схематическом виде маршрут может выглядеть следующим образом:
workflow

По маршруту можно запустить любой объект, у которого есть метаданные. Не всякий описанный метаданными объект должен быть запущен по маршруту.
Workflow может состоять из нескольких видов элементов. Рассмотрим их подробнее.

  • SuperFlow – контейнер, в который включаются другие элементы workflow. SuperFlow может включать другое superflow (каскад). Размещение в workflow двух workflow на верхнем уровне не допускается. В настоящее время каскадирование SuperFlow в системе не используется – для группировки действий предпочтительно использование отдельных workflow с переходом из одного workflow в другой с помощью DocFlowLink.
  • В SuperFlow присутствует один входящий коннектор и как минимум один исходящий (возможно несколько исходящих коннекторов).
  • ActionFlow – блок, позволяющий выполнить в отношение объекта определенные действия (действия выполнятся, когда объект войдет в этот блок).
actionflow

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

Во многих случаях код в блоке завершается командой go или goSelf (например, self.goSelf("out", user, null, param, {}); команда goSelf, в отличие от go,при выполнении не порождает новый поток) – с помощью этой команды осуществляется переход в следующий блок с передачей в него параметров, передаваемых в качестве параметров команды.

В качестве завершения также может использоваться команда setAnswer (например, self.setAnswer(param); ), данная команда разблокирует вызывающий поток, передавая ему результат выполнения кода. Следует обратить внимание, что после setAnswer может выполняться другой код

  • UserActionWaitFlow – блок ожидания действия пользователя или системного события. При переходе в блок UserActionWaitFlow из предыдущего блока передаются параметры, показывающие, в связи с каким объектом осуществляется ожидание, и кто должен вывести указанный объект из ожидания (конкретный пользователь, группа пользователей по роли, система). При необходимости в UserActionWaitFlow можно передать дополнительные JSON параметры (в контекст);

Для UserActionWaitFlow можно задать параметры leave in и leave through. В параметре leave in указывается время до автоматического выхода из блока (в минутах); в параметре leave through – через какой коннектор осуществляется автоматический выход из блока.

  • DocFlowLink – блок вызова workflow из другого workflow. Принимает один параметр – имя вызываемого workflow;
  • Connector – добавление исходящего коннектора к выбранному блоку workflow. Для каждого добавляемого коннектора можно указать ряд следующих параметров.

Title – наименование коннектора;
Description – описание коннектора;
Service – данный признак показывает, что данное действие вызывается только программно, а не из интерфейса. В этом случае не создается кнопка, на которую мог бы нажать пользователь;
Saves state – в том случае, когда одно и то же действие может выполняться многократно без выхода из состояния (блока), у коннектора должен быть отмечен данный признак. При переходе в коннектор создается новый поток (старый остается нетронутым), который, в зависимости от кода в блоке, выполнит действие и вернет результат или завершится;
Delegate access – признак возможности выполнения данного действия от имени одного сотрудника другим сотрудником (от имени руководителя – помощником, от имени сотрудника – пользователем с ролью «делопроизводитель» или «группа контроля»);
В блоке Code разработчик имеет возможность разместить блок кода, который выполнится при прохождении через данный коннектор.
В блоке Condition – данный параметр может быть полезен, когда в данный блок приходит несколько потоков с одинаковыми параметрами, отличающихся параметрами в контексте.
.

# Пример использования платформы ”E.Soft Process Automation Platform (E.PAP)”

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

Параметры протокола представлены в таблице ниже:
Номер протокола Число
Дата протокола Дата
ФИО проверяемого Строка
Дата рождения проверяемого Дата
Полное название организации осуществлявшей проверку Строка
Квалификационная категория Число (1-4)
Дата проверки Дата
ФИО проводившего проверку Строка
Должность проводившего проверку Строка
Количество заданных вопросов Число
Количество данных правильно ответов Число
Количество ошибок при проведении практического теста Число
Результат проверки Да/Нет
Начальник управления Строка
.

# Пример cоздания xml схемы метаданных документа

Назовем объект, например, «exam-protocol». Подготовим xml файл со следующим содержимым:
<?xml version="1.0" standalone="no"?>
  <metadata xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="metadata.xsd">
      <meta typeId="exam-protocol">
          <fields>
              <value-field alias="_id">id</value-field>
              <value-field alias="_table">exam_protocols</value-field>
              <value-field alias="_typeId">type_id</value-field>
              <value-field alias="_versionId">version_id</value-field>
   
              <long-field alias="protocolId" common="true">protocol_id</long-field>
              <date-time-field alias="protocolDate" common="true">protocol_date</date-time-field>
              <text-field alias="verifiableName" common="true">verifiable_name</text-field>
              <date-time-field alias="verifiableBirthDate" common="true">verifiable_birth_date</date-time-field>
              <text-field alias="auditOrgName" common="true">audit_org_name</text-field>
              <text-field alias="category" common="true">category</text-field>
              <date-time-field alias="auditDate" common="true">audit_ate</date-time-field>
              <text-field alias="auditorName" common="true">auditor_name</text-field>
              <text-field alias="auditorJob" common="true">auditor_job</text-field>
              <long-field alias="questionsCount" common="true">questions_count</long-field>
              <long-field alias="answersCount" common="true">answers_count</long-field>
              <long-field alias="errorsCount" common="true">errors_count</long-field>
              <boolean-field alias="auditResult" common="true">audit_result</boolean-field>
              <text-field alias="auditorBoss" common="true">auditor_boss</text-field>
          </fields>
      </meta>
  </metadata>
Если нам необходимы файловые приложения в форме нужно добавить следующее:
<document-field alias="attachments"typeId="file" cascade="store">
      <m2m joinTable="document_files" aliasColumn="document_field">
          <sources>
              <field alias="_id" column="document_id"/>
              <field alias="_versionId" column="document_version_id"/>
              <field alias="_typeId" column="document_type_id"/>
          </sources>
          <destinations>
              <field alias="_id" column="file_id"/>
              <field alias="_versionId" column="file_version_id"/>
              <field alias="_typeId" column="file_type_id"/>
          </destinations>
          <fields>
              <long-field alias="index" common="true">idx</long-field>
          </fields>
      </m2m>
</document-field>
Поле events необходимо для установки статуса документа, а также позволяет обновлять таблицу при добавлении данных без ее перезагрузки
<document-field alias="events" typeId="event">
      <mapped relationship="O2M" mappedBy="document"/>
</document-field>
<document-field alias="eventsSummary" typeId="events-summary">
      <m2o>
          <field sourceAlias="_id" destinationAlias="paramId"/>
          <field sourceAlias="_typeId" destinationAlias="paramTypeId"/>
      </m2o>
</document-field> 
Далее необходимо создать в БД нужную нам таблицу и соответствующие поля.
.

# Пример запроса для получения параметров файлового вложения

if (document != null) {
  out.add(convert.document(storage.query(document.getTypeId())
      .use(["attachments"])
      .add(restrictions.id(document.getId()))
      .one()), 0);
}
.

# Пример построения маршрута документа

Создаем новый маршрут, назовем его «exam-protocol-automated» Добавляем его внутри «SuperFlow» потому как он обязателен, внутри него создаются остальные маршруты. Создаем «Add ActionFlow», соединяем коннектором входы (зеленый кружек) SuperFlow и ActionFlow. Далее добавляем «Add UserActionWaitFlow» (eventListenerFlow), соеденяем выход(красный кружек) ActionFlow и вход eventListenerFlow и задаем ему «title»: out, «service»: true (для этого два раза кликаем на красном кружке выхода ActionFlow) В поле code для ActionFlow пишем следующее:

// make new exam-protocol:
var param = storage.make("exam-protocol");
// setting fields:
param.setField("protocolId", ctx.get("protocolId"));
param.setField("protocolDate", ctx.get("protocolDate"));
param.setField("verifiableName", ctx.get("verifiableName"));
param.setField("verifiableBirthDate", ctx.get("verifiableBirthDate"));
param.setField("auditOrgName", ctx.get("auditOrgName"));
param.setField("category", ctx.get("category"));
param.setField("auditDate", ctx.get("auditDate"));
param.setField("auditorName", ctx.get("auditorName"));
param.setField("auditorJob", ctx.get("auditorJob"));
param.setField("questionsCount", ctx.get("questionsCount"));
param.setField("answersCount", ctx.get("answersCount"));
param.setField("errorsCount", ctx.get("errorsCount"));
param.setField("auditResult", ctx.get("auditResult"));
param.setField("auditorBoss", ctx.get("auditorBoss"));
// store attachments
$("updateAttachments")(param, ctx.get("attachments"));
// store document:
param = storage.store(param);
// continue execution:
self.goSelf("out", null, $("getRoleByName")("everyone"), param, {});
В методе eventListenerFlow пишем следующее:
param = storage.query(param.getTypeId()).
    add(restrictions.id(param.getId())).
    use(["eventsSummary"]).
    one();
sender.sendEvent(param, {role: role}, event);
// return created document:
self.setAnswer(param);
Посылаем событие слушателю, также получаем из базы информацию по событиям.

Далее добавим маршрут для обновления протокола. Создадим новый actionFlow с «description»: “update”. Соединим выход eventListenerFlow(“new”) со входом actionFlow(“update”) и зададим ему параметры «title»: save, «saves state»: true

В actionFlow(“update”) пишем следующее:
// setting fields:
param.setField("protocolId", ctx.get("protocolId"));
param.setField("protocolDate", ctx.get("protocolDate"));
param.setField("verifiableName", ctx.get("verifiableName"));
param.setField("verifiableBirthDate", ctx.get("verifiableBirthDate"));
param.setField("auditOrgName", ctx.get("auditOrgName"));
param.setField("category", ctx.get("category"));
param.setField("auditDate", ctx.get("auditDate"));
param.setField("auditorName", ctx.get("auditorName"));
param.setField("auditorJob", ctx.get("auditorJob"));
param.setField("questionsCount", ctx.get("questionsCount"));
param.setField("answersCount", ctx.get("answersCount"));
param.setField("errorsCount", ctx.get("errorsCount"));
param.setField("auditResult", ctx.get("auditResult"));
param.setField("auditorBoss", ctx.get("auditorBoss"));
// store attachments
$("updateAttachments")(param, ctx.get("attachments"));
//store doc
param = storage.update(param);
//return doc
self.setAnswer(param);

Далее выберем eventListenerFlow(“new”) и добавим новый коннектор(Add Workflow -> Append connector) и зададим ему «title»: “delete” , а поле code добавим следующее(посылаем событие “out:delete”):

// send notification to delete document from registry:
sender.sendEvent(param, {role: $("getRoleByName")("everyone")}, "out:" + event););
//return doc
self.setAnswer(param);

Еще раз выберем eventListenerFlow(“new”) и добавим новый коннектор(Add Workflow -> Append connector) и зададим ему «title»: “next” , а поле code добавим следующее(посылаем событие “out:next”):

// send notification to delete document from registry:
sender.sendEvent(param, {role: $("getRoleByName")("everyone")}, "out:" + event);

Создадим новый actionFlow с «description»: “deleted”. Соединим коннектор eventListenerFlow(“new”) («title»: “delete”) со входом actionFlow(“deleted”) В поле code для actionFlow(“deleted ”) пишем следующее:

param = storage.remove(param);
//return doc
self.setAnswer(param);

Создадим новый eventListenerFlow с «description»: “review”. Соединим коннектор eventListenerFlow(“new”)(«title»: “next”) и eventListenerFlow(“review”) В поле code для eventListenerFlow(“review”) напишем:

param.fetch(["eventsSummary"]);
sender.sendEvent(param, {role: $("getRoleByName")("everyone")}, event);
// return created document:
self.setAnswer(param);
.

# Пример добавления нового объекта

В событие для создания необходимо записать следующее:
en.getData().setField("_typeId", "exam-protocol");
en.getData().setField("_workflow", "exam-protocol-automated");
.

# Пример получения списка объектов

Для получения списка объектов напишем:
out.setField("_code", "alias");
out.setField("_description", "name");
storage.query("document-events").list();
storage.query("exam-protocol").use(["eventsSummary"]).list();
.

# Пример изменения событий для объектов

listeners.add(
    [{ role: $("getRoleByName")("everyone") }],
     eventRestrictions.and([
        eventRestrictions.eventIn(["new", "out:new", "review", "out:review"]),
        eventRestrictions.eq("_typeId", "exam-protocol")
    ]),
listener (en) {
    var event = en.getData().getFieldAsString("_event", "-");
    en.alert("event: " + event);
    importer.get("updateRegistry")(en, "protocolTable", "_param.", false);
});