Skip to main content

Hi there,

First message in this forum, although I have read a lot of the threads here.

Short story: Using code in K2, how do I enumerate through all instances of an activity and how do I force an instance to "Finish"?

Long story:
Ok, I have the following situation: Activity "A" which leads to "B" or "C", depending on the outcome. "A" is a multi-slot activity. In other words, it is being sent to a number of people (let's assume 5). For each one of those people a slot is being created. They have to basically choose "Yes" or "No" (Approve or Reject).

"A" leads to "B" if ALL SLOTS = Yes

"A" leads to "C" if AT LEAST 1 SLOT = No AND ALL SLOTS ARE NOT EMPTY (I have set a default value to Action Result which I check for).

So basically, everything works perfectly. The opinion of every participant matters and the workflow is not moving to the next step, before everyone 'votes'. However, an enhancement has to be introduced. After a period of time (let's say 2 days) all slots (participants) who have not selected "Yes" or "No" need to be deleted, as they never existed. Or, just force them to "finish" and default to "Yes". I have figured out most of it - use Default escalation and click "View Action code", but I am not sure what code exactly to write. I don't know how to enumerate through all of the instances of the activity, neither how to force it to "Finish". Here is what I have tried, but it doesn't work at all. I don't know if it's enumerating right and the Status property is read only :(

 
for (int i = 0; i < K2.ActivityInstance.Destinations.Count; i++)
{
  if (K2.ActivityInstance.Destinations.Status == ActInstDestStatus.Active)
  {
    K2.ActivityInstance.Destinations.DataFieldsF"Action Result"].Value = "Approved";
    K2.ActivityInstance.Destinations.Status = ActInstDestStatus.Completed;
  }
}

 Any help is highly appreciated!

 
Thanks,

Hristo


 

Just to step back a bit, wouldn't using a escalation on the activity (set to 2 days) and then using a GotoActivity action to move it to activity B work?

icon-quote.gifjohnny:
Just to step back a bit, wouldn't using a escalation on the activity (set to 2 days) and then using a GotoActivity action to move it to activity B work?

 Well, not really, because the GoTo is not conditional. When I use GoTo Escalation logic, I basically have to hard-code if it should go to "B" or "C". But in the real world, that depends on how users have voted. In other words - using GoTo overrides my line & succeeding rules and I am losing the business logic.

 Thanks for the reply, though!

 


 


Come on, anybody? That shouldn't be that hard?

Could you clarify if this is what you are trying to do?


A task will be assigned to X participants for voting.  Everyone must vote yes or no.  If everyone votes yes, the process continues to activity B.  If everyone votes no, the process continues to activity C. If the escalation period is reached before everyone votes, count everyone who didn't vote as a Yes, then proceed to activity B if there were more yes votes, or activity C if there were more no votes.


icon-quote.gifDavidL:

Could you clarify if this is what you are trying to do?


A task will be assigned to X participants for voting.  Everyone must vote yes or no.  If everyone votes yes, the process continues to activity B.  If everyone votes no, the process continues to activity C. If the escalation period is reached before everyone votes, count everyone who didn't vote as a Yes, then proceed to activity B if there were more yes votes, or activity C if there were more no votes.



Hi,

Yes, a task will be assigned to X number of participants. Everyone must vote yes or no. If everyone votes yes it will go to "B".

However, if at least one votes No, it should go to "C". For example - 4 voted "Yes" and one voted "No" it should  go to "C".

If the escalation period is reached before everyone votes, count every who didn't vote as a Yes, and then proceed to "B" if all the votes are "Yes" or to "C" if at least one vote is "No".

 

See, the thing is, I have already set up everything and it works perfectly, except the escalation. All I still need to do is make the escalation to force the "unvoted" votes, to vote "Yes", or even better, make them disappear as if they never existed and then proceed with the line rules I already have in place.

 Please tell me you have an idea :)

 
 Thanks! 

 


Here's an idea.  I am working under the assumption that you want to give everyone a chance to vote, and not just end the voting after the very first no vote is cast.


Create an integer process-level data field called TotalVotes.  After the voting client event, add a server code event to increment this counter by 1.  Then add succeeding rules:  if all slots are Yes, continue to activity B.  If at least one vote is No and the counter TotalVotes is equal to the number of slots, got to activity C.  Then create a default escalation and edit the code.  If there is at least one no vote, use K2.GotoActivity("Activity C") else K2.GotoActivity("Activity B").  It should also be possible to do this with an activity-level data field and a variation on the rest of the logic.


Yes, right assumption, everyone has to have a chance to vote.

 

About the vote counter, I have achieved it in other way - set the Action Result data field to have a default value of 'none'. Then I am checking for all slots being <> 'none'. But essentially it's the same thing you propose.

 

