Dynamics CRM: Disabling the selection of contacts for opportunities all the way

I recently got a requirement for a Dynamics CRM implementation that all recorded sales should follow the B2B (business to business) and therefore, opportunities should only able to be related to accounts, and not contacts as well.

This is a common scenario which can be easily covered with some simple JScript in the opportunity form. However I found out that in some circumstances, an opportunity could still be created for a contact, thus violating the requirement and even worse, breaking some of the implemented processes and business logic. Here is how I fixed this issue.

Background

In a default Dynamics CRM implementation, customers can either be accounts or contacts. When creating a record that can be bound to either an account or contact such as an opportunity, the customer field is bound to a virtual composite entity where either an account or a contact is valid. Thinking from a database perspective, there is no customerid column in the opportunities table, because there is no customer entity on the system. There is, however, an accountid column and a contactid column. When the record is saved, either accountid or customerid will be filled with a link to the respective record, depending whether if the user specified an account, or a contact.

A simple JScript?

A simple way to avoid users from selecting a contact as a potential customer for opportunities is through the use of a JScript bound the form’s onLoad event. Here is an example:

function FilteredCustomerLookupAccount(customerField) {
var CRM_FORM_TYPE = Xrm.Page.ui.getFormType();
if (CRM_FORM_TYPE == 4 || CRM_FORM_TYPE == 3) { return; }

//Limit the entity lookup choice to Account
document.getElementById(customerField).setAttribute("lookuptypes", "1");
}

The above example can be used on any form that has a lookup to the customer entity, such as the default forms for both the Case and Opportunity entities. Make sure to pass the name of the customer field between quotes as a parameter when calling the function. In the case of the Opportunity’s form, you should specify 'customerid'.

This should do the trick, right? Well, yes; if opportunity records are being created and edited through the form that contains such JScript. But this script takes no effect if an opportunity record is created through another context that would bypass the JScript. For example, if the user creates a record using the Mobile Express forms, or a third party client or solution that creates the record through the web service of Dynamics CRM, the JScript would never be called.

What is worse, even if you have disabled Mobile Express forms and no one uses a third party client to create records in Dynamics CRM, there are still cases where the Jscript above could be bypassed, such as activities that are converted into opportunities.

Update: Don’t forget the Opportunities link in the Contact’s form
As Adam V mentioned in the comments section below, make sure that you remove the link to Opportunities from the left navigation pane of the Contact forms. Since any CRM platform is all about relationships between different entities, it is important to consider both ends of the relationship tether when performing such type of customisations.

Activities: The agent provocateur

When a user converts an activity to an opportunity, the record specified in the Recipient field of the activity will become the Potential Customer of the opportunity that is created. Therefore if the user specified a contact as the recipient of the activity, the opportunity will be bound to the contact. This is because the opportunity record is created and then saved when the user converts the activity to an opportunity, thus bypassing the JScript to disallow contacts.

Solution – part I: Workflow

The first part of my solution involves a workflow. In this Dynamics CRM implementation, customised the solution so a contact must be bound to a parent account (parent field is mandatory), and it must be an account. That is, the above JScript used to block the selection of contacts is also bound to the onLoad of my Contact form.

So what I did is to create a workflow for the Opportunity entity that checks for the condition Opportunity:Potential Customer equals [Potential Customer (Contact):Contact] (that is, if the opportunity is bound to a contact). If it is bound to a contact, then it will change the customer from the current contact to {Parent Customer(Potential Customer (Contact))}, which is effectively the contact’s parent account.

Because an opportunity record created outside the scope of its form will bypass all JScripts, it not only bypasses the blocking of contact selection, but also any other business logic I might have in any other JScript. Therefore my workflow also checks for empty fields that shouldn’t be empty, and set some default values.

Solution – part II: JScript

The problem with workflows is that although they are useful, they are an asynchronous, background process. Therefore the user wouldn’t get any sort of feedback of what is going on. In order to resolve this, I added a second JScript to the onLoad event of my Opportunity form, as follows:

function setOpAccountPerContact() {

 var CRM_FORM_TYPE = Xrm.Page.ui.getFormType();
 if (CRM_FORM_TYPE != 2) { return; }

 if(Xrm.Page.getAttribute("customerid").getValue()!=null) {
 if(Xrm.Page.getAttribute("customerid").getValue()[0].typename=="contact") {
 alert ("This opportunity is bound to a contact. The relationship will automatically change for the contact's parent account.");

 var idCustomer = Xrm.Page.getAttribute("customerid").getValue()[0].id;
 var retrieveRecordsReq = new XMLHttpRequest();
 var ODataPath = Xrm.Page.context.getServerUrl() + "/XRMServices/2011/OrganizationData.svc";

 retrieveRecordsReq.open('GET', ODataPath+"/ContactSet(guid'" + idCustomer + "')",false);
 retrieveRecordsReq.setRequestHeader("Accept", "application/json");
 retrieveRecordsReq.setRequestHeader("Content-Type", "application/json; charset=utf-8");
 retrieveRecordsReq.send();

 var records = this.parent.JSON.parse(retrieveRecordsReq.responseText).d;

 var lookupValue = new Array();
 lookupValue[0] = new Object();
 lookupValue[0].id = records.ParentCustomerId.Id;
 lookupValue[0].name = records.ParentCustomerId.Name;
 lookupValue[0].entityType = "account";
 Xrm.Page.getAttribute("customerid").setValue(lookupValue); 

 }
 }
}

