Связь ролей BPM и Cuba

Добрый день,

Подскажите, пожалуйста, как лучше решить задачу…

Мне необходимо реализовать в BPM пользовательскую задачу, где пользователь зарвнее неизвестен и задача должна быть видна у всех пользователей с конкретной Cuba ролью (а еще лучше с разрешением). При этом я понимаю, что:

  • надо использовать Claim - т.е. задача видна у всех пользователей пока не “забрана” конкретным.Это устраивает, но есть ряд вопросов.

Я правильно понимаю, что роли процесса и роли/разрешения Cuba никак не связанны? Как их лучше связать? Я могу заполнить ProcRole при запуске процесса через запрос пользователей Cuba с конкретной ролью или разрешением (допустим равным роли в BPM), но …

Как быть если роли или права даны пользователю после запуска процесса? Т.е. мне надо заполнять ProcRole[] по достижении шага, а не при запуске. Механизм Assignee возвращает только одно пользователя (точно?), т.е. через него я не могу заполнить список потенциальных пользователей для Claim.

А желательно еще сложнее - роль может быть дана пользователю даже после остановки процесса на этой задаче. Мне надо чтоб задача у него тоже появилась.

Есть какой-то механизм связать роли BPM с ролями или разрешениями Cuba, чтоб задача появлялась в саиске у пользователя по динамической проверки наличия у него прав на момент запроса? Далее стандратный Claim устраивает.

Если нет, то как лучше подойти к такой задаче?

Спасибо

1 симпатия

На сегодня штатной возможности связать роли BPM с ролями или разрешениями Cuba нет. :frowning:
У нас была более простая задачка подобного рода:

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

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

Возможно, что-то появится в новом BPM для 7x, но не уверен.
Интересно послушать коллег из команды CUBA.
На мой взгляд, вполне востребованный сценарий, когда Задача BPM должна быть назначена не Конкретному пользователю, существующему в данный момент, а назначена Роли.
Далее, изменение персоналий пользователей в этой роли не должно влиять на исполнение задачи и последующий доступ к задаче.

Добрый день!

Связки security ролей и процессных ролей из коробки нет. Как вариант, можно делать так: в модели процесса перед задачей вызвать сервис таску, которая создаст необходимые экземпляры ProcActor. Т.е. модель у вас будет выглядеть так:
image
Expression у ServiceTask:
${sample_ProcRolesHelper.fillProcRoles('approver', 'approverSec', bpmProcInstanceId)}

Ну и собственно сам ProcRolesHelper:

@Component(ProcRolesHelper.NAME)
public class ProcRolesHelper {

    public static final String NAME = "sample_ProcRolesHelper";

    @Inject
    private Logger log;

    @Inject
    private DataManager dataManager;

    @Inject
    private Metadata metadata;

    public void fillProcRoles(String procRoleCode, String secRoleName, UUID bpmProcInstanceId) {
        ProcInstance procInstance = dataManager.load(ProcInstance.class)
                .id(bpmProcInstanceId)
                .view("procInstance-listener-view")
                .one();
        Optional<ProcRole> procRoleOpt = procInstance.getProcDefinition().getProcRoles().stream()
                .filter(procRole -> procRole.getCode().equals(procRoleCode))
                .findAny();
        if (!procRoleOpt.isPresent()) {
            log.error("ProcRole {} not found", procRoleCode);
            return;
        }
        List<User> usersWithSecRole = findUsersBySecRole(secRoleName);
        List<ProcActor> procActorsToCreate = new ArrayList<>();
        for (User user : usersWithSecRole) {
            ProcActor procActor = metadata.create(ProcActor.class);
            procActor.setUser(user);
            procActor.setProcInstance(procInstance);
            procActor.setProcRole(procRoleOpt.get());
            procActorsToCreate.add(procActor);
        }
        dataManager.commit(new CommitContext(procActorsToCreate));
    }

    private List<User> findUsersBySecRole(String secRoleName) {
        return dataManager.load(User.class)
                .query("select u from sec$User u join u.userRoles ur join ur.role r where r.name = :secRoleName")
                .parameter("secRoleName", secRoleName)
                .list();
    }
}

Демо-проект: bpm-assignment-sample.zip (110.4 КБ)

