Part 2: Make the Approval Process Multi-instance
Chapter 8: Create DMN Based Multi-instance Approval Process
In this second part, we are going to extend the approval process with a multi-instance activity based on the number of approvers we need. That number is calculated using a DMN decision table according to the specified travel data.
Add a New Main Approval Process
We currently have a straightforward, fixed approval process, and we are going to
make it a bit more sophisticated. To get started, go back to the case model
and select the Travel request approval process task in the Approval travel stage.
Select the Process Task - General>Process reference attribute (but not the referenced process link).
Create a new sub-process where we add the DMN based decision and multi-instance path.
Set the new name to Travel Request Main Approval Process, hit Create and then Finish to create the new main approval process.
As before, delete the predefined Start event and drag a new
Swimlanes>Pool onto the process diagram and name it Main Approval Process. Then name the
existing lane to Travel Requestor and add a new lane below (Swimlanes>Lane)
named System.
Add a Start events>Start event within the System lane and a new
Activities>Decision task,
name it Evaluate number of approvals needed. Next, connect Start event
with the Decision task by dragging the small sequence flow icon (arrows) from
the Start event onto the decision task.
Use the exclusive gateway icon after selecting the Decision task to
add a gateway and then the User task icon to add a new User
task after the gateway and name it Select approvers.
After the User task, add a gateway again by using that small icon,
then in the same fashion an end event.
Go back to the first gateway, use the sequence flow icon again and
drag it to the second gateway to add an alternative path, then use the
centric handle of that sequence flow to drag it below the user task.
The process model now looks like this:

Define a DMN Decision Table for the Number of Approval Evaluation
In the next step, we want to define a DMN based decision table to evaluate if and how many approvals we need for the travel request.
Select the Decision task and click the Decision table reference property,
enter Travel Request Approval Decision Table, hit Create and
then Finish to create a new decision table.
A DMN based decision table is defined using some input values and one or even
more output values. By default, the First hit policy is selected which
means, the engine evaluates all the conditions on the input values defined
and stops at the first row where all conditions are met and returns the
output value(s) specified on that row.
Click the New Input blue header to define our first input parameter
and modify it like this:

We can map process variable values to our DMN input columns on which to base our decisions.
For the next column, click the small, blue plus icon to add another input column and define it like this:

As you can see, we can use our previously defined transportation options, so it is easier to use those values within our table later on.
Let us add a third column with the following values:

Finally, we want to define our outcome of the decision table, which is
the number of approvals needed for the travel request according to the
input values we have. For this click on the grey New Output header to
define our output value:

Now we have defined our input and output values. The next step is to add some rules for the decision table, playing with the input values:

It is a good practice for a first-hit policy table to add the last row without any conditions on the input values to ensure the table always produces an outcome.
In our example, we defined some rules (rows) according to the estimated costs and transportation; we did not take the accommodation into account. Of course, you can adapt the rules to your needs.
Make sure you save the decision table and then return to our process.
Now we want to make use of our outcome, so select the first gateway and name it Approvals needed? and place the label on top of the gateway.
Making more space
If you need more space between existing elements, select the Make space
tool in the toolbar and drag the elements away from each other to make more
space or towards each other to reduce the space between elements. Manipulating
the amount of space works
both horizontally and vertically (see the selected icon in the image below,
it must be activated to make use of the spacing tool):

Now select the first outgoing flow leading to the User task, name it yes,
set the Details>Condition type to Conditional flow and
enter ${approvalsNeeded > 0} as the Details>Condition expression.
The engine evaluates that condition and if
true (in our case, if the decision tables outcome was more than 0), it
follows that path.
Now select the other outgoing flow, name it no and check the
Default flow checkbox. It is a good practice to always have one outgoing
flow set as the default. As an alternative, we also could make it a conditional
flow and set its condition to ${approvalsNeeded == 0}.
We also want to move the Select approvers user task to the Travel Requestor
lane. You can drag and drop it from one lane to the other. You can then
also rearrange the sequence flows and labels to get the process looking nice:

