The Microsoft Dynamics CRM Blog
News and views from the Microsoft Dynamics CRM Team

Microsoft Dynamics CRM – Advanced Workflows

Microsoft Dynamics CRM – Advanced Workflows

  • Comments 10

image

Today’s guest blogger is CRM MVP Ayaz Ahmad who is a CRM Solution Architect with more than seven years in software development and managing large projects and technical teams. Ayaz blogs at http://ayazahmad.wordpress.com/.

Microsoft Dynamics CRM provides very powerful workflow designer tools to create workflows for various business scenarios. Waiting workflows are one of them. Very often, it is required to create waiting workflow on dates. So if a date is changed the waiting workflow remains in waiting state and date change executes another waiting workflow. So this aggregation of workflows affects CRM Server performance and application functionality.

Recently I created a custom workflow activity to stop these waiting workflows in a recursive workflow. So I would like to share my experience with all of you.

I am in a condition to create a waiting workflow to reschedule my activity periodically such as in recurring appointments, Auto Reorder (Sales Order), etc. My workflow is fired on Create of activity/Order and on change of Next Due date/Next Order Date. So if the user has changed Next Due Date or Next Order Date, the workflows in waiting remains in waiting and also a new Waiting workflow is fired. My aim is to stop the existing workflow as it is invalid now and start a new waiting workflow with updated information.

My workflow name is Auto Reschedule Activity. Here is the description of my workflow:

Start:

    Wait for Next Due Date == Today

         Reschedule Activity Accordingly

         Recursive Call: Auto Reschedule Activity

End

What i am looking for is:

Start:

     Check if there is an existing workflow with same Workflow ID, Entity ID and in Waiting State.

          Then Kill that workflow.

     Wait for Next Due Date == Today

          Reschedule Activity Accordingly

          Recursive Call: Auto Reschedule Activity

End

So I started with a custom Activity to fetch workflows in waiting state with same name and regardingobjectid:

I created a query object in my custom workflow activity:

QueryExpression query = new QueryExpression();

query.EntityName = “asyncoperation”; 

query.ColumnSet = cols;

And created 3 conditions as below:

ConditionExpression c1 = new ConditionExpression();

c1.AttributeName = “name”;

c1.Operator = ConditionOperator.Equal;

c1.Values = new Object[] {‘My workflow Name’}; //Workflow

ConditionExpression c2 = new ConditionExpression();

C2.AttributeName = “statecode”;

C2.Operator = ConditionOperator.Equal;

C2.Values = new Object[] {1}; //Waiting

ConditionExpression c3 = new ConditionExpression();

C3.AttributeName = “regadingobjectid”;

C3.Operator = ConditionOperator.Equal;

C3.Values = new Object[] {context. PrimaryEntityId}; //Waiting

Now I have all the waiting workflows regarding my activity. Ideally there will be only one workflow in waiting state all the time. This workflow will be responsible for rescheduling.

Now is the time to kill the outdated workflow. Use code like this for setting that workflow to complete/cancelled.

foreach (BusinessEntity be in bec.BusinessEntities)

{

asyncoperation async = (asyncoperation)be;

async.statecode = new AsyncOperationStateInfo();

async.statecode.Value = AsyncOperationState.Completed;

service.Update(async);

}

Note that you can improve this workflow utility to add lot more functionality to the workflow designer. Workflow is also described in the Microsoft Dynamics CRM SDK.

Cheers,

