Parallel tasks for one procActor/Role

Hello,

I have a requirement to assign more than one UserTask for roles at one time. But standart implementation of procActionsFrame and even procInstance-edit frame doesn’t seems to support this functionality.

+

2018-05-07_11-45-252018-05-07_11-43-32

Any chance that someone already tried to implement it in their projects? What would be a better way to implement it?

I cant find a better solution than write my own “MultiTaskActionsFrame” and ProcActionsFrame extention.
If someone would be interested : I zipped test project below.
Code is far from ideal but it is test solution that i can easily share. Further development will be in my main proj so it is hard to track changes here.
Warning: Creating and cancelling process not allowed. If you yousing AfterCompliteTaskListener don’t forget to call method MultiTaskFrame.reload()

ExtProcActionsFrame
package com.company.bpmmultitask.web.screens;

import com.haulmont.bpm.entity.ProcTask;
import com.haulmont.bpm.gui.procactions.ProcActionsFrame;
import com.haulmont.cuba.core.global.LoadContext;

import java.util.List;

public class ExtProcActionsFrame extends ProcActionsFrame {
protected int taskNum = 0; // task-count starts from zero

public void setTaskNum(int num){
    this.taskNum = num;
}

public int getTaskNum() {
    return taskNum;
}

@Override
protected ProcTask findCurrentUserProcTask() {
    LoadContext<ProcTask> ctx = new LoadContext<>(ProcTask.class);
    ctx.setQueryString("select pt from bpm$ProcTask pt left join pt.procActor pa left join pa.user pau " +
            "where pt.procInstance.id = :procInstance and (pau.id = :userId or " +
            "(pa is null and exists(select pt2 from bpm$ProcTask pt2 join pt2.candidateUsers cu where pt2.id = pt.id and cu.id = :userId))) " +
            "and pt.endDate is null")
            .setParameter("procInstance", procInstance)
            .setParameter("userId", userSession.getCurrentOrSubstitutedUser());
    ctx.setView("procTask-complete");
    List<ProcTask> result = dataManager.loadList(ctx);
    return result.isEmpty() || result.size()<taskNum ? null : result.get(taskNum); 
}
}

MultiTaskFrame
package com.company.bpmmultitask.web.screens;

import com.haulmont.bpm.entity.ProcDefinition;
import com.haulmont.bpm.entity.ProcInstance;
import com.haulmont.bpm.entity.ProcTask;
import com.haulmont.bpm.gui.action.ProcAction;
import com.haulmont.bpm.gui.procactions.ProcActionsFrame;
import com.haulmont.cuba.core.entity.Entity;
import com.haulmont.cuba.core.global.DataManager;
import com.haulmont.cuba.core.global.LoadContext;
import com.haulmont.cuba.core.global.ReferenceToEntitySupport;
import com.haulmont.cuba.gui.components.AbstractFrame;
import com.haulmont.cuba.gui.components.Frame;
import com.haulmont.cuba.gui.components.VBoxLayout;
import com.haulmont.cuba.gui.xml.layout.ComponentsFactory;
import com.haulmont.cuba.security.global.UserSession;

import javax.annotation.Nullable;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;

