CRM 2011/2013 Dialog’s and Custom Workflow example

I had piece of functionality I did at work recently and it had a few interesting parts, so I thought I would blog it down.  You can read the blog or watch the video

The required functionality required the ability to update multiple appointments, in this deployment we have created a new appointment entity called Client appointment but basically I needed to update multiple appointments.

The user wanted to specify a cancellation reason.

I was initial thinking of using a workflow and selecting lots of appointments but because they needed to specify a cancellation reason then this wasn’t an option

With input required a dialog seemed like a good option.

The problem I quickly found was Dialog’s can only be triggered on one record at a time and not multiple records like workflows.

Contact Dialog

So the plan was changed to be run on the contact.  I would have a dialog which ran on the contact entity the user would be prompted for a start date, end date and a cancellation reason.

I would call a Custom Workflow from with the dialog and pass selected values to it and then the custom workflow would do a query, select all the appointments, cancel them and then create a reschedule task and assign it to a reschedule queue.

I usually trigger code changes from plugins and wondered what circumstances would I move that code to be triggered into a Custom Workflow but this a good example.

This code isn’t really suitable to be triggered from a plugin because there isn’t anything being changed/updated or created so there is no natural plugin trigger point.

Thinking about this I can see that within certain workflows and dialog’s is the perfect place to call a custom workflow, particularly when nothing has been updated or modified and you want to trigger those changes and the changes are going to be complex which needs a code to do.  This is a good example because the code needs to retrieve a lot of appointments linked to a contact, this functionality couldn’t be achieved in workflow or definitely not easily.

After re-reading the requirements I noticed we needed to specify a start time and end time and this would also work in a slightly unusual manner.  If the user specified 08:00 to 12:00 with a start date and end date which went over more than one day.  The user wanted the morning appointments to be cancelled for each day specified but the afternoon appointments to be left alone.  This would provide a tricky piece of querying which I will go into later.

In theory this seemed like it would work, so I initially created a skeleton Dialog and a Custom workflow which logged the fields.

 

The first little problem I had was, how to show the time.  The choices I had were text single response, this would involve the user typing in the time, I didn’t like this option because it wasn’t easy for the user and had the potential for them to mistype.

The other option was to create an optionset with an hour value for each hour.  e.g. 01:00, 02:00 etc.  In then end I chose this option.

The user also wanted the ability to not select a time and this would mean they wanted to cancel the whole days appointments.

So I created the the optionSet with a default value of – Full Day and then 01:00 label would have the value of 1, 23:00 would have the value of 23.

 

dialog example

SET THE CORRECT DATA TYPE

One mistake I made at this point was to incorrectly set the Data type to text.  This is a real schoolboy error because once you set this you can’t change it.  So I had set it to text gone through adding all 24 hours and then realizing and had to do it all again, very frustrating.

What is also very frustrating is the fact you can use an optionSet created in CRM, you have to copy the values again.  This means there isn’t any schronisation between the OptionSet in your dialog and entity.  It also means you may have to create the values numerous times in a dialog because you can’t reuse it in a dialog.

In this example I had to create the same OptionSet for start date and end date.  Tedious indeed.

dialog example 1

 

The cancellation reason was the same kind of thing, I had to copy an OptionSet inside CRM and recreate it and give the OptionSet values the same as the values in the CRM OptionSet field, this way I could pass the value into the Custom Workflow and then it could use the value to set the OptionSet.

 

Warning Popup Message

When I did a demo of the functionality to the client, they said they would like a warning to pop up, if the dates specified were within 3 days.  The reason for this is if you cancel appointments with 3 days of today, it’s difficult to reorganize these so users should be certain they want to cancel these appointments.

Sometimes when someone asks you about how to achieve some functionality in CRM, you can be momentary stumped and can’t see how to do it and this was one of those moments.

I couldn’t see anyway to pop up a warning message.

It was only later that I was talking through the problem with the team that the obvious answer came to light.

Dialog’s are made up of pages and on each page you have a one or more prompt and responses.

So if I wanted a warning message all I needed to do was to create a new page, show the information the user had entered earlier in the dialog and ask them to confirm if they wanted to proceed with the changes using an OptionSet with Yes/No.

 

What I did was to check if the start date was greater than three days, if so put a confirmation page but if the start date was less than three days I would add the words warning.

dialog example 2

 

Sensible variable names

This is the same for workflows but when you are adding conditions or adding steps/prompt and responses make sure you use easily understood variable names because later you will need to use these fields to update an entity/pass to a custom workflow so you need to be able to easily understand what each field is.

Here is the Custom Workflow being called from within the dialog

dialog example 3

 

Below you can see the main code for the Custom Workflow, you can match it up with the screen shot above.  The actual code is in this class commonClientAppointment class

 


