1.7.2. Contract Editor Controller

Go to the Controller tab and replace its content with the following code:

ContractEdit.java
package com.company.demo.web.contract;

import com.company.demo.entity.Contract;
import com.haulmont.bpm.entity.ProcDefinition;
import com.haulmont.bpm.entity.ProcInstance;
import com.haulmont.bpm.gui.procactions.ProcActionsFrame;
import com.haulmont.cuba.core.global.DataManager;
import com.haulmont.cuba.core.global.LoadContext;
import com.haulmont.cuba.core.global.Metadata;
import com.haulmont.cuba.core.global.PersistenceHelper;
import com.haulmont.cuba.gui.app.core.file.FileDownloadHelper;
import com.haulmont.cuba.gui.components.AbstractEditor;
import com.haulmont.cuba.gui.components.Table;

import javax.annotation.Nullable;
import javax.inject.Inject;

public class ContractEdit extends AbstractEditor<Contract> {

    private static final String PROCESS_CODE = "contractApproval";

    @Inject
    private DataManager dataManager;

    private ProcDefinition procDefinition;

    private ProcInstance procInstance;

    @Inject
    private ProcActionsFrame procActionsFrame;

    @Inject
    private Table attachmentsTable;

    @Inject
    private Metadata metadata;

    @Override
    protected void postInit() {
        super.postInit();
        procDefinition = findProcDefinition();
        if (procDefinition != null) {
            procInstance = findProcInstance();
            if (procInstance == null) {
                procInstance = metadata.create(ProcInstance.class);
                procInstance.setProcDefinition(procDefinition);
                procInstance.setEntityName("demo$Contract");
                procInstance.setEntityId(getItem().getId());
            }
            initProcActionsFrame();
        }
        getDsContext().addBeforeCommitListener(context -> {
            if (procInstance != null && PersistenceHelper.isNew(procInstance)) {
                context.getCommitInstances().add(procInstance);
            }
        });
        FileDownloadHelper.initGeneratedColumn(attachmentsTable, "file");
    }

    private void initProcActionsFrame() {
        procActionsFrame.setBeforeStartProcessPredicate(() -> {
            if (PersistenceHelper.isNew(getItem())) {
                showNotification(getMessage("saveContract"), NotificationType.WARNING);
                return false;
            }
            return true;
        });
        procActionsFrame.setAfterStartProcessListener(() -> {
            showNotification(getMessage("processStarted"), NotificationType.HUMANIZED);
            close(COMMIT_ACTION_ID);
        });
        procActionsFrame.setBeforeCompleteTaskPredicate(this::commit);
        procActionsFrame.setAfterCompleteTaskListener(() -> {
            showNotification(getMessage("taskCompleted"), NotificationType.HUMANIZED);
            close(COMMIT_ACTION_ID);
        });
        procActionsFrame.setCancelProcessEnabled(false);
        procActionsFrame.init(procInstance);
    }


    @Nullable
    private ProcDefinition findProcDefinition() {
        LoadContext ctx = LoadContext.create(ProcDefinition.class);
        ctx.setQueryString("select pd from bpm$ProcDefinition pd where pd.code = :code")
                .setParameter("code", PROCESS_CODE);
        return (ProcDefinition) dataManager.load(ctx);
    }

    @Nullable
    private ProcInstance findProcInstance() {
        LoadContext ctx = LoadContext.create(ProcInstance.class).setView("procInstance-start");
        ctx.setQueryString("select pi from bpm$ProcInstance pi where pi.procDefinition.id = :procDefinition and pi.entityId = :entityId")
                .setParameter("procDefinition", procDefinition)
                .setParameter("entityId", getItem());
        return (ProcInstance) dataManager.load(ctx);
    }
}

Press OK to save the changes.

Let’s examine the controller code in details.

To start the process, we have to create an instance of the process (ProcInstance object), link it to a process definition (ProcDefinition object), and perform start. The process instance can be started both without a link to any project entity and with this link. In our case a link to the contract is mandatory.

In the beginning of the postInit() method an instance of contract approval process is searched by findProcDefinition() method, which searches for a process definition with the contractApproval code. Next, there is a check whether a ProcInstance object linked with the contract exists in the database (findProcInstance() method). If the process instance object doesn’t exist, then it is created, the relation to ProcDefinition is set, and a linked entity name and identifier are filled.

if (procInstance == null) {
    procInstance = metadata.create(ProcInstance.class);
    procInstance.setProcDefinition(procDefinition);
    procInstance.setEntityName("demo$Contract");
    procInstance.setEntityId(getItem().getId());
}

CommitListener adds the created ProcInstance object to the list of entities that will be sent to the middleware for be committed.

getDsContext().addBeforeCommitListener(context -> {
    if (procInstance != null && PersistenceHelper.isNew(procInstance)) {
        context.getCommitInstances().add(procInstance);
    }
});

Next, go to the initProcActionsFrame() method.

A ProcActionsFrame is a standard frame displaying the buttons with available process actions. ProcActionsFrame is linked with a ProcInstance instance. If the process is not started yet, the frame will display the start process button. If the process is started and there are active tasks for the current user, then the frame will display buttons for task completion according to the task outcomes defined in the process model. For the detailed information about ProcActionsFrame see ProcActionsFrame.

private void initProcActionsFrame() {
    procActionsFrame.setBeforeStartProcessPredicate(() -> {
        if (PersistenceHelper.isNew(getItem())) {
            showNotification(getMessage("saveContract"), NotificationType.WARNING);
            return false;
        }
        return true;
    });
    procActionsFrame.setAfterStartProcessListener(() -> {
        showNotification(getMessage("processStarted"), NotificationType.HUMANIZED);
        close(COMMIT_ACTION_ID);
    });
    procActionsFrame.setBeforeCompleteTaskPredicate(this::commit);
    procActionsFrame.setAfterCompleteTaskListener(() -> {
        showNotification(getMessage("taskCompleted"), NotificationType.HUMANIZED);
        close(COMMIT_ACTION_ID);
    });
    procActionsFrame.setCancelProcessEnabled(false);
    procActionsFrame.init(procInstance);
}

The procActionsFrame.setBeforeStartProcessPredicate() method adds the check that is performed before the process start. If the contract is not saved yet, the process will not start and the warning message will be shown.

The procActionsFrame.setBeforeCompleteTaskPredicate() method invokes an editor commit and allows to complete a process action only if the editor commit was successful.

setAfterProcessStartListener and setAfterCompleteTaskListener methods will be invoked after corresponding events. They will show the notification and close the contract editor.

After all necessary listeners and predicates are set up, the initialization frame is invoked.

procActionsFrame.init(procInstance);

UI components are created during the frame initialization.