Что касается добавления пользователя в уже созданную задачу, то тут вам скорее всего придётся как-то в своём проекте отслеживать назначение пользователю security роли, где-то хранить информацию, какие proc-роли с ней связаны, находить экземпляры ProcTask и апдейтить у них коллекцию candidateUsers.

3 симпатии

Понял, большое спасибо. Думал listener вешать, но через сервисный шаг даже читабельнее …

Максим, добрый день.

.view("procInstance-listener-view")

изображение

Не пойму где взять/создать эту вьюшку

В сообщении выше есть ссылка на демо-проект. Там вы можете поиском воспользоваться и найдете места объявления и использования вьюшки.

Тестовый проект падает на этой строчке ((изображение

Может гляните что не так у меня то.

bpmdemo-roles.zip (78.4 КБ)

и модель

Удалить.json (6.6 КБ)

Кто бы еще писал ошибку с которой “падает” и путь воспроизведения проблемы на демо-проекте…
Попытался угадать…
Я под admin создал экземпляр Contract, создал ручками экз. приложенного процесса (BPM/Экземпляры процесса), связал с созданным экз. Contract, запустил процесс, принял процессную задачу, выполнил для нее процессное действие “Удалить”.
Все отработало без ошибок.

Могу предположить, что у вас dataManager.load(..).id(...).one() кидает
IllegalStateException, а такое случается, если по данному ID сущности в БД не обнаружено.
О чем сказано в Javdoc
image

1 симпатия

Добрый день, Андрей. Спасибо что помогаете ))
Если создавать ручками то все действительно отработало без ошибок, и процесс роль связывает задачу с ролью куба.

Но если это делать с экрана контракта, то падает ошибка:

Снимок экрана 2020-05-31 в 09.44.54

И теперь еще больше не понятна причина, почему ручками отрабатывает, а при “автоматизации” нет.

Спасибо

Но если это делать с экрана контракта, то падает ошибка

Как говорится “не могу не подтвердить, ни опровергнуть”.
В приведено демо-проекте нет возможности запустить процесс с контракта.
image
Собственно, в контроллере ContractBrowse нет рабочего кода который позволил бы запустить процесс с размещенного внизу procActionsFragment при выделенным Contract. А на экране ContractEdit вообще нет procActionsFragment.
Но судя по ошибке - проблема с чтением конкретного экземпляра procInstance по id.
Как вариант - есть ограничения для этой сущности, которые скрывают этот или все экземпляры procInstance от пользователя, выполняющего действие.

Андрей ещё раз спасибо за помощь, нужно выделить сущность, нажать кнопку обновить процесс, и внизу будет активным окно запуска( фрагмент).

Если использовать приложенную тут модель процесса - фрагмент не станет активным :slight_smile:
Можете проверить.

Странно, все работает, из изменения только фрагмент на верх кинул для удобства.

Конечно, у вас работает, потому что модель процесса другая.
Скачайте то, что вы выложили в эту ветку.
Контроллер ContractBrowse

private static final String PROCESS_CODE = "delete";
....
...

procActionsFragment.initializer()
                    .standard()
                    .init(PROCESS_CODE, contractsTable.getSingleSelected());

Открываем вашу модель процесса
image

Ладно, “обработал напильником” тестовый проект и смог под отладчиком вашу ошибку поймать.
Пробовали вот тут ставить точку останова и заглянуть в БД на тему того, есть ли в БД ProcInstance с данным ID?
image
На момент выполнения этого кода, ProcInstance таким ID еще не успел сохраниться в БД.
У вас в модели сразу идет вызов bpmdemo_ProcRolesHelper.fillProcRoles('boss', 'Administrators', bpmProcInstanceId).
В процессной переменной bpmProcInstanceId ID экземпляра уже есть, он СУБД еще не успела сохранить экземпляр.
Попробуйте перенести действия с только что стартовавшим процессом из модели процесса в ProcActionsFragment в слушатель setAfterStartProcessListener().

Спасибо, но не прокатило ))

bpmdemo-test.zip (79.6 КБ) Удаление заявки.json (10.7 КБ)

Ребят, вот итоговая демка и модель. Так и не могу решить как исправить этот эксепшен:

1

