Dynamics CRM/Dynamics 365 for Customer Engagement (CRM/D365CE) is an incredibly flexible application for the most part. Regardless of how your business operates, you can generally tailor the system to suit your requirements and extend it to your heart’s content; often to the point where it is completely unrecognisable from the base application. Notwithstanding this argument, you will come across aspects of the application that are (literally) hard-coded to behave a certain way and cannot be straightforwardly overridden via the application interface. The most recognisable example of this is the Lead Qualification process. You are heavily restricted in how this piece of functionality acts by default but, thankfully, there are ways in which it can be modified if you are comfortable working with C#, JScript and Ribbon development.
Before we can start to look at options for tailoring the Lead Qualification process, it is important to understand what occurs during the default action within the application. In developer-speak, this is generally referred to as the QualifyLead message and most typically executes when you click the button below on the Lead form:
When called by default, the following occurs:
- The Status/Status Reason of the Lead is changed to Qualified, making the record inactive and read-only.
- A new Opportunity, Contact and Account record is created and populated with (some) of the details entered on the Lead record. For example, the Contact record will have a First Name/Last Name value supplied on the preceding Lead record.
- You are automatically redirected to the newly created Opportunity record.
This is all well and good if you are able to map your existing business processes to the application, but most organisations will typically differ from the applications B2B orientated focus. For example, if you are working within a B2C business process, creating an Account record may not make sense, given that this is typically used to represent a company/organisation. Or, conversely, you may want to jump straight from a Lead to a Quote record. Both of these scenarios would require bespoke development to accommodate currently within CRM/D365CE. This can be broadly categorised into two distinct pieces of work:
- Modify the QualifyLead message during its execution to force the desired record creation behaviour.
- Implement client-side logic to ensure that the user is redirected to the appropriate record after qualification.
The remaining sections of this post will demonstrate how you can go about achieving the above requirements in two different ways.
Our first step is to “intercept” the QualifyLead message at runtime and inject our own custom business logic instead
I have seen a few ways that this can be done. One way, demonstrated here by the always helpful Jason Lattimer, involves creating a custom JScript function and a button on the form to execute your desired logic. As part of this code, you can then specify your record creation preferences. A nice and effective solution, but one in its guise above will soon obsolete as a result of the SOAP endpoint deprecation. An alternative way is to instead deploy a simplistic C# plugin class that ensures your custom logic is obeyed across the application, and not just when you are working from within the Lead form (e.g. you could have a custom application that qualifies leads using the SDK). Heres how the code would look in practice:
public void Execute(IServiceProvider serviceProvider)
{
//Obtain the execution context from the service provider.
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
if (context.MessageName != "QualifyLead")
return;
//Get a reference to the Organization service.
IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = factory.CreateOrganizationService(context.UserId);
//Extract the tracing service for use in debugging sandboxed plug-ins
ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
tracingService.Trace("Input parameters before:");
foreach (var item in context.InputParameters)
{
tracingService.Trace("{0}: {1}", item.Key, item.Value);
}
//Modify the below input parameters to suit your requirements.
//In this example, only a Contact record will be created
context.InputParameters["CreateContact"] = true;
context.InputParameters["CreateAccount"] = false;
context.InputParameters["CreateOpportunity"] = false;
tracingService.Trace("Input parameters after:");
foreach (var item in context.InputParameters)
{
tracingService.Trace("{0}: {1}", item.Key, item.Value);
}
}
To work correctly, you will need to ensure this is deployed out on the Pre-Operation stage, as by the time the message reaches the Post-Operation stage, you will be too late to modify the QualifyLead message.
The next challenge is to handle the redirect to your record of choice after Lead qualification
Jason’s code above handles this effectively, with a redirect after the QualifyLead request has completed successfully to the newly created Account (which can be tweaked to redirect to the Contact instead). The downside of the plugin approach is that this functionality is not supported. So, if you choose to disable the creation of an Opportunity record and then press the Qualify Lead button…nothing will happen. The record will qualify successfully (which you can confirm by refreshing the form) but you will then have to manually navigate to the record(s) that have been created.
The only way around this with the plugin approach is to look at implementing a similar solution to the above - a Web API request to retrieve your newly created Contact/Account record and then perform the necessary redirect to your chosen entity form:
function redirectOnQualify() {
setTimeout(function(){
var leadID = Xrm.Page.data.entity.getId();
leadID = leadID.replace("{", "");
leadID = leadID.replace("}", "");
var req = new XMLHttpRequest();
req.open("GET", Xrm.Page.context.getClientUrl() + "/api/data/v8.0/leads(" + leadID + ")?$select=_parentaccountid_value,_parentcontactid_value", true);
req.setRequestHeader("OData-MaxVersion", "4.0");
req.setRequestHeader("OData-Version", "4.0");
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
req.setRequestHeader("Prefer", "odata.include-annotations=\"OData.Community.Display.V1.FormattedValue\"");
req.onreadystatechange = function () {
if (this.readyState === 4) {
req.onreadystatechange = null;
if (this.status === 200) {
var result = JSON.parse(this.response);
//Uncomment based on which record you which to redirect to.
//Currently, this will redirect to the newly created Account record
var accountID = result["_parentaccountid_value"];
Xrm.Utility.openEntityForm('account', accountID);
//var contactID = result["_parentcontactid_value"];
//Xrm.Utility.openEntityForm('contact', contactID);
}
else {
alert(this.statusText);
}
}
};
req.send();
}, 6000);
}
The code is set to execute the Web API call 6 seconds after the function triggers. This is to ensure adequate time for the QualifyLead request to finish and make the fields we need available for accessing.
To deploy out, we use the eternally useful Ribbon Workbench to access the existing Qualify Lead button and add on a custom command that will fire alongside the default one:
As this post has hopefully demonstrated, overcoming challenges within CRM/D365CE can often result in different - but no less preferred - approaches to achieve your desired outcome. Let me know in the comments below if you have found any other ways of modifying the default Lead Qualification process within the application.