The above script will check if the opportunity is bound to a contact as the potential customer. If that is the case, it will inform the user through an alert, and it will proceed to change the Potential Customer field from the current contact to the contact’s parent account.

Wrapping up

When users convert an account to an opportunity for a contact and the option to open the new opportunity is selected, the users will receive an alert, and they will see the Potential Customer field change its value to the related account record. Even if the user doesn’t save the change performed by the JScript, the workflow will replace the Potential Customer value with the related account.

The JScript provides users with the visual, client-side aid, while the workflow is our catch-all in the lookout for any discrepancy in the system.

There is no way I could have done this solution without the help of Mahender Pal, a Dynamics MVP from India who helped me out with the JScript part of the solution. Thank you Mahender!


9 Comments

  • A great little bundle of bits to form a complete solution to a much-requested situation.

    Another thing that can help here:

    Remove “Opportunities” from the left navigation of the Contact form. Otherwise, users can click here, get an (empty) associated view, but they will have very tempting buttons to create a new Opportunity, which will inherit the customer = contact. (Your second JScript will get over this but why make it work harder than it needs to?)

    And for a bit of “Value add”, filter the Contact lookup to only show Contacts whose Parent Account is the one already chosen. This is great in the lookup dialogue and even greater when you type a partial name, even a single letter sometimes and hit tab and BINGO! the name is filled in completely for you (normally you would get a choice of all the names matching “Jo…” etc, but because the filter applies to automatic name resolution too, it has much shorter list, and often as short as one unique result which it can use without asking.

    To correct something about the fields involved here: In the database, the Opportunity table does have a single lookup field to Parent Customer (contact OR account) and a second field to specify the customer type (1 for Account, 2 for Contact, standard ETC values). It actually has a third field for the customer name which is odd since that is non-normalised, This may exist to help with integration to other Dynamics platforms, or just to simplify reporting, I don’t know (of course any reporting should use the filtered views for best practice and obeying the security model).

    What you describe with a separate field each for Account lookup and Contact lookup was (I think) correct in CRM 4.0 for Opportunities, as well as things like Account > Parent and Contact > Parent. As far as I know, these are all now replaced by the method described above.

    • Adam,

      Thanks for the comments!

      I totally took for granted the issue of Opportunities link in the contact's form. Great catch. It was one of the first things I did in the customisation I am working on so I guess I just forgot about it when writing this post.

      When you say to "filter the Contact lookup to only show Contacts whose Parent Account is the one already chosen", do you mean in the Account's form? Curiously in my Opportunity form I was requested to add a Contact field for the contact who the company would be dealing with regards to the opportunity. For this field I had added such filter. Also my workflow would copy the contact to this field before the workflow replaces the contact with its parent account in the Potential Customer field. I did cut this out of this post because I reckon that it would add unnecessary complexity to this tutorial.

      [EDIT: In Dynamics CRM 2013 and onward this is no longer necessary as it has one field for account and one field for contact by default. The customer will always be the account, that is unless there is only a contact specified and therefore the contact is the customer].

      As for how the fields work, I got this information from a Microsoft site, but it might as well be outdated. I do remember reading about it during my initial Dynamics CRM 4.0 training. I would like to update the text with the correct information. Any idea where I could fetch it from?

      All the best
      P.

  • Thanks for this! The JScript works great! But I have on question, is it possible to trigger it as soon as the field is changed? So as soon as the contact is selected, it should fire the script and grab its parent account. I tried calling the script in the OnChange event of the lookup field but nothing happens. Any ideas?

    Thanks again!
    Tudor

    • Got it figured out!! The reason it wasn't firing for me was because I was on FormType 1 and the if condition would return anytime it was any FormType other than 2. Works like a charm! Thanks a lot!

      • Hi Tudor,

        Glad you figured it out.

        Remember, you don't need the second script on FormType 1 because when creating a record, you already have the first JScript which doesn't allow users to select Contacts as Potential Clients in the first place (the first script above, under 'a simple JScript' header).

  • You can also create a new Account field, hide the customer field and link the two with a workflow

    • Hi Alec,

      You could do that BUT that adds a new field in the entity which could create confusion for users using advanced find. Also in many cases the customer field can't be removed from the form and is a required field, which requires us to hide the field (or hack the XML customisafion file to remove it) and making sure that the field is no longer business required. Also I have to say that this post was mostly an exercise in Javascript indulgence 😉

  • Greetings!
    Where does the above code go, and or where do we call the function setOpAccountPerContact?
    Thanks!

    • Hi,

      In this example all scripts go in the form for the Opportunity entity. Bear in mind that this article was wrote for Dynamics CRM 2011.

      Now for Dynamics CRM 2013 and onward, Microsoft changed the Opportunity for so you specify both the account and the contact. If only a contact is specified, then the contact is the customer. Otherwise the account will be specified as the customer regardless if a contact is also specified or not.