Как советовал Андрей выше, я пробывал через слушатель setAfterStartProcessListener(), результат такой: роли подставляются, но не назначаются, я думаю это связано с тем, что процесс уже запущен на момент назначения роли, а потом просто подставляет роль. Хотелось бы решить эту проблему. Так же пробывал сделать этот сервисом, результат такой же как вызывать эти методы таском через модель из бина - экспешен такой же как на скрине.

Спасибо.

В вашем случае проблема следующая: вы стартуете новый процесс - в этот момент открывается новая транзакция БД. В этой транзакции создаётся экземпляр ProcInstance. Затем в этой же транзакции управление переходит к первому элементу процесса (вызов ProcRolesHelper)
Внутри ProcRolesHelper вы используете DataManager для поиска экземпляра ProcInstance, но DataManager создаёт новую транзакцию, а предыдущая транзакция ещё не закоммичена. Поэтому метод load ничего и не возвращает.
Вариант1 - можете попробовать использовать TransactionalDataManager - он не создаёт новую транзакцию, а присоединяется к текущей.
Вариант 2 - если участники процесса у вас заполняются при старте процесса, а не где-то в середине его выполнения, то использовать подход с сервисом, заполняющим роли, смысла не имеет. Вот пример как задать участников процесса программно из контроллера экрана.

2 симпатии

Спасибо, Максим, буду пробывать первый вариант. Второй вариант теряют всю суть - для измениния согласующего нужен разработчик, а не человек с учеткой админа. Еще раз спасибо

Максим, спасибо. Первый вариант сработал отлично. Почти все заработало :handshake: :beers:

Я так понимаю менеджер тразакция не может сейвить контекст, только ентити. Может есть вариант делать коммит после “получения” id процесса, что бы было что грузить из БД.

package com.company.bpmdemo.core;

import com.haulmont.bpm.entity.ProcActor;
import com.haulmont.bpm.entity.ProcInstance;
import com.haulmont.bpm.entity.ProcRole;
import com.haulmont.cuba.core.TransactionalDataManager;
import com.haulmont.cuba.core.global.DataManager;
import com.haulmont.cuba.core.global.Metadata;
import com.haulmont.cuba.security.entity.User;
import org.slf4j.Logger;
import org.springframework.stereotype.Component;

import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

@Component(ProcRolesHelper.NAME)
public class ProcRolesHelper {
    public static final String NAME = "bpmdemo_ProcRolesHelper";


    @Inject
    private Logger log;

    @Inject
    private DataManager dataManager;

    @Inject
    private Metadata metadata;

    @Inject
    private TransactionalDataManager transactionalDataManager;

    public void fillProcRoles(String procRoleCode, String secRoleName, UUID bpmProcInstanceId) {


        ProcInstance procInstance = transactionalDataManager.load(ProcInstance.class)
                .id(bpmProcInstanceId)
                .view("procInstance-listener-view")
                .one();
        Optional<ProcRole> procRoleOpt = procInstance.getProcDefinition().getProcRoles().stream()
                .filter(procRole -> procRole.getCode().equals(procRoleCode))
                .findAny();
        if (!procRoleOpt.isPresent()) {
            log.error("ProcRole {} not found", procRoleCode);
            return;
        }
        List<User> usersWithSecRole = findUsersBySecRole(secRoleName);
        List<ProcActor> procActorsToCreate = new ArrayList<>();
        for (User user : usersWithSecRole) {
            ProcActor procActor = metadata.create(ProcActor.class);
            procActor.setUser(user);
            procActor.setProcInstance(procInstance);
            procActor.setProcRole(procRoleOpt.get());
            procActorsToCreate.add(procActor);
            transactionalDataManager.save(procActor);
        }
    }

    private List<User> findUsersBySecRole(String secRoleName) {
        return dataManager.load(User.class)
                .query("select u from sec$User u join u.userRoles ur join ur.role r where r.name = :secRoleName")
                .parameter("secRoleName", secRoleName)
                .list();
    }
}

Максим, если заменяем DataManager на TransactionalDataManager, то в итоге не можем закоммитить вот эту штуку : procActorsToCreate.add(procActor); , раньше это было реализовано через dataManager.commit(new CommitContext(procActorsToCreate)); , как можно выкрутиться из этой ситуации ? Без нее роли конечно назначаются, но только роли и сохраняются, комменты, статус заявки, ничего не меняется, можно бесконечно создавать заявки, фрагмент инициализируется постоянно.