Практические советы#

Входные параметры процедур#

Процедуры процесса#

Входными параметрами процедур процесса являются идентификатор процесса idpProcess и текстовый параметр param, который задается в интерфейсе задания процедуры.

Процедуры состояния#

Входными параметрами процедуры состояния являются идентификатор состояния процесса idpPrState и текстовый параметр param, который задается в интерфейсе задания процедуры.

Процедуры потока#

Входными параметрами процедуры потока являются идентификатор состояния процесса idpPrState, из которого выходит поток, и идентификатор самого потока idpPSElFlow.

Процедуры задач#

Процедура формирования текста задачи#

Входным параметром процедуры формирования текста задачи является идентификатор задачи idpTask.

Процедура, выполняемая после выполнения задачи#

Входными параметрами процедуры, выполняемой после выполнения задачи, являются идентификатор задачи idpTask и текстовый параметр param, который задается в интерфейсе задания процедуры.

Зарезервированные слова в jexl-скриптах процедур#

Кроме служебных слов, которые можно посмотреть в документации к jexl (https://commons.apache.org/proper/commons-jexl/reference/syntax.html), зарезервированными словами в скриптах процедур являются только их входные параметры, которые перечислены выше в разделе Входные параметры.

Список тэгов, доступных в шаблонах задачи#

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

  • [Process] - наименование процесса текущей задачи,

  • [State] - наименование состояния процесса текущей задачи,

  • [Subject] - наименование субъекта процесса текущей задачи,

  • [Now] - текущие дата и время в формате dd.MM.yyyy HH:mm:ss,

  • [Date] - текущая дата в формате dd.MM.yyyy,

  • [ParentTask] - наименование исходной задачи,

  • [ThreadStartTask] - наименование стартовой задачи потока,

  • [SourceComment] - комментарий исходной задачи,

  • [ThreadStartComment] - комментарий стартовой задачи потока,

  • [CorrectionThreadComments] - комментарии корректировки задачи,

  • [Task] - наименование текущей задачи,

  • [TaskMC]- мнемокод текущей задачи,

  • [TaskURL] - url-ссылка на задачу

  • [Doc] - наименование основного документа, прикрепленного к текущей задаче,

  • [DocMC]- мнемокод основного документа, прикрепленного к текущей задаче,

  • [Doc#attr_name]- значение атрибута attr_name основного документа, прикрепленного к текущей задаче (если attr_name - ссылочный атрибут, то будет получено наименование объекта, на которой ссылается атрибут)

Подходы для получения данных для обработки внутри процедуры#

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

//метод получения идентификатора процесса idvProc по идентификатору состояния idpPrState
var idvProc = Bpm_ProcLibraryPkg.getProcByState(idpPrState);
//метод загрузки объекта процесса ropProc по его идентификатору idvProc
var ropProc = Bpm_ProcessApi.load(idvProc);
//метод превращения ропы объекта в JexlRop для более удобного обращения к атрибутам объекта
var jRopProc = new("ru.bitec.app.gtk.jexl.session.JexlRop", ropProc);
//метод получения гида документа процесса gidDoc по идентификатору состояния idpPrState
var gidDoc = Bpm_PrDocApi.getProcDocByPrState(idpPrState);
//загрузка ропы документа по его гиду (например, в данном случае это объект класса Wf_Doc)
var ropDoc = Wf_DocApi.loadByGid(gidDoc);
var jRopDoc = new("ru.bitec.app.gtk.jexl.session.JexlRop", ropDoc);
//получим из документа указанное в нем подразделение idDepartment
var idDepartment = jRopDoc.idDepartment;
//пусть в качестве параметра в процедуру мы передали системное имя нужного нам подразделения 'Otdel1', присвоим его в переменную
var svOurDepartment = param;
//получим идентификатор нашего подразделения с помощью метода нахождения его по мнемокоду
var idvOurDepartment = Bs_DepartmentApi.findbyMnemoCode(svOurDepartment);
//сравним подразделение в документе и наше, и положим в булеву переменную 
var isOurDepartment = idDepartment == idvOurDepartment;
//системное имя переменной
var spVarCode = "bNeedHeadPersonSign";
//положим полученное значение в переменную процесса
Bpm_ProcessApi.setProcessVar(ropProc, spVarCode, isOurDepartment);
//если нужное нам подразделение, то заполним начальником подразделения соответствующий субъект
if (isOurDepartment) {
    //системное имя субъекта процесса
    var svSubj = "HeadPerson";
    //получение субъекта по его системному имени
    var ropTargetOpt = Bpm_PrSubjectApi.getByPSSubject(ropProc, svSubj);
    //проверка наличия нашего субъекта в схеме
    if (ropTargetOpt.isDefined()) {
        //получаем субъект, если он есть
        var ropTarget = ropTargetOpt.get();
        //запишем SQL-запрос к БД для получения идентификатора пользователя начальника нашего подразделения
        var query = `select coalesce (t.id,0) as iduser 
                   from btk_user t
                   join bs_person t1 on t.id = t1.iduser
                   join bs_department t2 on t2.idmngperson = t1.id
                  where t2.id = ` + toString(idDepartment);
        //выполним запрос, и результат представим в виде коллекции значений
        var l = sql(query).asList();
        //обойдем получившуюся коллекцию значений
        for (w:l){
            //обращаемся к атрибуту, который мы получили из запроса, следующим образом w.iduser
            //кидаем ошибку, если пользователя не нашли
            if (w.iduser == 0) {
                raise("Не найден пользователь");
            }
            //если нашли, регистрируем пользователя в субъект
            Bpm_PrSubjUserApi.register(ropTarget, w.iduser, 0B, 0B);
        }
    } else {
        raise("Субъект " + svSubj + " не найден. Обратитесь к администратору");
    }
}

В данной процедуре происходит сравнение атрибута Подразделение в документе процесса (документ, в данном случае, является объектом класса Wf_Doc) с переданным, в качестве параметра в процедуру, подразделением. Результат их сравнения записывается в переменную процесса. Если подразделения совпадают, то с помощью SQL-запроса из база данных берется пользователь, являющийся начальником данного пдразделения, и этот пользователь регистрируется в субъект процесса.

Некоторые обязательные действия, которые всегда должны присутствовать в процедурах#

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

Методы получения процесса#

//метод получения идентификатора процесса idvProc по идентификатору состояния idpPrState (для процедур состояния и потока)
var idvProc = Bpm_ProcLibraryPkg.getProcByState(idpPrState);
//метод получения идентификатора процесса idvProc по идентификатору задачи idpTask (для процедур задачи)
var idvProce = Bpm_TaskApi.getProcessId(idpPrState);

Получение данных текущего запущенного процесса#

Методы получения идентификатора процесса описаны выше в разделе Методы получения процесса. При наличии идентификатора процесса можно загрузить его объект с помощью вызова метода load из Api класса:

//метод загрузки объекта процесса ropProc по его идентификатору idvProc
var ropProc = Bpm_ProcessApi.load(idvProc);

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

//метод превращения ропы объекта в JexlRop для более удобного обращения к атрибутам объекта
var jRopProc = new("ru.bitec.app.gtk.jexl.session.JexlRop", ropProc);

Обращение к атрибутам такого объекта производится следующим образом: jRopProc.имя_атрибута.

Получение данных внутри процедур из разных объектов#

Загрузка объектов в скриптах может производиться следующим образом (на примере сверху):

//метод загрузки объекта по идентификатору с помощью метода load
var ropProc = Bpm_ProcessApi.load(idvProc);

//метод загрузки объекта по его гиду с помощью метода loadByGid (например, в данном случае это объект класса Wf_Doc)
var ropDoc = Wf_DocApi.loadByGid(gidDoc);

Для нахождения идентификатора объекта по его мнемокоду используется метод findByMnemoCode из Api класса объекта:

//получим идентификатор нашего подразделения с помощью метода нахождения его по мнемокоду
var idvOurDepartment = Bs_DepartmentApi.findbyMnemoCode(svOurDepartment);

Также получить необходимые данные можно с помощью sql-запросов:

//запишем SQL-запрос к БД для получения идентификатора пользователя начальника нашего подразделения
        var query = `select coalesce (t.id,0) as iduser 
                   from btk_user t
                   join bs_person t1 on t.id = t1.iduser
                   join bs_department t2 on t2.idmngperson = t1.id
                  where t2.id = ` + toString(idDepartment);
        //выполним запрос, и результат представим в виде коллекции значений
        var l = sql(query).asList();

Все возможные методы выполнения sql-запросов можно посмотреть в контекстной помощи jexl-скрипта. Найти ее можно по пути: пункт меню Сервисы - Инструменты - Выполнить jexl-скрипт, в открывшемся окне выполнить операцию Помощь.

Методы получения документа процесса и работы с его атрибутами и коллекциями#

//метод получения гида документа процесса gidDoc по идентификатору состояния idpPrState
var gidDoc = Bpm_PrDocApi.getProcDocByPrState(idpPrState);

//загрузим объект по его гиду с помощью метода loadByGid (например, в данном случае это объект класса Wf_Doc)
var ropDoc = Wf_DocApi.loadByGid(gidDoc);
//метод превращения ропы объекта в JexlRop для более удобного обращения к атрибутам объекта
var jRopProc = new("ru.bitec.app.gtk.jexl.session.JexlRop", ropDoc);
//получим идентификаторы объектов версии документа Wf_DocVer, данный класс является коллекцией к Wf_Doc
var listDocVer = sql(`
    select t.id as idverdoc 
      from wf_docver t 
     where t.idDoc = ` + toString(ropDoc.id));
//обойдем все полученные идентификаторы
for (i:listDocVer) {
    //загрузим объекты по идентификатору
    var ropDocVer = Wf_DocVerApi.load(i.idverdoc)
    var jRopDocVer = new("ru.bitec.app.gtk.jexl.session.JexlRop", ropDocVer);
    //изменим атрибут "Описание" в полученном объекте, например, добавим строчку "Новая строка в описании"
    var svDesc = jRopDocVer.sDescription + " Новая строка в описании";
    Wf_DocVerApi.setsDescription(ropDocVer, svDesc);
    //для изменения ссылочных атрибутов объекта необходимо указывать именно идентификаторы объекта, на который ссылается атрибут, т.е. id или gid
    //например, при задании "Состояния" объекта idState необходимо указывать соответствующий id в классе Btk_ClassState, а при задании "Формата бумаги" gidFormatPaper - gid соответствующего объекта из класса Btk_Object
}

Работа с переменными процесса#

Метод получения значения переменной процесса#

//метод получения значения переменной процесса value по ропу процесса ropProc и системному имени переменной процесса spVarCode
var value = Bpm_ProcessApi.getProcessVar(ropProc, spVarCode);

Метод установки значения переменной процесса#

//метод установки значения переменной процесса value по ропу процесса ropProc и системному имени переменной процесса spVarCode
Bpm_ProcessApi.setProcessVar(ropProc, spVarCode, value);

//из примера выше
//сравним подразделение в документе и наше, и положим в булеву переменную 
var isOurDepartment = idDepartment == idvOurDepartment;
//системное имя переменной
var spVarCode = "bNeedHeadPersonSign";
//положим полученное значение в переменную процесса
Bpm_ProcessApi.setProcessVar(ropProc, spVarCode, isOurDepartment);

Получение текущей версии схемы бинес-процесса#

//метод получения текущей версии схемы БП, который принимает на вход роп процесса, и возвращает роп версии схемы БП
var ropPSVersion = Bpm_PSVersionApi.getByProcess(ropProc);

Получение задач процесса#

Получить весь список задач процесса можно с помощью метода

`Bpm_TaskApi.getTaskByProcess(idvProc)`, 

в который необходимо передать идентификатор процесса. Из метода вернется список ропов задачи.

Пример:

//получим все задачи процесса
var list = asJava(Bpm_TaskApi.getTaskByProcess(idvProc));
//обойдем весь список задач
for (i:list) {
    //преобразуем вернувшиеся ропы в JexlRop
    var jRopTask = new("ru.bitec.app.gtk.jexl.session.JexlRop", i);
    //...

    //для нахождения выполненных задач можно сравнить состояние задачи с состоянием "Выполнено"
    //получим идентификатор состояния "Выполнено" для задач 
    var idvStateDone = Btk_ClassStateApi.findByNameAndIdClass("Bpm_StateDone", Btk_ClassApi.findByMnemoCode("Bpm_Task"), 0B);
    if (jRopTask.idState == idvStateDone) {
        //если состояние данной задачи "Выполнено"
        //...
    }
}

Задание условия пропуска этапа#

Для этапов с типом «Необязательное задание» в схеме маршрута необходимо указывать условия пропуска. Условие пропуска этапа должно содержать в себе jexl-код, возвращающий булево значение. Если jexl-код вернул true - то данный этап пропускается, если false - то выполняется. Для того, чтобы условие было максимально простым, в нем можно обращаться к переменным процесса.

bNeedHeadPersonSign == false //в этом случае этап будет пропущен, если переменная процесса bNeedHeadPersonSign равна false 

Динамическое назначение исполнителей по задаче для определенного этапа#

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

`Bpm_PrSubjUserApi.register`, 

который принимает два параметра: rop субъекта процесса и идентификатор пользователя.

//системное имя субъекта процесса
var svSubj = "HeadPerson";
//получение субъекта по его системному имени
var ropTargetOpt = Bpm_PrSubjectApi.getByPSSubject(ropProc, svSubj);
//проверка наличия нашего субъекта в схеме
if (ropTargetOpt.isDefined()) {
    //получаем субъект, если он есть
    var ropTarget = ropTargetOpt.get();
    //запишем SQL-запрос к БД для получения идентификатора пользователя начальника нашего подразделения
    var query = `select coalesce (t.id,0) as iduser 
                from btk_user t
                join bs_person t1 on t.id = t1.iduser
                join bs_department t2 on t2.idmngperson = t1.id
                where t2.id = ` + toString(idDepartment);
    //выполним запрос, и результат представим в виде коллекции значений
    var l = sql(query).asList();
    //обойдем получившуюся коллекцию значений
    for (w:l){
        //обращаемся к атрибуту, который мы получили из запроса, следующим образом w.iduser
        //кидаем ошибку, если пользователя не нашли
        if (w.iduser == 0) {raise("Не найден пользователь");}
        //если нашли, регистрируем пользователя в субъект
        Bpm_PrSubjUserApi.register(ropTarget, w.iduser);
    }
} else {
    raise("Субъект " + svSubj + " не найден. Обратитесь к администратору");
}

Случаи, когда в субъекте несколько пользователей#

  1. Если для одного субъекта процесса заданно несколько исполнителей, тогда для каждого из исполнителей будет сгенерирована задача.

Как видно на рисунке выше, для состояния «Согласование согласующими лицами: 4ый ур. согласования» задано пять согласующих лиц, каждый из согласующих получает личную задачу на согласование документа.

  1. Задачи всегда направляются параллельно, но есть возможность настроить состояния маршрута таким образом, чтобы исполнители получали задачи последовательно. Для примера разберем маршрут «Согласование распоряжения».
    Для того, чтобы определить, будет согласование происходить параллельно или последовательно, для документа была добавлена и выведена в фрейм характеристик объектная характеристика «Параллельное согласование».

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

В состоянии маршрута «Определение начальных данных» при помощи процедуры «Получение значения атрибута в переменную» происходит определение переменной «Маршрут параллельный» (isParallel), а также происходит заполнение субъекта согласующих лиц (Coordinator) при помощи процедуры «Обновление состава согласующих лиц из документа».

Далее в состоянии «Согласование параллельно?» с типом эксклюзивный шлюз, в зависимости от значения переменной, определяется последовательно или параллельно документ будут согласовывать согласующие лица.

Если значение переменной не равно 1, маршрут переходит в поток «Проверка пользователей», в котором будет осуществлено последовательное согласование документа.

На данном этапе в субъекте «Согласующие» (Coordinator) может находиться несколько исполнителей, и при направлении задачи по данному субъекту, задачи направятся всем исполнителям параллельно. Для этого реализовано следующее состояние «Проверка пользователя», в котором циклично будет отбираться один исполнитель из субъекта «Согласующие», и только для него будет направляться задача. Цикл будет повторяться, пока задачи не будут направлены всем исполнителям.

В состоянии «Проверка пользователей» последовательно выполняются две процедуры:

• Процедура перемещения первого пользователя субъекта в другой субъект – данная процедура берет первого исполнителя из субъекта «Согласующие» (Coordinator) и перемещает его в другой субъект «Последовательные согласующие» (NumCoordinator). В дальнейшем задача будет направлена для субъекта «Последовательные согласующие», в котором будет находиться только один испонитель.

• Проверка отсутствия пользователей в субъекте процесса. Результат записываются в переданную переменную – данная процедура производит проверку на наличие исполнителей в субъекте «Последовательные согласующие» (NumCoordinator), и записывает значение (1 – отсутствует или 0 – пользователь задан в субъекте) в переменную «Отсутствует последовательный согласующий» (isEmptyNumCoordinator).

Если значение переменной «Отсутствует последовательный согласующий» = 0 (значит в субъекте есть пользователь и на него можно направить задачу), происходит переход в состояние «Последовательное согласование», на котором исполнителю, перемещенному в субъект «Последовательные согласующие» (NumCoordinator), будет направлена задача.

Если значение переменной «Отсутствует последовательный согласующий» = 1 (для субъекта не указан пользователь, что предполагает, что все исполнители завершили последовательное согласование), происходит переход в состояние «Подписант работает в системе?», что по сути является выходом из последовательного согласования на маршруте.

На состоянии «Последовательное согласование» согласующему из субъекта «Последовательные согласующие» приходит задача на согласование. В случае успешного согласования, выполняется процедура «Процедура перемещения первого пользователя субъекта в другой субъект», которая перемещает исполнителя из субъекта «Последовательные согласующие» (NumCoordinator) в субъект «Архив согласующих» (CoordinatorArchive).

Исполнители, перемещенные в субъект «Архив согласующих» (CoordinatorArchive), далее не участвуют в согласовании, т.к. свою задачу по согласованию они выполнили. Маршрут при этом возвращается на состояние «Проверка пользователей», на котором повторно происходит перемещение следующего исполнителя из субъекта «Согласующие» (Coordinator) в субъект «Последовательные согласующие» (NumCoordinator). Последовательное согласование закончится тогда, когда в субъекте «Согласующие» (Coordinator) не останется пользователей, после чего процедура «Проверка отсутствия пользователей в субъекте процесса. Результат записываются в переданную переменную» вернет значение переменной «Отсутствует последовательный согласующий» = 1, и по условиям эксклюзивного шлюза маршрут выйдет из данного цикла.

  1. При выполнении параллельных задач, итоговый результат определяется по настроенным на исходящих переходах условиях.

На выбор итогового потока при выполнении задач исполнителями влияют такие параметры потока, как «Порядковый номер» и «Условие».

В условии есть выбор из трех опций:

• Все – чтобы перейти в поток с данным условием, все исполнители должны выполнить задачу с одинаковым результатом, заданным на потоке в поле «Результат выполнения». Выполнение задачи хотя бы одним из исполнителей с отличным результатом приводит к невозможности входа в данный поток;

• Хотя бы один – чтобы перейти в поток с данным условием, хотя бы один из исполнителей должен выполнить задачу с заданным на потоке результатом, при этом задачи остальных исполнителей будут автоматически аннулированы, и маршрут перейдет в соответствующий поток;

• Хотя бы один (ждать всех) – чтобы перейти в поток с данным условием, хотя бы один из пользователей должен выполнить задачу с заданным на потоке результатом, при этом система будет ждать, пока остальные исполнители также выполнят свои задачи, и только после выполнения задач всеми исполнителями произойдет переход в заданный поток. Если для состояния настроено несколько исходящих потоков с условием «Хотя бы один (ждать всех)», после выполнения всех задач исполнителями, система автоматически выберет более приоритетный поток, ориентируясь на «Порядковый номер» потока, который также является приоритетом потоков, где 1 – максимальный приоритет. Условие «Хотя бы один» всегда является условием с максимальным приоритетом, при выполнении задачи с результатом, заданным для потока с условием «Хотя бы один», все остальные задачи аннулируются, и маршрут идет по данному потоку.

Разберем пример настроенного маршрута. Для состояния «Согласование согласующими лицами: 1ый ур. согласования» задано три исходящих потока.

Для потока №1 настроено условие «Хотя бы один (ждать всех)» – данное условие означает, что при выборе результата выполнения задачи «Отклонено» хотя бы одним из исполнителей, маршрут пойдет по данному потоку, но прежде чем войти в данный поток, все остальные исполнители должны выполнить свою задачу.

Для перехода №2 настроено условие «Все» – чтобы маршрут перешел в данный поток, необходимо чтобы все исполнители выполнили задачу с результатом «Согласовано согласующими лицами». Выполнение задачи с результатом отличным от «Согласовано согласующими лицами» хотя бы одним из исполнителем заблокирует возможность перейти в данный поток.

Для перехода №3 настроено условие «Хотя бы один (ждать всех)» – аналогично потоку №1, чтобы маршрут перешел в данный поток, хотя бы один исполнитель должен выполнить задачу с результатом «Согласовано согласующими лицами с замечаниями». Так как на состоянии настроено два потока с условием «Хотя бы один (ждать всех)», приоритет входа в поток определяется по порядковому номеру потока.

Это означает, что если один из пользователей выполнит задачу с результатом «Отклонено», а другой пользователь выполнит задачу с результатом «Согласовано согласующими лицами с замечаниями», то маршрут перейдет в поток №1, т.к. данный поток приоритетнее.

Завершение задачи при определенных условиях автоматически#

В данный момент автоматическое завершение задачи не реализовано.

Возможны самостоятельные решения, расммотрим на следующем примере:

Есть этап «Согласование СП». На выполнение задачи по этапу пользователю отводится 5 рабочих дней. За эти 5 дней пользователь может выполнить свою задачу с результатом либо «Согласовать», либо «Отклонить». Если прошло 5 дней и пользователь не завершил свою задачу, то она автоматически в фоне завершается с результатом «Завершено автоматически».

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

Bpm_TaskApi.setTaskResult(ropTask, idvResult);

который принимает в качестве параметров роп задачи ropTask и идентификатор результата idvResult, с которым эту задачу необходимо выполнить.