/ Development  

Ultimate productivity with this jaw-dropping follow-up to auto-assign Cases and Tasks

Hi there AppWorks fans,

Welcome to a new installment of AppWorks tips.

This is an addition (or part 2) on this previous post. We’ll have a look (based on what we have so far) if it would be possible to claim a new case/task once the current one is “DONE”. So, we’re looking for some trigger moments:

  1. For a ‘Case’, the trigger moment (in our example) is the completion of the lifecycle. After this you get assigned to the next case in the lst_all_cases; We keep it simple, but a next step would be to only pick the next one corresponding to your skill level!
  2. For a ‘Task’, the trigger moment (in our example) is the completion of the task itself! I assume our tasks land in different queues New cases, Review cases, and Final cases; We keep it simple with only the New cases queue and grab a new task when “DONE” with the current task.

Time for some advanced business logic…


Let get right into it…

Again, two sections in this post…Let’s mumble-jumble a bit on the implementation strategy; Always a good thing to do with your colleagues!

The ‘Case’; Well, picking up a new case is not a feature out of the box from the platform. The trigger however is! This will just be an ordinary ‘Rule’ BB checking the ‘Completion’ state of the lifecycle. The action falls under the ‘Advanced action’ feature for a rule where we call the appropriate actions via a BPM. So, my next step would be the implementation of such a BPM with specific logic to manage the next assignee. This means webservices to “find” all cases ordered by creation date (I guess), get the first one and make use of a secret assignee webservice to make it happen…AND don’t forget to read the flag if auto-assignment is enabled! Interesting enough? I just pop it out of my sleeve as I’m writing! 🤣

The ‘Task’; Also, picking up a new task isn’t a feature OOTB from the platform. So, the same principle here. When the task is completed, we trigger a BPM managing the rest of the business logic…


Case implementation

Let’s first plot our action plan into something touchable. So, our case will get a ‘Rule’ BB with name e_lc_complete_start_bpm_next_case. The condition will be Lifecycle.InstanceStatus == 'Complete', and the action will trigger BPM bpm_next_case with this pseudocode:

1
2
3
4
5
6
7
8
9
var config = readConfig();
if(config.autoAssign) {
var itemIdCase = rootEntityInstanceId;
var caseCurrent = readCase(itemIdCase);
caseCurrent.updateCase(null); //Revoke assignee! Because we can.
var assignee = caseCurrent.getAssignee();
var caseNew = findUnassignedCasesOrderedByCreationDateDesc().get(0);
caseNew.updateCase(assignee.ItemId); //We could also get the current user ItemId...A challenging task for you?
}

Time for BPM craftsmanship…I first plot out the flow with empty activities:

autoqueue_001

Next, we apply all the webservices (from the right-click menu of an activity):

autoqueue_002

The findUnassignedCasesOrderedByCreationDateDesc operation is enabled on the case ‘Web Service’ BB. Just like this:

autoqueue_003

The second filter (in the screenshot above) is for our implementation choice; The descending ordering on ‘Tracking.CreateDate’ is done in the last tab…An extra task for you to explore!

Before you can publish, make sure the decision is configured properly; Make it an exclusive check (only one output and not parallel) and read the config setting:

autoqueue_004

Publication should be possible, but you can’t run it properly yet! Why? Because we miss the message mapping parts! This is the consolidated view of my mappings on the activities. Comment me if you don’t have a clue, but I guess by now you’ll understand the principles of BPM message mapping.

autoqueue_005

Extra notes on those mappings:

  1. To empty a field with a service call, you can make use of the “nil” attribute: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true". In the messagemap assignments you have an extra option to set it (see screenshot).
  2. You don’t need to map the Id’s itself; The ItemId’s are enough…At least, that’s my experience!
  3. Always change “Replace with select” to “Replace with expression” if the default mapping is changed (after a <Ctrl> click from source to target!); So, when you use a real expression…This is not always needed, but just a best practice! I updated my expression (see the [1]) to get the first element from the “Find” result.
  4. Be cautious when adding 2 of the same webservice calls (‘updateCase’) in one BPM! For some reason (?) the activities pick up mappings from each other! A common BPM feature/bug(?) you should be familiar with. Just swallow it and go with the flow! 😎

When ready, publish it, but don’t do anything yet as we first need that event type of ‘Rule’ BB to trigger the BPM. This is a real simple implementation like this:

autoqueue_006

Well, where are we waiting for…Publish again, move to runtime. Create a new case-related case instance, move it through the lifecycle and see if the next case is assigned to you (and the old case gets unassigned)! How easy…Did it work the first time? Hell no!…What is the PIM artifact telling me?

autoqueue_007

