I recently had a client who needed a workflow that could handle voting in a way that was a bit challenging to get working. I'm sure there a multiple ways to handle this, and I tried many unsuccessfully. I wanted to share an approach that I was able to apply successfully in case anyone else has a similar scenario. At a very high level, they need to gather votes and count the different responses, but the number of people who would actually respond might vary.
The business requirements were as follows:
- Allow for email reply (LazyApproval)
- Send reminders
- Auto-close the task(s) after a specified time
- Allow everyone to vote
- Tally the votes, including the number of "no response", and save back to the item
I'll go through the requirements individual and describe what I used to address them first.
Allow for email reply
This one was easy, enable LazyApproval in whatever task action I would use.
Auto-close the tasks after a specified time (escalation)
This was a requirement that pointed me to Flexi tasks. The due date and time for the voting on these items could be specific, like in the middle of the day, so I couldn't just use days for this one. I started with a Build string action, so I could take advantage of the inline functions like fn-DateDiffMinutes to get the number of minutes until the due date and time.
Then, I used Set variable to make the Flexi task happy since it wanted a number in that field. Looking back on this now, I am not sure this action is necessary. You might just be able to use the text variable instead of making it a number. It is all working now after a lot of effort, so I'm going to leave it.
Here is the Flexi task with that variable for the Time to escalation:
I added an outcome and comments text to more easily track who didn't respond in the workflow tasks list. Also, I need it for the tallying part later. Here are the two actions.
The Flexi tasks have an "Other" branch to handle the "No Response" scenario when the voting window has closed.
This was one of the requirements that drove the decision to use Flexi tasks. Initially, during the design of the workflow, I put "5" into the "Number of reminders" field arbitrarily.
Something unexpected happened with the escalations when I did this and it took me a while to put the pieces together. Now, it's possible that I got this wrong, but it seemed like the items wouldn't do the escalation (Complete task) until after all of the reminders were sent. I was not expecting a dependency there, and maybe it is a bug in the version I have or I just got it wrong, but everything I observed made me think this is how it was functioning. If my item was set with a time to escalation that was 1 day away, but I had the 5 reminders above, it wouldn't complete the task until after all of the reminders were sent. So, with that in mind, I did the following:
The voting due date was a required field in the form, so I wanted to get the number of days between the current date and the due date. With a Build String action, I used the fn-DateDiffDays to get that number.
I noticed that this was rounding when the result was something like 2.8 days, so I would get one extra reminder and the tasks would close one day late. To address this, I used a Regular expression to split on the decimal.
So, that result is stored into a collection variable as 2;8. Then, of course, I needed to use a Collection operation to get just the whole number of days, 2, from the collection.
Now, I have the number of reminders in a text variable that I can reference in the Flexi task.
The variable "numCollIndex" is just an integer variable with a default value of 0. I am only using it for this collection operation, so it is always 0 in order to get the number 2 from index 0 of the collection.
Here, the result is referenced in the Number of reminders field for the Flexi task.
This gave me the variable number of reminders without rounding, so if the due date is 1.2 days away, I get one reminder, if it is 2.8 days away, I get 2 reminders.
Here are the three actions together, ready to make some magic happen!
Allow everyone to vote
The "Request approval" action has a vote option, but unfortunately, it doesn't have the reminders or the escalation options, so this became a challenge and a significant driver of the workflow design. Ultimately, there are 7 teams that get a vote. For the most part, there is only one representative, but there may be cases when two people are part of the team. So, I used SharePoint permissions groups here to make sure the members could be changed easily.
I couldn't use just one Flexi task action assigned to all of the groups, or just one group with all of the members, because it doesn't allow for everyone to vote. The tasks would close once a majority was reached. With the all must agree options, the tasks closed when there was disagreement. Your options are the following:
When I tried these options, the task would not allow for all responses in the way that we wanted that would be easy to understand for the process owners. So, after trying a few approaches, I went with parallel actions. I wanted to do a streamlined, elegant solution, but I opted for clarity and better supportability for my team later.
Here are a few of the tasks... there are just 7 branches of Flexi tasks. Since all the votes get tallied, there are no actions in any of the specific branches under the Flexi tasks. I'll describe what these are doing in the next section.
Tally the votes
The Flexi tasks have the standard Reject and Approve options, and we also need to know when people don't respond before the time to escalation elapses, so the "No Response" outcome is captured in those instances. The escalation options for the Flexi task look like this:
A key part is to capture both the outcome and the task id for each of the voters. We have 7 total, and below is the config for #3:
After each of the Flexi tasks, I am using a Collection operation to capture all of the task IDs so I can get the specifics later with a list query action.
The Collection operation just adds each task id from the corresponding variable:
Now, we do a For each through the voting tasks in the CollVoteTaskIDs collection. I did a pause for 5 minutes here to make sure all the updates to the items in the task list were complete before trying to loop through them:
Here is the For each configuration
The Query list
The variable "txtCurrentVoteOutcome" is the "Yes", "No", or "No Response" results for each voting group. The "perCurrentAssignedTo" is the person who entered the vote.
Next, I am going to get the display name of the person and start creating a multiline of text field, so later on the process owner can get an email with all of the voters and their responses.
The Set variable looks like this:
And the Build string looks like this:
Now the tally at the end of the For each... I have 3 variables to store the tallied results, numYesVotes, numNoVotes, and numNoResponse. The conditions are looking at the variable I got from the Query list action called txtCurrentVoteOutcome.
In the first condition, if txtCurrentVoteOutcome = "Approve", the Math operation adds one to the numYesVotes variable. In the second condition, if txtCurrentVoteOutcome - "Reject", the Math operation adds one to the numNoVotes variable. If neither of these are true, one is added to the NumNoResponse variable. Then it goes back to the top of the For each and checks the next one.
After the loop, the tallied votes are put into the fields for the item.
At the very end is a final approval checkpoint. This task goes to the process owner before the final "go/no go" meeting.
This is just a regular Flexi task, but it contains all the voting info in the notification to the process owner.
it includes a summary of the number of votes along with the outcome of each vote from the multiline of text variable (mtxtVoteOutcome) that was created earlier.
In total, this voting component is part of a pretty large state machine workflow. The rest of it deals with other parts of the business process, but this voting part was the most complex piece for me to figure out. Hopefully, it gives you some ideas on a way to handle this if you have a similar scenario. Happy Nintexing!