Modify the Task Form to Select Multiple Approvers
As there can be more than one approver, we need to modify the approver
selection task form. For this first select the Select approvers user task in our process,
then the Form reference property and choose "Reference", as we already
have such a task form available, we need to modify it.
Select the Select Approver Task Form and then click on the form
link to open it.
Double click the first text element and set its content to something similar to this:

Please select {{approvalsNeeded}} person(s) to review and approve or decline your travel request.
This might be your superior or propject manager, depending on the reason for the travel.
We use the number of approvals needed from the output of our decision table as an expression.
In our old selection task form, we just had one selection element where we selected one person as the approver. Now we might need more than one. The multi-entry subform now comes in handy for this.
So let us add a Container>Subform at the bottom of the form and
name it Approvers.
Go to the right hand side panel and click on Subform - General>Store subform data in a single variable and set it to {{root.approval}}.Then you will be able to see Minimum and Maximum elements attribute under the Subform - Validation>Validation. Click on the pencil icon to edit the Minimum and Maximum elements with information as an output of our decision table, so use that information by clicking the small "lightning" icon on the right and then enter {{approvalsNeeded}} for both the elements.
As we want the user to enter exactly that amount of elements in the subform.
We do not need to add or remove elements, so uncheck the
Subform - Form>Show remove button and Subform - Form>Show add button.
The properties of the subform should look now like this:

Next, we need an actual subform, so select the Subform - General>Form reference property,
enter Select Travel Request Approver Subform as its name, hit Create and
then Finish to create a new subform. This new subform is used within that
list of elements of our root subform component. Each element, depending
on the number of approvers we need, renders that exact form.
Go back to the Select Approver Task Form where we added the subform widget and select
the Select approver component, then either hit command-X to delete it
(or use the scissor icon in the toolbar) as we want it moved to the subform.
Go to the subform by clicking the Details>Form reference property link
and paste the element from the clipboard onto the empty form.
The Select Approver Task Form now looks something like this:

and the Select Travel Request Approver Subform like this:

The final step is to define the input variables needed for our process.
Go back to the case model, select the Travel request approval process, and
click the Process Task - Variable Mapping>In property to open the in variable mapping for the process,
and enter this data there:

Now save everything (the best way is with the save-all toolbar icon), and we are ready for another test spin, at least to test the decision table output and the approver selection. Of course, we need to adapt the case again later, however, let us deploy and test in between nonetheless.
Deploy and Run the Travel Request Again
Publish the app again, switch to the runtime, and start a new travel request case.
Fill out the details and choose something larger than 1000 to make sure we
need two approvers to be selected. Then file the request, and you see a task
named Select approvers. Select it, and there should be two selections required
for approvers:

We want to see what the decision table outcome was as there is a
debug-view available at runtime. Select the Travel Request Main Approval Process in the header of the task to navigate to the process.
Next, select the "History" tab, and you can see the process diagram
with the Select approvers as the current state. Now navigate to
the sub-tab "Decisions" and the evaluated decision table is visible
with the data we entered in the travel request:

During modeling, this is very helpful, especially if the table does not produce the expected output. In this view, you can see how the rules are evaluated and which one was a hit or matched.
Chapter 9: Add a Multi-instance Call Activity for Approvals
In this chapter, we want to use the collection of selected approvers to repeat the approval sub-process for each of the selected approvers.
Add a Multi-instance Call Activity
Go back to our Travel Request Main Approval Process and make sure there is
some space between the User task and the gateway. See the discussion
in the last chapter for a review of the process of adding space in the diagram.
Add a Structural>Call activity by dragging and dropping it directly
onto the sequence flow between the User task and the second (joining) gateway and name it
Approval subprocesses. Select the Details>Process reference property and
use the tab "Reference" and select the Travel Request Approval Process
(the one from the first part we previously created).
Of course, we need to adapt that sub-process, but let us first set up the call activity to be multi-instance.
For this, go to the property Multi instance>Multi instance type
and set it to Parallel, which means, we start a new
sub-process instance in parallel for each of the selected approvers. We
could also choose Sequential, which would start them one after the other.
The Multi instance>Element index variable is predefined as loopCounter,
which is fine and allows us to use it as the index counting for each of the sub-process
instances (starting with 0). We need to define the collection to loop for,
so we set the property Multi instance>Collection to
${root.approvers}.
That variable was previously created through our multi-entry subform and
contains a list (collection) of the selected approver user ids.
More data is added for each element through that sub-process later on.
Let us also specify an element variable which we can then use within the
sub-process to access our specific element. So set Multi instance>Element variable
to approver for that. Next, add some values we need within the
sub-process as in-variables. Select the Process Task - Variable Mapping>In property on the right hand side panel and set
the following values:

Now let us adapt the approval sub-process to be ready for multi-instance approvals.
Select that process by clicking on the Travel Request Approval Process
link on the Details>Process reference property of the call activity.
First, we remove (delete) the first user task (Select approver for travel request) where we previously
selected the approver as this is now done in the main process.
We also move the Start event into the second lane of the Travel approver and
later remove the travel requestor lane entirely, as we do not need it anymore.
If we add a feedback or adjusting loop between the approver and requestor
at some point, then it would make sense again to have that lane.
We also change the expression used for the assignee of the approval
user task which is just ${approverId} in our previous version,
but now needs the value of our local loop element variable.
Select the Approve travel request user task, go to the
Assignment>Assignee property and set
it to ${approver.approverId}. This uses the approver id variable
of the local element data we have on the sub-process scope.
Adapt Approver Task Form
Once done, let us adapt the approver task form, so select the user task,
then click on the Approve Travel Request Task Form link to open that task form.
We need to change the binding of the review comments as it is added to each approver within our collection.
So select the Review comments input field and set its General>Value to
{{root.approvers[loopCounter].reviewComments}}. Remember: we have our
approver collection on the case level; hence, we need root to resolve it.
Then we set that review comment at the index of our multi-instance loop counter.
That is why we use the loopCounter as the index within the collection.
Lastly, we use the dot-notation and add a new field named reviewComments
to that collection. Now each comment is found at the same index as the
approver user id.
In the first version, we used the outcomes to approve or decline. Let us change
this also to see alternative outcomes. First, remove the outcomes by
clicking the Details>Outcomes and
then remove the two outcomes that are currently there.
Now drag and drop a Selection>Radio button component between the text display and the
comment field and name it Review decision. Of course, we want that review
decision stored in our approvers' collection as well, so set the General>Value of
the radio buttons to {{root.approvers[loopCounter].reviewDecision}}.
Now make the component required by selecting Validation>Required and
set its Details>Orientation property to Horizontal.
Click the Data source>Items property and add the following values to it:

We want the required option of the review comment to depend on the decision,
so click the lightning icon of the Validation>Required property and set its expression
to {{root.approvers[loopCounter].reviewDecision == 'declined'}}.
This expression means that whenever we select the declined option, the comment
field is mandatory; otherwise, it is optional.
Save the form and go back to the sub-process as we need to deal with the decision of the task (approve or decline).
Collect the Approval Result from Each Approver
Each approval user task produces a decision according to the approver’s selection
whether to approve or decline the travel request, which is stored back at
the same index within the approvers' collection. We want to add it to
the parent process level and combining it with all the other approval results.
To do that, add an Flowable Work Activities>Initialize variables
service task after the Approve travel request user task. Label the new task,
Set approval result and make sure Details>Overwrite if existing is checked
and add one variable initialization to the Details>Init variables property:
${parent.approval && flwJsonUtils.getAtIndex(root.approvers, loopCounter).reviewDecision == 'approved'}

We use parent as the target because we want to set that approval
variable on our parent main process instance. As the variable value,
we use an expression, where we use the currently set decision value
and combine it with the outcome of the local selection.
The Travel Request Approval Process now looks something like this:

Now the first part of the expression, parent.approval is a variable that
is not initialized the first time, so we need to make sure it exists and
is set to true, otherwise, we would never be able to approve the case.
So go back to the main travel request case model and select the
Travel request approval process task and open the Process Task - Variable Mapping>In variable mapping
and add initialization of our approval variable on the main process
instance level:

With the expression ${true}, we create a Boolean true value to
initialize the approval variable.
Now for the Details>Out mappings (what comes back from the process and is
saved on case level), we remove all the previous mappings and
only add approval to the list:
This means we store the final approval variable value from our main process
back to the case, once all approval sub-processes are finished. We
then have the final approval flag (true or false) back on the case and
all the detailed results in our approvers collection.
As our result now is a Boolean value, we need to adapt the entry sentry
expression (condition) on the entry criterion of our last stage. So select
the entry criterion and set the Condition to
${vars:getOrDefault('approval',false)} to create a Boolean variable value
(from a string based in our first version of the approval process).
Improve the Case Overview Form
As we no longer have a single approver, let us modify the case overview (work) form by adding a multi-entry subform with an overview of the approvals.
Go back to the case model and open the work form (Travel Request Work Form).
Delete the two existing
text display components at the top of the form representing the approval review.
Add a Container>Subform component at the top of the form and name it
Approval overview.
Then set the value binding of General>Store subform data in single variable
to {{approvers}}. Also check the property
Details:>Multiple elements as we want to have more than one subform rendered,
as the variable approvers is representing a collection, not a single value.
Turn off the Details>Show add button and the Details>Show remove button
by unchecking each attribute.
We also need the form to be read-only, so uncheck the
General>Enabled property as well. Furthermore, we only want to show the form,
if the approvers are initialized, so use the lightning icon on the
General>Ignored property and set the expression to
{{!(approvers[0].approverId)}}.
The approvers[0].approverId part is only true, once we have at least one
approver set in the collection of approvers.
Now select the Details>Form reference property, set the name for the new
subform to Travel Request Approval Overview Subform, hit Tab and then
Enter to create a new subform for our overview list.
We want to put the selected approver, the decision, and comments in the form.
The easiest way is to go back to the approver selection
form we did for the Select approvers task or just by opening the
Select Travel Request Approver Subform. Select and copy that
Select approver widget and go back to our newly created subform
and paste it to the form. Remove its label and make it not required as we do not
need it here.
Add a new Data entry>Multiline text component, and do not set a label as it
is not needed, but set its General>Value property to {{reviewComments}}.
As the goal is to either display a text saying the review is not yet done
or as an alternative, show the review result, add a Container>Panel component on
the same row, in between the approver selection widget and the comment widget.
Make the selection widget use 3 columns in width, the panel use 4
columns, and the comment use 5 columns.
Now drop a Display>Text display widget onto the panel and set the content to
Request was {{reviewDecision}}.. You can even mark the expression and make
it bold for highlighting the decision result. The widget is shown once the
decision is made, so set the General>Ignored property to {{!reviewDecision}}.
Now drop another Display>Text display in the panel below the existing one and
set its content to The request was not yet reviewed and decided..
This widget is visible if the review is not yet completed, so set the
General>Ignored property to {{reviewDecision}}.
That subform now looks similar to this:

Save the subform and go back to its parent form. Drag and drop a
Display>Text display below the subform and set its content to something like:
The travel request review and approval was not yet started. So there is no approval result available yet.
Set the General>Ignored property to {{approvers[0].approverId}} so it is only
shown if there is no approver set.
Now save everything, and we should be ready for another test.
Deploy and Run the Travel Request Again
Publish the app again, switch to the runtime view, and start a new travel request case as before. Make sure the estimated costs are greater than 1000, so the outcome requires two approvers and then file the case for approval.
After selecting the two approvers and completing that task, you should now see two approval tasks in the list of open tasks of our case:

They are assigned to the approvers you selected in the previous task and are created in parallel.
Of course, you cannot complete those tasks as the travel requestor, so log in with each of the selected approvers, go to their task inbox, select the approval task and make a decision (works like before). When both users approve the task, then the travel request is approved. If either one declines the request, then the case is declined.
If you approved with both approvers, you can go back to the case and
see the Organize travel task like before.