As expected…First test is OK! 😂 Let’s enable our feature flag in ‘app/admin’ and give it a second try…This time it’s working fine!

The final version checks also if the queue is empty; Otherwise, the update (to assign) on the new case fails! You see that pseudocode is one thing, but in realtime you’ll always face those bumps in the road!

This is my final BPM with proper naming:

autoqueue_008

Let’s jump to the next one…

Ohw…before I forget; Make the BPM short-lived to get direct feedback/updates in the UI! #KeepYourEndUsersHappy! 😜


Task implementation

Here also, first something touchable…So, our LifecycleTask entity will get a ‘Rule’ BB with name e_tsk_complete_start_bpm_next_task. The condition will be Task.State == 'Completed'; The action will trigger BPM bpm_next_task with this pseudocode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var config = readConfig();
if(config.autoAssign) {
//This static block is not required, but just interesting to see the difference in ID's (see comment below)!
static {
var itemIdTask = rootEntityInstanceId;
var taskCurrent = readLifecycleTask(itemIdTask); //For this one, I add the 'Webservice' BB with the default READ operation enabled
var taskId = taskCurrent.getParentTaskId();
var task = getTask(taskId);
}

var taskNew = getHumanTasksOrderedByDateDesc('CREATED').get(0);
claimTask(taskNew.ItemId);
//We leave the old task as is with state 'Completed'...Feel free to do something fancy with it! :)
}

Did you find the difference in the ‘ItemId’ of a lifecycleTask entity instance and the ‘Id’ of a real inbox task instance? If so, you see a direct difference between the “old” part of the platform Id’s compared with the “new” entity modelling ItemId’s! Where did we see this before? Well, at user level! Do a SOAP request on operation ‘FindUserByName’ and after that a ‘ReadUser’ with the ‘ItemId’. Compare that with the ‘GetUserDetails’ operation where you receive a ‘UserId’ (not an ‘ItemId’). Now you see the platform has a long history…Interesting stuff!

Time for BPM craftsmanship…This time I do a simple ‘Save as’ action on the previous BPM as “The Skeleton” is the same. This is the screenshot after cleaning and updating it to continue our journey:

autoqueue_009

You see I do a sub-process grouping trick to have the option available to add a condition true() on multiple activities; See it as the static block in the pseudocode to play with. This true() | false() condition trick is always useful to temporary skip things in development instead of removing activities…Just a trick from the godfather of BPMs! You know who you are…😜

Now what? Well, attach services again…

autoqueue_010

Extra notes to help you (comment me if you don’t follow here):

  1. The GetTask service isn’t available directly in your BPM. For this you need to add a new runtime reference (of type ‘webservice’) into your project. See the same context menu as when you create a new entity.
    autoqueue_011
  2. This reference also contains the GetHumanTasks and ClaimTask service operations

In the image of the first note, I just discovered a typo in my BPM name! I’m human too…In the final solution it’s renamed to bpm_next_task. 😁

Next step is adding the correct message mapping for the activities where GetHumanTasks is the most interesting call to make…Have a look first in the ‘Web Service Interface Explorer’ artifact for a test request like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP:Body>
<GetHumanTasks xmlns="http://schemas.cordys.com/notification/workflow/1.0">
<Query xmlns="http://schemas.cordys.com/cql/1.0">
<Select>
<QueryableObject>TASK_INSTANCE</QueryableObject>
<Field>TaskId</Field>
<Field>ParentTaskId</Field>
<Field>SourceInstanceId</Field>
<Field>State</Field>
<Field>ProcessName</Field>
<Field>Activity</Field>
<Field>Priority</Field>
<Field>Target</Field>
<Field>Sender</Field>
<Field>Assignee</Field>
<Field>DeliveryDate</Field>
<Field>StartDate</Field>
</Select>
<Filters>
<And>
<EQ field="SHOW_BUSINESS_ATTRIBUTES">
<Value>false</Value>
</EQ>
<EQ field="RETURN_TASK_DATA">
<Value>false</Value>
</EQ>
<EQ field="State">
<Value>CREATED</Value>
</EQ>
</And>
</Filters>
<OrderBy>
<Property direction="desc">StartDate</Property>
</OrderBy>
<Cursor position="0" numRows="1" maxRows="1"/>
</Query>
</GetHumanTasks>
</SOAP:Body>
</SOAP:Envelope>