Ayaz Ahmad



  • The url for workflow customization tutorial at Microsoft website is actually pointing to the old v3.

    Do you know the tutorial for v4?

  • Ah, my bad. Here's the correct link: http://rc.crm.dynamics.com/rc/regcont/en_us/op/articles/8-workflowcustomization.aspx

    Thanks for the ping.

  • Okay the link at "workflow" is fixed.

  • Thanks mate it is very helpfull solution...

  • Thanks mate it is very helpfull solution...

  • Hi,

    wonder if i could get your assistance/ point me in the right direction.

    For the data retrieval i've added the following code:

    <code>

    FilterExpression filter = new FilterExpression();

    filter.AddCondition(c1);

    filter.AddCondition(c2);

    filter.AddCondition(c3);

    query.Criteria = filter;

    RetrieveMultipleRequest retrieve = new RetrieveMultipleRequest();

    retrieve.Query = query;

    retrieve.ReturnDynamicEntities = true;

    RetrieveMultipleResponse retrieved = (RetrieveMultipleResponse)service.Execute(retrieve);

    <code>

    When the code hits the line:

    RetrieveMultipleResponse retrieved = (RetrieveMultipleResponse)service.Execute(retrieve);

    An error is generated:

    exception: "Server was unable to process request"

    InnerText: "\n  0x80040216\n  An unexpected error occurred.\n  Platform\n"

  • show me your complete code. there must be some issue with conditions.

  • HI,

    How can i create recurrance system Job,

    i wants to create recurance system job (Asyncjob)

    how can i create

    it showing an error org name privilage

    Thanks

    nileshkankariya@gmail.com

  • Hi.

    Nice post.  Can you also share your code for your recursive call to the Auto-Reschedule Activity?

    Many thanks and more power.

  • It is possible to create reccuring appointments in MS CRM 4.0 with form customization as well.

    I have done one implementation for creating reccuring service activity. I have added two new fields in the Service Activity entity.

    Please read the detailed implementation below:

    Entity Name: serviceappointment

    Add two new Fields

    New Field 1

    -----------

    Field Label: Recurring Frequency

    Field Name: new_recurringactivity

    Requirement Level: No Constraint

    Field TYpe: picklist

    Field Values:

    1 - Daily

    2 - Weekly

    3 - Fortnightly

    4 - Monthly

    5 - Bi-Monthly

    6 - Quarterly

    7 - Half Yearly

    8 - Yearly

    New Field 2

    -----------

    Field Label: Recurring End Date

    Field Name: new_recurringenddate

    Field Type: datetime

    Field Format: Date Only

    Requirement Level: No Constraint

    Purpose of the new fields

    -------------------------

    Field Name: new_recurringactivity

    Purpose: This field will accept recurring frequency from the user.

    Field Name: new_recurringenddate

    Purpose: This field will accept the date from the user. It will not create any activity beyond the date. Otherwise it will go in a endless loop.

    CODE SNIPPET 1

    --------------

    // Add this code snippet to the OnLoad event of the Service Activity Form

    // Lock the recurring Service Activiy fields once activities have been created

    if (crmForm.all.new_recurringactivity.DataValue)

    {

    crmForm.all.new_recurringactivity.disabled = true;

    crmForm.all.new_recurringenddate.disabled = true;

    }

    ----------------------------------------------------------------------------------

    CODE SNIPPET 2

    --------------

    // Add this code snippet to the OnSave event of the Service Activity

    // Function to format a date to the UTC format required by web services

    function DateToUTCFormat(inputDate)

    {

    var date = inputDate.getDate();

    var month = inputDate.getMonth()+1;

    var year = inputDate.getYear();

    var hours = inputDate.getHours();

    var minutes = inputDate.getMinutes();

    var ampm = " AM";

    if (hours > 11)

       {

       ampm = " PM";

       hours = hours - 12;

       }

    if (hours == 0)

       {hours = 12;}

    if (minutes < 10)

       {var time = hours.toString() + ":0" + minutes.toString() + ":00" + ampm;}

    else

       {var time = hours.toString() + ":" + minutes.toString() + ":00" + ampm;}

    var UTCDate = month.toString() + "/" + date.toString() + "/" + year.toString() + " " + time;

    return UTCDate;

    }

    if (crmForm.all.new_recurringactivity.disabled == false && crmForm.all.new_recurringactivity.DataValue && crmForm.all.new_recurringactivity.DataValue)

    {

    var interval = 0;

    switch (parseInt(crmForm.all.new_recurringactivity.DataValue))

    {

    case 1:   // Daily (Please check the value you are getting from the CRM Form)

    interval = 1;

    break;

    case 2: // weekly

    interval = 7;

    break;

    case 3: // Fortnightly

    interval = 14;

    break;

    case 4: // Monthly

    interval = 30;

    break;

    case 5: // Bi-Monthly

    interval = 60;

    break;

    case 6: // Quarterly

    interval = 90;

    break;

    case 7: // Half Yearly

    interval = 180;

    break;

    case 8: // Yearly

    interval = 365;

    break;

    }

    var recurringEnd = crmForm.all.new_recurringenddate.DataValue;

    recurringEnd.setDate(recurringEnd.getDate()+1);

    var activityStart = crmForm.all.scheduledstart.DataValue;

    var activityEnd = crmForm.all.scheduledend.DataValue;

    // Set the first reccuring appointment as per the recurring frequency opted by the user

    activityStart.setDate(activityStart.getDate()+interval);

    activityEnd.setDate(activityEnd.getDate()+interval);

    // Prepare values for the new Service Activity

    var subject = crmForm.all.subject.DataValue;

    var regardingId = crmForm.all.regardingobjectid.DataValue[0].id;

    var customerId = crmForm.all.customers.DataValue[0].id;

    var serviceid = crmForm.all.serviceid.DataValue[0].id;

    var resourceId = crmForm.all.resources.DataValue[0].id;

    var ownerId = crmForm.all.ownerid.DataValue[0].id;

    var new_recurringactivity = crmForm.all.new_recurringactivity.DataValue;

    // Loop for the number of recurring Service Activities

    while (activityStart < recurringEnd)

       {    

       // Prepare the SOAP message.

       var startUTC = DateToUTCFormat(activityStart);

       var endUTC = DateToUTCFormat(activityEnd);

      // alert("startUTC: "+startUTC);

       var recurringEndUTC = DateToUTCFormat(recurringEnd);

       var authenticationHeader = GenerateAuthenticationHeader();

       var xml = "<?xml version='1.0' encoding='utf-8'?>" +

       "<soap:Envelope xmlns:soap='schemas.xmlsoap.org/.../&

       " xmlns:xsi='www.w3.org/.../XMLSchema-instance&

       " xmlns:xsd='www.w3.org/.../XMLSchema&

       authenticationHeader+

       "<soap:Body>"+

       "<Create xmlns='schemas.microsoft.com/.../WebServices&

       "<entity xsi:type='serviceappointment'>"+

       "<subject>"+subject+"</subject>"+

       "<serviceid>"+serviceid+"</serviceid>"+

       "<ownerid>"+ownerId+"</ownerid>"+

       "<customers>"+

           "<activityparty>"+

               "<partyobjecttypecode>account</partyobjecttypecode>"+

               "<partyid>"+customerId+"</partyid>"+

               "<participationtypemask>11</participationtypemask>"+

           "</activityparty>"+

       "</customers>"+

       "<resources>"+

           "<activityparty>"+

               "<partyobjecttypecode>systemuser</partyobjecttypecode>"+

               "<partyid>"+resourceId+"</partyid>"+

               "<participationtypemask>1</participationtypemask>"+

           "</activityparty>"+

       "</resources>"+

       "<scheduledstart>"+startUTC+"</scheduledstart>"+

       "<scheduledend>"+endUTC+"</scheduledend>"+

       "<new_recurringenddate>"+recurringEndUTC+"</new_recurringenddate>"+

       "<new_recurringactivity>"+new_recurringactivity+"</new_recurringactivity>"+

       "</entity>"+

       "</Create>"+

       "</soap:Body>"+

       "</soap:Envelope>";

       // Prepare the xmlHttpObject and send the request.

       var xHReq = new ActiveXObject("Msxml2.XMLHTTP");

       xHReq.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);

       xHReq.setRequestHeader("SOAPAction","schemas.microsoft.com/.../Create");

       xHReq.setRequestHeader("Content-Type", "text/xml; charset=utf-8");

       xHReq.setRequestHeader("Content-Length", xml.length);

       xHReq.send(xml);

       // Capture the result

       var resultXml = xHReq.responseXML;

       // Check for errors.

       var errorCount = resultXml.selectNodes('//error').length;

       if (errorCount != 0)

           {

            var msg = resultXml.selectSingleNode('//description').nodeTypedValue;

            alert(msg);

           }

       // Notify user of Service Activity creation

       else

           {

          // alert("Service Activity created on " + activityStart);

           }

       // Increment the next Service Actvity to be created by as per the recurring frequency opted by the user

       activityStart.setDate(activityStart.getDate()+interval);

       activityEnd.setDate(activityEnd.getDate()+interval);

       }

    } // Code block Ends

    Some More Explanation

    ---------------------

    Please note in the above code snippet I am taking resources as user. So for that I have used in the code snippet

    "<resources>"+

           "<activityparty>"+

               "<partyobjecttypecode>systemuser</partyobjecttypecode>"+

               "<partyid>"+resourceId+"</partyid>"+

               "<participationtypemask>1</participationtypemask>"+

           "</activityparty>"+

       "</resources>"+

    If you are considering equipments as your resources then use

      "<partyobjecttypecode>systemuser</partyobjecttypecode>"+

    Also note that if you want to accept multiple resources then you need to change the value in the "participationtypemask" node.

    Since I am expecting 1 resource so I have kept the value as 1.

    Let me know if this helps.

    Regards,

    Sarbashish Bhattacharjee

Page 1 of 1 (10 items)
Leave a Comment
  • Please add 4 and 6 and type the answer here:
  • Post