In a rush to finish his day, Bob accidentally assigns an urgent help desk request from the Marketing Director to Bill Wilson in accounting instead of to Billy Wilson in PC Desktop Support. Before Bob makes it home, his beeper goes off, showing the number of the now frantic Marketing Director. What went wrong? Though the help desk tracking application was well designed and has drastically improved the responsiveness of Bob's department, there is no way the application can compensate for Bob incorrectly typing Bill instead of Billy.
Many Domino applications rely on the assignment of a person or group name to work on a task, for example, sales management, workflow, or project management applications. The problem with many task-oriented applications is that the assignment field doesn't restrict or validate the entry beyond the basic "must not be empty and must be a syntactically correct name." Domino Designer makes it easy to help users enter one or more names correctly into an Authors field with the "Use Address dialog for choices" option:
Figure 1. Field Properties box
But even if users enter a name correctly, it is still quite possible to choose someone who is unwilling and/or unable to take on the assignment. Fortunately, there is a better way to ensure correct and appropriate task assignments using assignment rules.
Rather than assign a person to a task, users can instead pick an easy-to-remember assignment role name, such as Computer Support, Human Resources, or Building Maintenance. Using the assignment rule, it is up to the application to convert the assignment role name into one or more person names ready and able to perform the task. After an assignment is made, there are several things you can build into an application to increase the odds that assignees know about the tasks assigned to them.
Rule-based task assignment can be considered an application strategy because it combines design elements and techniques to make up a value-add feature that you can easily add to any application that assigns people to tasks.
Note: Assignment role as used in this article has nothing to do with Roles as defined in the database Access Control List (ACL). Also, assignment rules have nothing to do with Rules that you define in your mail to filter incoming messages.
This article is the first in a series on Notes application strategies. In it, we explain how to create a rules-based task assignment application, using a sample application that you can download from the Sandbox. This article assumes that you are an experienced Notes applications developer with knowledge of LotusScript.
Assignment roles and rules in practice
Before we explain the code behind the Task Assignment application, copy the database to your Notes data directory and open it in Notes. A page with an embedded outline control is in the left frame and a page with an embedded view element is on the right:
Figure 2. Embedded outline and view
This diagram shows the sequence of how to demonstrate the application:
Figure 3. Diagram
Click Application Settings in the outline to open the Application Settings profile document. This document lets you define the assignment roles and rules. Just for testing, the fields are pre-populated with these values:
Figure 4. Applications Settings profile document
Enter the assignment role names for whichever values makes sense for your company, such as department/division, geographic location, day of the week, and so on. (It is a trivial effort to add more role field sets if you need them.)
Enter or select one or more user names for each assignment role. Use the helper button to ensure that the names and/or groups are entered correctly. Select an assignment rule for each assignment role. Here is briefly what the rules mean:
- All: Assign all of the names in the list of assignees to the task.
- First: When making the assignment, always pick the first name from the list of assignees.
- Random: Pick randomly from the list of assignees.
- Sequential: Cycle through the list of possible assignees, keeping track of the sequence in your local Notes.ini file for each assignment role.
- Workload: Look up the workload of each person and assign the person with the least amount of work.
For any rule, select the Expand Group Name(s) option to have the application resolve any group names listed in the Assignee fields when a task is assigned (groups must already be defined in the Domino Directory on the server). Click Save and Close to save the profile document.
Click Report a Problem in the outline to create a new Problem Report. When making the task assignment, you can enter names manually, click the field entry helper button to select them from the Domino Directory, or click the Select Role button. When you click Select Role, you can pick from the assignment roles defined in the profile document:
Figure 5. Assign Role dialog box
Depending on the assignment rule for the assignment role defined in the profile document, an appropriate name is picked from the list of names for that role in the profile document.
Now that you have seen how rule-based task assignment operates, let's see how it works from a developer's perspective. Open the HomeOutline outline control in Domino Designer 6.x. The entry of interest is the Application Settings entry:
Figure 7. Home Outline
This entry opens the ProfileDoc as a profile document from the Application Settings Outline Entry with an action that uses @Command([EditProfile]; "ProfileDoc"). Notice that the second argument in the command, uniqueKey, is omitted so that the profile settings are available to all users.
Though not done in the example database, it is presumed that you will limit who can edit the profile document to one or more database administrators, typically defined in the ACL as members of some role like [DBAdmin]. At the very least, you should hide the Button, Action, or Outline Entry used to edit the profile document based on role membership, for example hidden from everyone except users in the [DBAdmin] role. For easy testing, this security has been removed from the Task Assignment database.
The Application Settings entry in the outline control opens the ProfileDoc form. Open the ProfileDoc form in Domino Designer 6.x. There are a few important things to point out:
- The field names are incrementally numbered, for example, Role1, Role2, Role3, and so on. This is done so that you can write code (in all languages) that loops through the field names by incrementing the trailing digit. Later, we show how the index of the assignment role picked by the user is used to read the corresponding assignment field in the profile document.
- The Assignee fields use the Names field type and allow multiple values.
- A hidden computed field named Roles creates a multi-value list that is used when prompting the user to pick an assignment role. If you add more assignment role/rule field sets, remember to change this field formula to match.
Open the ProblemReport form in Domino Designer. This form's purpose is to let the user populate the AssignTo field by clicking the Select Role button. Click the Select Role button to see the event code in the Programmer's Pane. For demonstration purposes, all of the assignment rule code and helper functions are included in the Select Role button Click event.
Note: If you are using assignment roles in other places, move the code to a Script Library, so it can be maintained in one place.
Prompt user for assignment role name
The first task of the Select Role button is to prompt the user to pick an assignment role. The Declarations event declares a number of objects that are used in other events.
Note: Some of the variables, including the NotesSession, NotesUIWorkspace, NotesDatabase, NotesUIDocument, and NotesDocument, may already be declared and set in a form level event in your application, so you can safely remove them from here (and rename the variables in this code to match). They are put here for demonstration purposes, so you can easily see what is going on.
The Click event is where you will find the LotusScript that performs the task assignment. Fortunately, the list of all the assignment roles has already been computed by the Roles field in the profile document, so the following line instantiates the profile document to read its items: Set profiledoc = db.GetProfileDocument("ProfileDoc").
Before reading the values from the Roles item, remember that when you use the extended class syntax to read an item from a document that it returns an array of values by default: GetList = profiledoc.Roles.
Now that you have the array of values for the list of the assignment role names from the profile document, you can use it when you call the Prompt method:
UserPick = w.Prompt (PROMPT_OKCANCELCOMBO, "Assign Role", "Please pick a role to work on this request. A person in the role will be selected to be assigned to this request.", GetList(0), Fulltrim(GetList)) |
The Prompt method using PROMPT_OKCANCELCOMBO returns a variant of type string to the UserPick variable, so a simple test in the next line checks if the user didn't pick anything or clicked the Cancel button on the prompt dialog box: If Isempty (UserPick) Or UserPick="" Then Exit Sub.
If the code gets this far, it means that the user has selected one of the assignment roles from the prompt dialog box and has clicked OK. The job now is to go back to the profile document to find the corresponding:
- Assignee names listed for the assignment role
- Assignment rule to pick a name from the list of names
Find the corresponding list of assignees
As you recall, there are five sets of assignment role name/names/assignment rules in the ProfileDoc form. The user's choice in the ProblemReport form is stored in the UserPick variable. So how do you know which assignment role item to read from the profile document?
Remember that the fields were named using a number at the end, such as Role1? Because you have an array of role names stored in GetRoles and the role picked by the user stored in UserPick, you can use the Arraygetindex function to determine the element number of the user choice (and add 1 because by default the first element in an array is at the 0 location): GetElementNo = Arraygetindex(GetRoles, UserPick) + 1.
With the array location number matching the user selection, you must convert it to a string to concatenate it to the field name stem, for example, concatenate "Role" to "1" to get "Role1" as the argument for the GetItemValue method.
GetElementStr = Fulltrim(Str(GetElementNo))
GetTechRaw = profiledoc.GetItemValue("Tech" & GetElementStr)
|
The second line of code in the previous snippet shows how to find the list of assignees corresponding to the assignment role. Because the set of field names starts with "Tech," use the GetItemValue method to fetch the list of names for that assignment role. We use GetItemValue here instead of the extended class syntax (for example, profiledoc.Tech1) because the item name is determined at run time, in this case from the array location number of the assignment role selected by the user. Because it is quite possible that the field contains more than one name, remember to treat GetTechRaw as an array of values.
For any of the assignment rules, the database administrator can enter individual user names or one or more group names in the Assignee fields. The advantage of using a group name defined in the Domino Directory is that you can maintain the list of names outside any particular application and then use the groups across applications for task assignment, message addressing, security, and so on.
You may have noticed the Expand Group Name(s) option in the profile document:
Figure 9. Expand Group Name(s) option
When this option is selected, every name is looked up in the Domino Directory on the server. If the name happens to be a group, the members are put into the CumulativeNameArray and any nested groups are recursively resolved.
If GetExpandTechGroup = "1" Then
Redim Preserve CumulativeNameArray ( 1 )
Redim Preserve CumulativeGroupArray ( 1 )
If Not Isempty (s.AddressBooks) Then
Forall dddb In s.AddressBooks
Call dddb.open("", "")
If dddb.isopen And dddb.CurrentAccessLevel > 1 Then
If dddb.IsPublicAddressBook And Lcase(dddb.filename) =
"names.nsf" And dddb.server<>"" Then
Set dddbObj = dddb
Forall ListedName In GetTechRaw
Call ExpandName ( dddbObj, ListedName )
GetTechFull = Arrayappend(GetTechRaw,
CumulativeNameArray)
End Forall
Exit Forall
End If
End If
End Forall
End If
If Isempty( GetTechFull ) Then GetTechFull = GetTechRaw
GetTech=Fulltrim(Arrayreplace(Arrayunique(GetTechFull),
CumulativeGroupArray, ""))
Else
GetTech = GetTechRaw
End If
|
If the user cannot authenticate with his Home Server, an array of address books is not created for the NotesSession object. So the line: If Not Isempty (s.AddressBooks) Then prevents an error from occurring in this case. As you loop through the address books, it is possible that some of the databases have an ACL that prohibits reading. The line: If dddb.isopen And dddb.CurrentAccessLevel > 1 Then tests to make sure that the database can be opened and that the user has at least Reader access.
The next line ensures that the group is found (only) in the primary Domino Directory on the Domino server. If you have defined groups in other secondary directories, you can change this test. By the way, if the user is off-line and using a local replica of the Task Assignment database or if the group doesn't exist, the literal group name is returned. As you loop through the names from the profile document in the lines beginning Forall ListedName, the helper function ExpandName is called in the line Call ExpandName, which in turn calls the ExpandGroupMembers function (which calls itself recursively to resolve nested groups). If you are curious, you can look at these functions under the Select Role button to see how they work.
Note: The ExpandName and ExpandGroupMembers functions are good candidates for a Script Library if used in other places in an application. If you do this, remember to declare the variables globally, especially the dynamic CumulativeNameArray that is the "results" array of all the resolved names and the dynamic CumulativeGroupArray that stores any group names that are encountered and resolved.
The final transformation of the GetTech array of resolved names is in the line that begins GetTech. Here we remove any duplicate names, any group names (stored in CumulativeGroupArray), and the resulting empty locations from the removed group names. Now you are ready to apply the assignment rule to decide who is assigned to the task.
Find the corresponding assignment rule
Using the same technique used to get the list of assignees for a particular assignment role, you can determine the corresponding assignment rule in the following line: GetTechHow= profiledoc.GetItemValue("SelectTechHow" & GetElementStr)(0).
Because we are only interested in the first value--the assignment rule--of the item, we specify (0) to indicate the first element of the array that is returned by GetItemValue. By the way, the choice of assignment rules in the ProfileDoc form uses keyword synonyms, so we expect a single letter in the GetTechHow variable:
All | A First | F Random | R Sequential | S Workload | W |
Before we continue, let's summarize what has happened up until now. The user picked an assignment rule name from a prompt dialog box. We figured the index value of the rule picked and went to the corresponding fields in the profile document to read the list of names (GetTech array) and the assignment rule (GetTechHow). With these two pieces of information, we then used the corresponding assignment rule to determine which name or names to pick from the list of names for that assignment role.
The best way to do this work is by using a Case statement: Select Case GetTechHow. GetTechHow contains a single letter that represents the letter of the assignment rule, so you can use it as the basis of the branching.
When you apply an assignment rule, the last step is to take the result and put it into the AssignTo field in the current document, the ProblemReport. But for now, let's look at how the assignment rules are used to pick a name from those listed in the GetTech array.
This is an easy rule to apply. Simply assign the task to all the names listed in the GetTech array to the AssignTo field in the current document:
Case "A"
thisdoc.AssignTo = GetTech Should be doc.AssignTo
|
Because the AssignTo field accepts multiple values, assign the entire array of names to it. Notes handles the splitting of the array into multiple values for you.
This is also an easy rule to apply because all you need to do is read the first element in the GetTech array of names and assign it to the AssignTo field:
Case "F"
thisdoc.AssignTo = GetTech(0) Should be doc.AssignTo
|
The last line selects the first name in the array of names by specifying the (0) location of the GetTech array of names.
Rather than assigning a task to all possible assignees or always picking the first name, the Random rule randomly assigns the task to just one of the possible assignees in GetTech. You may have noticed right before the Case statement that we determined the number of the names in the GetTech array and assigned it to the GetMax variable using the Ubound function.
Using the number of elements in the list and the Rnd (random) function, we compute a random number (integer) between 0 up to and including the value of GetMax. Then we use the random number in the line thisdoc.AssignTo to read one name from the GetTech array of names:
Case "R"
GetMin = 0
Randomize
GetIndex = Int(((GetMax - GetMin +1) * Rnd) + GetMin)
thisdoc.AssignTo = GetTech( GetIndex ) Should be doc.AssignTo
|
Sequential rule Whereas making assignments randomly seems fair, another school of thought suggests that it makes sense to assign tasks in a round-robin manner. The theory here is the workload will more naturally and fairly spread out among all the possible assignees, especially if only one person is making the assignments and the total number of assignments are few.
While we could have used the profile document to store the sequences for each assignment role and keep one counter for each, we decided to store them in the local preferences file (Notes.ini). This might provide a greater resource smoothing effect for each assignment rule.
Because the Notes.ini file often contains many variables, it is important to generate unique variable names so that developers don't step on the variables used by other applications. We decided to use our company name (wareSource) with the vowels removed, the file name of the current database (in case there are multiple instances of this application), and the name of the assignment role field. The resulting variable in the Notes.ini looks something like this: $WRSRC-taskassignment-4 = 1.
Now that we have a somewhat unique set of variable names, the strategy of the Sequential rule is to track which element was last used, referring of course to the GetTech array of names for a particular assignment role. As each name is used, the variable for the role in the Notes.ini file is incremented until the last element is used, and then the counter is reset to 0. The following code snippet builds the variable name to be used in the Notes.ini file:
Case "S"
dbShortName = Strleft( Lcase(db.FileName), ".nsf")
INIVar = "WRSRC-" & dbShortName & "-" & RoleSeq1 & GetElementStr
GetIndex = GetRoleCounter ( s, GetMax, INIVar)
thisdoc.AssignTo = GetTech( GetIndex )
|
The fourth line in the snippet calls the GetRoleCounter function, which handles the work of reading and setting the variable in the Notes.ini. All that is returned from the GetRoleCounter function is an integer from 0 through the value in GetMax, which is then used to read one of the elements from the GetTech array of names in the last line of the snippet.
The GetRoleCounter function is passed a handle to the NotesSession, the number of names in the GetTech array, and the name of the Notes.ini variable to read and set. This function is responsible for both setting and reading the set of variables tied to this rule.
When you look at the GetRoleCounter function, remember that Notes.ini variables only handle strings, so that is why we convert the value to an integer using Cint to increment the value. Then convert it back to a string using Cstr before setting it back to the Notes.ini file.
This is the most difficult rule to implement, but the most powerful. The strategy of this rule is to assign the task to the person who is the least busy. But how does the developer determine which user is the least busy? That is up to the demands of a particular application. In this application, the Status and Priority fields determine the level of work for a Problem Report:
Figure 10. Status and Priority
These two fields feed a hidden lookup view named LookupWorkload to do the computation of who is least busy based on two criteria set when making the assignment:
- Status. Only select ProblemReports that have the Status field set to Assigned, not selecting those marked New, On Hold, or Completed.
- Priority. The priority of each ProblemReport increases the "weight" of the work factor:
Low | 0 Moderate | 1 Urgent | 2 |
This information is then totaled in the WorkFactor column so that the overall workload is determined:
Figure 11. Task assignment
As tasks are marked completed, they drop out of the view, and the workload is reduced. More urgent tasks increase the workload. Because the view handles totaling of the workload, all the Workload rule needs to do is look for each name in the view and read the work factor value. The person with the smallest work factor is selected and assigned the task. Remember that the variable GetTech contains the list of possible names from the assignment role selected by the user. The idea here is to loop through the names and to find the one with smallest work factor. We decided to use a simple comparison starting with an arbitrarily high number as a starting value. To keep track of the lowest workload, use the variable Workload as the scoreboard and Assignee to keep track of who has the lowest work factor.
Case "W"
Dim WorkloadView As NotesView
Set WorkloadView = db.GetView( "LookupWorkload" )
Dim ViewNav As NotesViewNavigator
Set ViewNav = WorkloadView.CreateViewNav
Dim ViewEntry As NotesViewEntry
Workload = 100000
Set ViewEntry = ViewNav.GetFirst
Do Until ViewEntry Is Nothing
Forall OneName In GetTech
If OneName = ViewEntry.ColumnValues (0) Then
If Cint(ViewEntry.ColumnValues (1)) < Workload Then
Assignee = ViewEntry.ColumnValues (0)
Workload = ViewEntry.ColumnValues (1)
End If
End If
End Forall
Set ViewEntry = ViewNav.GetNextCategory (ViewEntry)
Loop
. . .
doc.AssignTo = Assignee
|
It seems inefficient to loop through all the ViewEntry objects in the ViewNavigator object and then loop through the list of names for each one, looking for a match. But because you are reading from a view (and not opening any documents), things move fairly quickly.
If the name matches one in the view column, we read the workload from the second column in the line:If Cint(ViewEntry.ColumnValues (1)) < Workload Then and compare it to the arbitrarily high value first set in the line Workload = 100000. If the assignee has less of a workload, that person becomes the new "winner" and is recorded in the Assignee variable in the line Assignee = ViewEntry.ColumnValues (0), and that person's workload is used to reset the Workload variable. As you loop through the list of assignees, compare each workload to the previous assignee. The assignee with the least amount of work is finally assigned the task in the last line doc.AssignTo = Assignee.
At first, we were tempted to take a more direct approach and loop through the list of names (outer loop) and then scan the view for a match, for example: Set ViewEntry = WorkloadView.GetEntryByKey( x , True). The problem in this case is that GetEntryByKey only reads documents, ignoring categories and totals in views. And because the workfactor values are totaled in the view, we really want to read the column values from the categories. So it seems that we can only loop through the ViewEntry objects if we want to read the totals. At least, we can use GetNextCategory to skip over documents to save time.
There is another approach that we included (but commented out) in the code to loop through the list of names (outer loop) in the array of names in GetTech. Start with the NotesView object and use the GetAllEntriesByKey method to get a collection of ViewEntry objects (NotesViewEntryCollection) matching the first name. Then loop (inner loop) through the collection of matching entries to manually total the workload for that person rather than use the total column from the view.
In testing the performance of both approaches on small data sets, it seems that the first approach is faster. But you can try the second approach to see which one works best for your particular application.
By the way, don't even consider looping through the NotesDocument objects from the NotesView object, for example, Set doc = view.GetFirstDocument. It is simply not necessary in this case to open the documents just to read one piece of information that is efficiently stored, displayed, and totaled in the view.
Of course just assigning someone to a task doesn't guarantee that the person will know about the assignment or that the person will actually work on the task. There are, however, things you can do as the developer to at least let users know that they have been assigned to a task and also to make it easy for them to see all their assignments in one place when they open the application (which could also be added to a custom Welcome Page in Notes).
To give you some idea of what you can do with a task assignment, let's see a few things that we did in the wareSource.com Self-Help Desk application (from which the design of the Task Assignment database was extracted). In Self-Help Desk, the assignment name is used in three ways.
Address reminder messages to Notes users
The Problem Report includes the option Email when assigned:
Figure 12. Problem Report
When the document is saved and this option is selected, the assignee(s) receive an email containing a short description of the problem and a Doclink to the Problem Report. Here is a piece of the code that shows how to send a reminder message from the QueryClose event:
If source.editmode And IsBeingSaved=1 And (doc.InformMe(0) = "1" Or doc.InformAssignee(0) = "1" ) Then Call SendReminderEmail (w, s, db, doc, oldStatus) End If |
It just so happens that we also send an email to the person making the request (that option is set on the Contact tab). The SendReminderEmail subroutine takes care of the mechanics of sending the two reminder messages.
Another option is to categorize views using the assignment name or to show users only their assignments on the application home page. To do this, simply embed a view on a page that sets the Show single category view property to the user's name. For example, the HomePageWelcome page displays your assignments when you open the application. This is accomplished using an Embedded View element using a Show single category selection using your user name:
Figure 13. My assignments
The example database also happens to open to such a view in the right frame.
Control who can edit a document
Control who can edit the document by setting the value of an Authors field to the assignee name so that the person owns the document until the application logic determines another owner. The Owner field on the Problem Report form, for example, is a computed Authors field that uses this formula to reassign ownership of the document depending on the Status (New, Assigned, On Hold, Closed) of the request:
@If( Status="1"; Requestor : "[HelpDesk]" : "[DBAdmin]"; Status="2"; AssignTo : "[DBAdmin]"; Status="3"; AssignTo : "[DBAdmin]"; Status="4"; CloseBy : "[DBAdmin]"; "[DBAdmin]") |
As you recall, names surrounded by brackets, such as [HelpDesk] represent roles defined in the database ACL. So for example, when a ProblemReport document status is changed to 2 (Assigned) and is saved, the document can now be edited by the person assigned to handle the problem as well as to any user listed in the [DBAdmin] role in the ACL.
To use these features in your application, copy these design elements from the example database to your database:
- Forms: ProfileDoc and elements of ProblemReport (Status, Priority, and AssignTo fields, and the Select Role button)
- Views: LookupWorkload and views to display ProblemReport (or equivalent) documents.
It is up to you to configure your application so that database administrators can open the ProfileDoc form as a profile document. You can follow the example of the HomeFrameset and HomeOutline design elements.
This article describes a Notes application strategy that uses assignment rules to determine who should be assigned to a task. This strategy can be used to increase the accuracy and appropriateness of task assignments. After you have assigned someone to a task, you can then use the assignment name for such things as to inform the assignee of the task via email, to show all the assignments in a single category view, and to pass ownership of the task document to the assignee. When you add rule-based task assignment to your applications, the chance of incorrect task assignment decreases, and your organization's accuracy and responsiveness improves. And because you are adding this feature to a Notes application using Domino Designer, it takes very little effort to quickly realize significant benefits.
Resources Download the sample Task Assignments database from the Sandbox. For information about new LotusScript enhancements in Notes/Domino 6, see these articles LotusScript: Rich text objects in Notes/Domino 6, LotusScript: XML classes in Notes/Domino 6, LotusScript: More XML classes in Notes/Domino 6, LotusScript: The NotesAdministrationProcess class in Notes/Domino 6, and LotusScript: Programming views in Notes/Domino 6.
Figure 1. The Java Beans view
- Get involved in the developerWorks community by participating in developerWorks blogs.
- Browse for books on these and other technical topics.
Kent Kurchak is the owner of wareSource, an IBM Business Partner that provides robust, customizable, and economical training materials and applications for Lotus Notes and Domino users, developers, and administrators. wareSource courseware is used worldwide by IBM Business Partners, training companies, independent instructors/consultants, corporate training departments, and individuals.