ReThink and conclude this is just an ordinary SQL query in CQL-format for XML compatibility! Specially for you, I enabled my Postgres DB with extra logging…This is the outcome behind it (for what it’s worth!):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
SELECT NOTF_TASK_INSTANCE.TASK_INSTANCE_ID AS "TaskId",
NOTF_TASK_INSTANCE.PARENT_TASK_ID AS "ParentTaskId",
NOTF_TASK_INSTANCE.SOURCE_INSTANCE_ID AS "SourceInstanceId",
NOTF_TASK_INSTANCE.STATE AS "State",
NOTF_TASK_INSTANCE.SOURCE_NAME AS "ProcessName",
NOTF_TASK_INSTANCE.SUBJECT AS "Activity",
NOTF_TASK_INSTANCE.PRIORITY AS "Priority",
NOTF_TASK_INSTANCE.TARGET,
NOTF_TASK_INSTANCE.TARGET_TYPE,
NOTF_TASK_INSTANCE.SENDER AS "Sender",
NOTF_TASK_INSTANCE.TASK_OWNER AS "Assignee",
NOTF_TASK_INSTANCE.DELIVERY_DATE AS "DeliveryDate",
NOTF_TASK_INSTANCE.START_DATE AS "StartDate",
NOTIFICATION_SEARCH_DATA.SEARCH_TYPE_ID
FROM NOTF_TASK_INSTANCE
LEFT JOIN NOTIFICATION_SEARCH_DATA ON NOTF_TASK_INSTANCE.TASK_INSTANCE_ID = NOTIFICATION_SEARCH_DATA.MESSAGE_ID
WHERE (NOTF_TASK_INSTANCE.STATE = '1')
AND ((NOTF_TASK_INSTANCE.TARGET IN('08002700-8c65-a1ee-948d-0701e8b73db9',
'00505681199811E6EE060C6E413BFF76.2',
'F8B156E1037111E6E9CB18D875425BBF.655361')
AND NOTF_TASK_INSTANCE.DEPENDENT ='1')
OR (NOTF_TASK_INSTANCE.TASK_OWNER ='08002700-8c65-a1ee-948d-0700da64bdb9'
OR NOTF_TASK_INSTANCE.DELEGATED_TO = '08002700-8c65-a1ee-948d-0700da64bdb9'))
AND (NOTF_TASK_INSTANCE.STATE ='1'
OR NOTF_TASK_INSTANCE.STATE ='2'
OR NOTF_TASK_INSTANCE.STATE ='3'
OR NOTF_TASK_INSTANCE.STATE ='4'
OR NOTF_TASK_INSTANCE.STATE ='7')
AND (NOTF_TASK_INSTANCE.ORGANIZATION ='08002700-8c65-a1ee-948d-07031284bdb9'
OR NOTF_TASK_INSTANCE.ORGANIZATION = 'C3TASK')
AND (NOTF_TASK_INSTANCE.DELETE_FLAG = '0')
ORDER BY NOTF_TASK_INSTANCE.START_DATE DESC
LIMIT 1
OFFSET 0;

Fascinating! 🤓 But we’re getting off track here…The message mapping…This in the consolidated view from my side:

autoqueue_012

Extra notes on those mappings:

  • The ‘LifecycleTask’ has ‘ItemId’ and ‘ItemId1’ as input; Use the second one (‘ItemId1’) as the other one is the ‘Id’ of the parent entity (our case!)
  • XML data is key for the ‘GetHumanTasks’ activity; Watch the use ‘Add XML structure’ in the assignments

Finally, we need a ‘Rule’ BB (e_tsk_complete_start_bpm_next_task) to trigger the BPM…

autoqueue_013

With this in place, it’s time to publish it all again and have a try in runtime…So, make a task-related case, complete the task and see if the BPM does its tricks well. The greatest question of all questions: Did it do the tricks well the first time? Well, it works; It even works so well that it directly picks the next task in the lifecycle state and isn’t picking up the old one…So, we need to order ASC to get the oldest first; You already saw this coming…right? 😅

This is my final BPM with proper naming and error-catch when the claiming fails (because it’s the last one?):

autoqueue_014

During all my testing I found another interesting thing…When you complete the ‘Finalize’ task, you’ll reach the end of the case lifecycle…Guess what triggers then! 🤐 I leave that one for you the figure out! It’s time for weekend…


Nicely “DONE”…Before starting this post I didn’t have a clue where to start, but you see I’m also figuring out features as we go. Have your learnings out of it and spread the word! I’m also open on any feedback on the things I’m doing and presenting…Just to make sure I’m improving myself as well; Which will benefit you too for a next new post on AppWorks Tips; Have a great weekend…Cheers.
Owh…And is the downloadable solution; “Monkey-proof” so far!? Probably not, but it triggers creativity and exposes ways of doing things! 😁

Don’t forget to subscribe to get updates on the activities happening on this site. Have you noticed the quiz where you find out if you are also “The AppWorks guy”?