public class MultiTaskFrame extends AbstractFrame {
@Inject
protected ComponentsFactory componentsFactory;
@Inject
protected ReferenceToEntitySupport referenceToEntitySupport;
@Inject
protected UserSession userSession;
@Inject
protected DataManager dataManager;

@Inject
protected VBoxLayout frameBox;

protected List<ExtProcActionsFrame> openedFrames = new ArrayList<>();
protected ProcInstance procInstance = null;

protected ProcAction.BeforeActionPredicate beforeStartProcessPredicate;
protected ProcAction.BeforeActionPredicate beforeCompleteTaskPredicate;
protected ProcAction.BeforeActionPredicate beforeClaimTaskPredicate;
protected ProcAction.BeforeActionPredicate beforeCancelProcessPredicate;

protected ProcAction.AfterActionListener afterStartProcessListener;
protected ProcAction.AfterActionListener afterCompleteTaskListener;
protected ProcAction.AfterActionListener afterClaimTaskListener;
protected ProcAction.AfterActionListener afterCancelProcessListener;

protected boolean startProcessEnabled = true;
protected boolean cancelProcessEnabled = false;
protected boolean completeTaskEnabled = true;
protected boolean claimTaskEnabled = true;
protected boolean taskInfoEnabled = true;

protected String buttonWidth = "150px";

protected static String extFrameAllias = "procActionsFrame.ext";

public void init(ProcInstance procInstance){
    if (procInstance == null){ //TODO starting new process NOT supported
        return;
    }else{
        this.procInstance = procInstance;
        for(Long i = 0l; i<getTaskCount();i++){
            Frame currFrameComponent = componentsFactory.createComponent(Frame.class);
            ExtProcActionsFrame currFrame = (ExtProcActionsFrame)openFrame(currFrameComponent,extFrameAllias);
            currFrame.setTaskNum(i.intValue());
            ProcActionsFrame.Initializer currInitializer = currFrame.initializer();
            copyFields(currInitializer);
            currInitializer.init(procInstance);

            openedFrames.add(currFrame);
            frameBox.add(currFrameComponent);
        }
    }
}

public void init(String procCode, Entity entity){
    ProcDefinition procDefinition = findProcDefinition(procCode);
    if (procDefinition == null){
        return;
    }else{
        init(findProcInstance(procDefinition, entity));
    }
}

/**
 * Clears (//hides in fact) opened task-frames and reloads new ones <br>
 * Called by default (if null) in AfterCompleteTaskListener <br>
 */
public void reload(){
    for(ExtProcActionsFrame frame : openedFrames){
        frame.getParent().setVisible(false);
    }
    openedFrames.clear();
    init(this.procInstance);
}

/**
 * @return number of tasks for that (or substituted) user for input procInstance
 */
private Long getTaskCount() {
    if (this.procInstance == null){
        return null;
    }else {
        LoadContext<ProcTask> lc = new LoadContext<>(ProcTask.class);
        lc.setQueryString("select pt from bpm$ProcTask pt left join pt.procActor pa left join pa.user pau " +
                "where pt.procInstance.id = :procInstance and (pau.id = :userId or " +
                "(pa is null and exists(select pt2 from bpm$ProcTask pt2 join pt2.candidateUsers cu where pt2.id = pt.id and cu.id = :userId))) " +
                "and pt.endDate is null")
                .setParameter("procInstance", procInstance)
                .setParameter("userId", userSession.getCurrentOrSubstitutedUser());
        return dataManager.getCount(lc);
    }
}

/**
 * Copying setted parameters to opened task-frame
 * @param initializer Initializer of ExtProcActionsFrame
 */
protected void copyFields(ProcActionsFrame.Initializer initializer){
    initializer.setAfterCancelProcessListener(afterCancelProcessListener);
    initializer.setAfterClaimTaskListener(afterClaimTaskListener);
    initializer.setAfterCompleteTaskListener(afterCompleteTaskListener == null ? this::reload : afterCompleteTaskListener);
    initializer.setAfterStartProcessListener(afterStartProcessListener);

    initializer.setBeforeCancelProcessPredicate(beforeCancelProcessPredicate);
    initializer.setBeforeClaimTaskPredicate(beforeClaimTaskPredicate);
    initializer.setBeforeCompleteTaskPredicate(beforeCompleteTaskPredicate);
    initializer.setBeforeStartProcessPredicate(beforeStartProcessPredicate);

    initializer.setStartProcessEnabled(startProcessEnabled);
    initializer.setCancelProcessEnabled(cancelProcessEnabled);
    initializer.setCompleteTaskEnabled(completeTaskEnabled);
    initializer.setClaimTaskEnabled(claimTaskEnabled);
    initializer.setTaskInfoEnabled(taskInfoEnabled);

    initializer.setButtonWidth(buttonWidth);
}

@Nullable //ctrl+c ctfl+v from ProcActionsFrame
protected ProcDefinition findProcDefinition(String processCode) {
    LoadContext ctx = LoadContext.create(ProcDefinition.class);
    ctx.setQueryString("select pd from bpm$ProcDefinition pd where pd.code = :code")
            .setParameter("code", processCode);
    return (ProcDefinition) dataManager.load(ctx);
}

@Nullable //ctrl+c ctfl+v from ProcActionsFrame
protected ProcInstance findProcInstance(ProcDefinition procDefinition, Entity entity) {
    String referenceIdPropertyName = referenceToEntitySupport.getReferenceIdPropertyName(entity.getMetaClass());
    LoadContext<ProcInstance> ctx = LoadContext.create(ProcInstance.class).setView("procInstance-start");
    ctx.setQueryString("select pi from bpm$ProcInstance pi where pi.procDefinition.id = :procDefinition and " +
            "pi.entity." + referenceIdPropertyName + " = :entityId order by pi.createTs desc")
            .setParameter("procDefinition", procDefinition)
            .setParameter("entityId", referenceToEntitySupport.getReferenceId(entity));
    List<ProcInstance> list = dataManager.loadList(ctx);
    return list.isEmpty() ? null : list.get(0); //TODO MultiProcSupport
}


/*
 * Options and hooks methods
 */

public MultiTaskFrame setBeforeStartProcessPredicate(ProcAction.BeforeActionPredicate predicate) {
    this.beforeStartProcessPredicate = predicate;
    return this;
}

public MultiTaskFrame setBeforeCompleteTaskPredicate(ProcAction.BeforeActionPredicate beforeCompleteTaskPredicate) {
    this.beforeCompleteTaskPredicate = beforeCompleteTaskPredicate;
    return this;
}

public MultiTaskFrame setBeforeClaimTaskPredicate(ProcAction.BeforeActionPredicate beforeClaimTaskPredicate) {
    this.beforeClaimTaskPredicate = beforeClaimTaskPredicate;
    return this;
}

public MultiTaskFrame setBeforeCancelProcessPredicate(ProcAction.BeforeActionPredicate beforeCancelProcessPredicate) {
    this.beforeCancelProcessPredicate = beforeCancelProcessPredicate;
    return this;
}

public MultiTaskFrame setAfterStartProcessListener(ProcAction.AfterActionListener afterStartProcessListener) {
    this.afterStartProcessListener = afterStartProcessListener;
    return this;
}

public MultiTaskFrame setAfterCompleteTaskListener(ProcAction.AfterActionListener afterCompleteTaskListener) {
    this.afterCompleteTaskListener = afterCompleteTaskListener;
    return this;
}

public MultiTaskFrame setAfterClaimTaskListener(ProcAction.AfterActionListener afterClaimTaskListener) {
    this.afterClaimTaskListener = afterClaimTaskListener;
    return this;
}

public MultiTaskFrame setAfterCancelProcessListener(ProcAction.AfterActionListener afterCancelProcessListener) {
    this.afterCancelProcessListener = afterCancelProcessListener;
    return this;
}

public MultiTaskFrame setStartProcessEnabled(boolean startProcessEnabled) {
    this.startProcessEnabled = startProcessEnabled;
    return this;
}

public MultiTaskFrame setCancelProcessEnabled(boolean cancelProcessEnabled) {
    this.cancelProcessEnabled = cancelProcessEnabled;
    return this;
}

public MultiTaskFrame setCompleteTaskEnabled(boolean completeTaskEnabled) {
    this.completeTaskEnabled = completeTaskEnabled;
    return this;
}

public MultiTaskFrame setClaimTaskEnabled(boolean claimTaskEnabled) {
    this.claimTaskEnabled = claimTaskEnabled;
    return this;
}

public MultiTaskFrame setTaskInfoEnabled(boolean taskInfoEnabled) {
    this.taskInfoEnabled = taskInfoEnabled;
    return this;
}

public MultiTaskFrame setButtonWidth(String buttonWidth) {
    this.buttonWidth = buttonWidth;
    return this;
}
}