public sealed class CwaBulkAppointmentStatusChange : CodeActivity
{

/// <summary>
/// Gets or sets Min Wait Time
/// </summary>
[Input("Start Date")]
[RequiredArgument]
public InArgument<DateTime> StartDate { get; set; }

/// <summary>
/// Gets or sets Max Wait Time
/// </summary>
[Input("End Date")]
[RequiredArgument]
public InArgument<DateTime> EndDate { get; set; }

/// <summary>
/// Gets or sets Max Wait Time
/// </summary>
[Input("Start Time")]
[RequiredArgument]
public InArgument<int> StartTime { get; set; }

/// <summary>
/// Gets or sets Max Wait Time
/// </summary>
[Input("EndTime")]
[RequiredArgument]
public InArgument<int> EndTime { get; set; }

/// <summary>
/// Gets or sets Max Wait Time
/// </summary>
[Input("Cancellation Reason")]
[RequiredArgument]
public InArgument<int> CancellationReason { get; set; }

/// <summary>
/// Gets or sets Task Reference
/// </summary>
[Input("Contact")]
[ReferenceTarget("contact")]
public InArgument<EntityReference> Contact { get; set; }

/// <summary>
/// Executes the workflow activity.
/// </summary>
/// <param name="executionContext">The execution context.</param>
protected override void Execute(CodeActivityContext executionContext)
{
// Create the tracing service
ITracingService tracingService = executionContext.GetExtension<ITracingService>();

if (tracingService == null)
{
throw new InvalidPluginExecutionException("Failed to retrieve tracing service.");
}

tracingService.Trace("Entered CwaBulkAppointmentStatusChange.Execute(), Activity Instance Id: {0}, Workflow Instance Id: {1}",
executionContext.ActivityInstanceId,
executionContext.WorkflowInstanceId);

// Create the context
IWorkflowContext context = executionContext.GetExtension<IWorkflowContext>();

if (context == null)
{
throw new InvalidPluginExecutionException("Failed to retrieve workflow context.");
}

tracingService.Trace("CwaBulkAppointmentStatusChange.Execute(), Correlation Id: {0}, Initiating User: {1}",
context.CorrelationId,
context.InitiatingUserId);

IOrganizationServiceFactory serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

try
{

DateTime startDate = this.StartDate.Get(executionContext).ToLocalTime();
DateTime endDate = this.EndDate.Get(executionContext).ToLocalTime();
int startTime = this.StartTime.Get(executionContext);
int endTime = this.EndTime.Get(executionContext);
int cancellationReason = this.CancellationReason.Get(executionContext);
EntityReference contactRef = this.Contact.Get(executionContext);

CommonClientAppointment commonClientAppointment = new CommonClientAppointment();
commonClientAppointment.CancelHPAppointments(service, contactRef, startDate, endDate, startTime, endTime, cancellationReason);

}
catch (FaultException<OrganizationServiceFault> e)
{
tracingService.Trace("Exception: {0}", e.ToString());
}

tracingService.Trace("Exiting CwaBulkAppointmentStatusChange.Execute(), Correlation Id: {0}", context.CorrelationId);
}
}

 

Other interesting bits of code, below I set the start date to add the hours selected in the start time, unless it’s value is zero (which means whole day), which I then use the default time 00:00 and for enddate I give the value of 23:59 (e.g. the whole day)


if (startTime > 0)
{
startDate = startDate.Date;
startDate = startDate.AddHours(startTime);
}
else
{
startDate = startDate.Date;
}

if (endTime > 0)
{
endDate = endDate.Date;
endDate = endDate.AddHours(endTime);
}
else
{
endDate = endDate.Date;
endDate = endDate.AddHours(23).AddMinutes(59);
}

 

The other tricky bit of code was time specified code.  To recap if a time was specified then the appointments between the time should be cancelled on every day specified in the date criteria.  To do this I calculated the difference in days and then looped around that many times and added a day each time I went round  the loop.

e.g.

start date 01/01/2014

end date 07/01/2014

start time 07:00

end time 12:00

 

This would cancel all morning appointments for all the days (01, 02, 03, 04, 05 , 06, 07)

I had two options, I could either create one query with multiple date conditions or I could create one query and then repeatedly call this with the start and end date changed.

I couldn’t quite invisage doing one query so I called the query once for each day in the date range.

List<Entity> bookedAppointments = new List<Entity>();
if (startTime == 0 || endTime == 0)
{
//full days
bookedAppointments = CancelAppointmentsRetrieve(service, hpEntityRef, startDate, endDate, statusCode);
}
else
{
//time specified
int days = calculateDays(startDate, endDate);
int counter = 0;
DateTime loopEndDate = startDate.Date.AddHours(endDate.Hour);
while (counter <= days)
{
bookedAppointments.AddRange(CancelAppointmentsRetrieve(service, hpEntityRef, startDate, loopEndDate, statusCode));
startDate = startDate.AddDays(1);
loopEndDate = loopEndDate.AddDays(1);
counter++;
}
}

if (bookedAppointments != null && bookedAppointments.Count > 0)
{
this.UpdateBookedAppointments(service, bookedAppointments, cancelReasonInt);
}

&nbsp;
<pre>

 

 

 

Advertisements

8 thoughts on “CRM 2011/2013 Dialog’s and Custom Workflow example

  1. Andy May 14, 2014 / 6:17 pm

    Hi Ben
    Looks like a great solution. You don’t have midnight in your options list though?

    Like

    • Hosk May 14, 2014 / 10:28 pm

      good spot, although appointments don’t really go to midnight so I will hopefully get away with it

      Like

      • Abhijit August 25, 2015 / 7:07 am

        Hi Hosk,
        Can you favor me with step by step process to create custom workflow using Devp Toolkit for Dynamics Online 2015

        Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s