And now the real thing - your idea is great. I never thought about this. However, the K2 classes are totally alien to me. Can you give me a tip how to enumerate through all the votes? Which collection should I enumerate?

Wouldn't it be nice if there was an out-of-the box escalation with conditional GoTo? :)

 

Thank you!

 


I am trying the following code, but it always goes to "ApprovedStep", so I assume something is wrong it. Probably that's not the right way to get the data field. Any suggestions?

 

for (int i = 0; i < K2.ActivityInstance.Destinations.Count; i++)
{
  if (K2.ActivityInstance.Destinations.DataFieldsd"Action Result"].Value.ToString() == "Declined")
  {
    K2.GotoActivity("DeclinedStep");
  }
}

K2.GotoActivity("ApprovedStep");


The reason why it is going to ApprovedStep is because it is the last line of code.  I think you need to do the following.


for (int i = 0; i < K2.ActivityInstance.Destinations.Count; i++)
{
  if (K2.ActivityInstance.Destinations.DataFieldsd"Action Result"].Value.ToString() == "Declined")
  {
    K2.GotoActivity("DeclinedStep");
  } else {


K2.GotoActivity("ApprovedStep");  


}
}

Thing I am uncertain right now if the escalation event fires once for all destination instances or will fire for each and every destination instance.  If it is the latter, I am not sure the loop is needed...


icon-quote.gifjapergis:

The reason why it is going to ApprovedStep is because it is the last line of code.  I think you need to do the following.


for (int i = 0; i < K2.ActivityInstance.Destinations.Count; i++)
{
  if (K2.ActivityInstance.Destinations.DataFieldsd"Action Result"].Value.ToString() == "Declined")
  {
    K2.GotoActivity("DeclinedStep");
  } else {


K2.GotoActivity("ApprovedStep");  


}
}

Thing I am uncertain right now if the escalation event fires once for all destination instances or will fire for each and every destination instance.  If it is the latter, I am not sure the loop is needed...

Thanks for the reply!

 
Yes, you almost got it right. The way you suggest it, will transfer to another activity, after checking just the first of all the destinations. The right way to do it is to have a "return" statement in the "Go To Declined" block, so it doesn't execute both of them. The other thing I got wrong is the fields. I shouldn't be looking at the DataFields, instead, I should be checking the XmlFields.

Lucky for me, the escalation is only one instance. Not an escalation per instance... :) 

 Here is the whole thing, I hope it will help someone else. Credit goes to this post.

private void codeActivity1_ExecuteCode(object sender, EventArgs e)
{
  for (int i = 0; i < K2.ActivityInstance.Destinations.Count; i++)
  {
    if (IsDeclined(K2.ActivityInstance.Destinations))
    {
      K2.GotoActivity("DeclinedActivity");
      return;
    }
  }

  K2.GotoActivity("ApprovedActivity");
}

private bool IsDeclined(ActivityInstanceDestination aid)
{
  // we need to get the xml from the K2 state data for my form2
  // Note: 'PolicyApproval' is the name of my infopath form as it appears in the K2 Object Browser
  string k2xmlString = aid.XmlFields/"PolicyApproval"].Value.ToString();

  // get stuff from form
  XmlDocument xmlDoc = new XmlDocument();
  xmlDoc.LoadXml(k2xmlString);

  // create a namespace manager for InfoPath
  XmlNamespaceManager nsMgr = new XmlNamespaceManager(xmlDoc.NameTable);
  nsMgr.AddNamespace("my", xmlDoc.DocumentElement.GetNamespaceOfPrefix("my"));

  // get the value of the dropdown with the "Approved" / "Declined" values
  XmlNode drpApprovalNode = xmlDoc.SelectSingleNode("//my:myFields/my:drpApprovedOrDeclined", nsMgr);

  return (drpApprovalNode.InnerText == "Declined");
}

Everything works perfect from this standpoint, but now I have another problem. Activity "A" leads to activity "B", which then leads back to activity "A". However, on the Process level, activity "A" preserves the values of the last its instances. So when a user goes to this steps again, his fields are already pre-populated 0_o ... I have to clean it somehow.


Just thought I'd chime in with a simpler approach to retrieve the action result rather than pulling it from the InfoPath form. The action result in actually stored as a Slot level DataField. I have a post about it on my blog which goes a little deeper in depth, but if you just want sample code, accessing it looks something like:

    string actionResult = null;
 
    foreach (SourceCode.KO.Slot slot in activityInstance.WorklistSlots)
    {
        if( slot.DataFields.Contains("Action Result") )
        {
            actionResult = (string)slot.DataFieldsc"Action Result"].Value;
            if( actionResult != null && actionResult != String.Empty )
            {
                break;
            }
        }
    }
You don't necessarily need the loop, etc., if you know your activity only has one slot.

Reply