Screen

2018-05-07_14-19-55
2018-05-07_14-21-362018-05-07_14-21-512018-05-07_14-22-022018-05-07_14-22-252018-05-07_14-22-55

test project (158.6 KB)

1 Like

Since from 6.9.* there is no findCurrentUserProcTask method in ProcActionsFrame so i made some workaround if someone would be interested:

ExtProcActionsFrame
public class CustomTaskProcActionsFrame extends ProcActionsFrame {
    @Inject
    protected BpmEntitiesService baseBpmEntitiesService;

    @Inject
    protected UserSession userSession;

    protected int taskNum = 0;

    public void setTaskNum(int num){
        this.taskNum = num;
    }

    public int getTaskNum() {
        return taskNum;
    }

    @Override
    public void init(Map<String, Object> params){
        bpmEntitiesService = new BpmEntitiesService() {
            @Nullable
            @Override
            public ProcDefinition findProcDefinitionByCode(String procDefinitionCode, String viewName) {
                return baseBpmEntitiesService.findProcDefinitionByCode(procDefinitionCode, viewName);
            }

            @Override
            public List<ProcInstance> findActiveProcInstancesForEntity(String procDefinitionCode, Entity entity, String viewName) {
                return baseBpmEntitiesService.findActiveProcInstancesForEntity(procDefinitionCode, entity, viewName);
            }

            @Override
            public List<ProcTask> findActiveProcTasks(ProcInstance procInstance, User user, String viewName) {
                return baseBpmEntitiesService.findActiveProcTasks(procInstance, user, viewName);
            }

            @Nullable
            @Override
            public ProcRole findProcRole(String procDefinitionCode, String procRoleCode, String viewName) {
                return baseBpmEntitiesService.findProcRole(procDefinitionCode, procRoleCode, viewName);
            }

            @Override
            public List<ProcTask> findActiveProcTasksForCurrentUser(ProcInstance procInstance, String viewName) {
                List<ProcTask> result = new ArrayList<>();
                ProcTask procTask = findCurrentUserProcTask();
                if(procTask != null) result.add(procTask);
                return result;
            }

            /**
             * Creates a new not-persisted ProcInstance according to the information from the {@code procInstanceDetails}
             *
             * @param procInstanceDetails
             */
            @Override
            public ProcInstance createProcInstance(ProcInstanceDetails procInstanceDetails) {
                return baseBpmEntitiesService.createProcInstance(procInstanceDetails);
            }
        };
        super.init(params);
    }

    /**
     * Finds a task with a proper index number
     * @return indexed procTask or null if there is no such index
     */
    protected ProcTask findCurrentUserProcTask() {
        List<ProcTask> result = baseBpmEntitiesService.findActiveProcTasksForCurrentUser(procInstance, BpmConstants.Views.PROC_TASK_COMPLETE);
        return result.isEmpty() || result.size() < taskNum ? null : result.get(taskNum);
    }
}