One thing I love digging into with Microsoft Dynamics 365 is all the “special” actions. Although just about any entity type can be used with Create
, Update
, Retrieve
etc., there are a lot of other actions that do more specialised jobs, and QualifyLead
is a great one of these.
The standard case of qualifying a lead sets up the entities shown in the diagram above:
- Account record is created
- Contact record is created and linked to the new account
- Opportunity record is created and linked to the opportunity
- The lead is updated with references to the new account and contact in the
parentaccountid
andparentcustomerid
fields - The lead status is changed to Qualified
Mappings
You can see details of how the system copies information from fields in the lead to those in the new account/contact/opportunity by using the Mappings configuration on the relationship between the lead and the other entity. For example, go to Settings > Customization, click Customize the System, expand the Lead entity and go to 1:N Relationships, double-click on the opportunity_originating_lead
relationship, then click on Mappings in the left hand menu. Here you can select fields from the lead entity and the corresponding field in the opportunity entity. When the lead is qualified the system uses these mappings to copy information from the lead to the opportunity. You can repeat this process to change the mappings to the account and contact entities too.
Parameters
From a developer perspective you can qualify a lead by using the QualifyLead
action. There are two main documentation pages for it which are slightly contradictory. The QualifyLeadRequest page from the SDK documentation describes what each parameter is for, while the QualifyLead WebAPI action documentation does a better job at identifying which parameters are really required or not. Both of these sources list the main parameters:
CreateAccount
CreateContact
CreateOpportunity
As you might expect, you can set these to true or false to control whether or not the system will create each of those additional entities. If you don’t want to create an opportunity when qualifying your lead, simply set CreateOpportunity
to false.
Four more optional parameters control some of the details of the opportunity that the system creates:
OpportunityCurrencyId
OpportunityCustomerId
SourceCampaignId
ProcessInstanceId
Once the lead is qualified, the statecode
is changed to Qualified, and there is another parameter that controls what the statuscode
is changed to:
Status
More Parameters!
So far, so good. However, there are some (as far as I’ve found) undocumented details to the behaviour of this action. Because they’re undocumented I’d assume they’re liable to change without notice, though I’d be surprised if they did:
- Duplicate Detection. If creating the new account/contact/opportunity would create a duplicate that is identified by a duplicate detection rule, qualifying the lead will fail. You can avoid this if necessary by setting an additional parameter
SuppressDuplicateDetection
to true - Existing account & contact. If the
parentaccountid
orparentcontactid
fields are set on the lead before qualifying, regardless of whether theCreateAccount
andCreateContact
parameters are set to true, no new account or contact will be created. The system won’t make any changes to the existing account & contact by mapping across data from the lead into the existing records – it ignores those mappings entirely. - Blank company name. If the
companyname
field on the lead is blank, regardless of whether theCreateAccount
parameter is set to true, no new account will be created. Note that this doesn’t seem to be affected by what the mappings are between the lead and account entities – even if you maptelephone1
on the lead to name on the account, it’s still thecompanyname
field on the lead that controls this behaviour. If the lead contains data that only maps to the account record, you will effectively lose that data - Blank contact name. Similarly, if both the
firstname
andlastname
fields on the lead are blank, no contact will every be created, regardless of the mappings from lead to contact or what theCreateContact
parameter is set to.
Permissions
Because QualifyLead can touch 4 different entities, there is plenty of scope for it to fail with security errors. You might need to be able to create the account, contact and opportunity. Or if you’re using existing an account & contact you’ll need the AppendTo
privilege on those entity types. If you’re missing a required permission you won’t get a nice helpful error, just get the standard “Access Is Denied”. Download the log file to get detailed information on what privilege is missing, but it’s not easy to parse:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><s:Fault><faultcode>s:Client</faultcode><faultstring xml:lang="en-GB">SecLib::AccessCheckEx failed. Returned hr = -2147187962, ObjectID: 0b80cd6c-4114-e411-b9de-00155d00b203, OwnerId: cca04df7-0b02-e411-b9de-00155d00b203, OwnerIdType: 8 and CallingUser: 40097a3c-4b91-e611-80c5-00155d007101. ObjectTypeCode: 1, objectBusinessUnitId: d32d38d8-e501-e411-b9de-00155d00b203, AccessRights: AppendToAccess </faultstring><detail><OrganizationServiceFault xmlns="http://schemas.microsoft.com/xrm/2011/Contracts" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><ActivityId>dc3f52ee-8fbb-4b73-a79e-f1bf01ccd3bc</ActivityId><ErrorCode>-2147187962</ErrorCode><ErrorDetails xmlns:a="http://schemas.datacontract.org/2004/07/System.Collections.Generic"/><Message>SecLib::AccessCheckEx failed. Returned hr = -2147187962, ObjectID: 0b80cd6c-4114-e411-b9de-00155d00b203, OwnerId: cca04df7-0b02-e411-b9de-00155d00b203, OwnerIdType: 8 and CallingUser: 40097a3c-4b91-e611-80c5-00155d007101. ObjectTypeCode: 1, objectBusinessUnitId: d32d38d8-e501-e411-b9de-00155d00b203, AccessRights: AppendToAccess </Message><Timestamp>2019-03-20T16:30:23.176178Z</Timestamp><ExceptionRetriable>false</ExceptionRetriable><ExceptionSource i:nil="true"/><InnerFault><ActivityId>dc3f52ee-8fbb-4b73-a79e-f1bf01ccd3bc</ActivityId><ErrorCode>-2147187962</ErrorCode><ErrorDetails xmlns:a="http://schemas.datacontract.org/2004/07/System.Collections.Generic"/><Message>SecLib::AccessCheckEx failed. Returned hr = -2147187962, ObjectID: 0b80cd6c-4114-e411-b9de-00155d00b203, OwnerId: cca04df7-0b02-e411-b9de-00155d00b203, OwnerIdType: 8 and CallingUser: 40097a3c-4b91-e611-80c5-00155d007101. ObjectTypeCode: 1, objectBusinessUnitId: d32d38d8-e501-e411-b9de-00155d00b203, AccessRights: AppendToAccess </Message><Timestamp>2019-03-20T16:30:23.176178Z</Timestamp><ExceptionRetriable>false</ExceptionRetriable><ExceptionSource i:nil="true"/><InnerFault i:nil="true"/><OriginalException i:nil="true"/><TraceText i:nil="true"/></InnerFault><OriginalException i:nil="true"/><TraceText i:nil="true"/></OrganizationServiceFault></detail></s:Fault></s:Body></s:Envelope>
Not too helpful at first glance, but all the information you need is in there:
ObjectID
: the GUID of the record that you don’t have a required privilege onObjectTypeCode
: the type of the record. Unfortunately the error message presents this as an object type code rather than logical name, which I find much easier to work with. You can use tools such as Metadata Browser in XrmToolBox to find this, or there are websites that list the type codes for standard entitiesOwnerID
: the GUID of the user or team that owns that objectOwnerIdType
: the type of record that owns the object. This will be either8
for a user or9
for a teamCallingUser
: the GUID of the user that is making the request and missing the privilegeAccessRights
: the exact type of privilege that the user is missing on the record
Put this information together and you can work out either the additional security role the user might need, or the change to the existing security role to implement. Of course, you might then get what looks like the same error again but with subtly different values which will point you to the next privilege you need to add.
One thought on “MSDyn365 Internals: QualifyLead”