By default, we can only specify the contact associated with the customer for the case record. If we try specifying a contact record that is not associated with the Customer selected, we will get the below error message.
Invalid Contact – The specified contact doesn’t belong to the account selected as the customer. Specify a contact that belongs to the selected account, and then try again.
Also, CRM will filter the lookup to show only the associated contact record
However, if we do not want this validation, we can remove it using the environment variable.
Search for Customer Entities Relationship For Incidents / msdyn_IncidentShouldValidatePrimaryContact
Set the new value as 0.
Now we will be able to save the record using an unassociated contact.
With the Associated Grid Control we can configure and show up four subgrids in the form, making it a more intuitive and cleaner form layout, with ease of navigation.
Below we are adding the Associated Grid Control component to the Contact’s form.
Here we have specified the Subgrid Tables and Views to begin with.
For the 1st subgrid specified we can set the specific options from the properties pane. Here let us try checking the Show related rows properties for the cases subgrid.
On doing so, we get the below message.
It basically reset the 1st subgrid’s table to Accounts from Case, the rest of the configuration remains unchanged.
Let us change it back to Cases and select the appropriate view.
Let us save and publish the change.
Now in the Contact form we can see the grid control with 4 different subgrids as configured.
The Active Cases shows the associated cases.
However, that is not the case with other subgrids as they show all the records.
To filter it to show the associated records we need to specify the relationship name while configuring the component.
Let us edit the component.
Below we specified the relationship name for the subgrid 2 i.e Accounts.
As expected, this time we can see Active Accounts filtered to show only those account records where the contact is associated as the primary contact.
To enable the new lead experience, navigate to App Settings >> Lead + opportunity management in the Sales Hub App.
On enabling it, we get the option to specify whether the Account, Contact, and Opportunity records are created automatically or the user (seller) has the option to specify which record to create.
We also see the option to Copilot summarizing the case once a lead is qualified.
For opportunity, if we select Seller, we get an additional option to modify the Opportunity form by adding or removing fields and also the option to allow a maximum of 5 new opportunities during lead qualification.
Clicking on the Add or remove fields link opens the Opportunity qualify lead form for customization.
Let us see it in action.
On clicking the Qualify command, we can see the new Qualify lead dialog opened in the side pane.
It gives us the option to create a new Account, Contact, and/or Opportunity record.
For Account and Contact, we can specify an existing record or select none if we do not want the Account and Contact record to be created.
For opportunity, we can click on edit icon to update the details of the opportunity record to be created. It will open the Opportunity qualify lead form.
Similarly clicking on + New opportunity allows us to specify a value for the new opportunity record.
As we had selected the option of a maximum of 5 opportunities, we got the below message on trying to specify the 6th opportunity record.
“You can’t add more than 5 opportunities.”
Let us click on Qualify.
We can see below details, the link to the records generated, and the summary.
Clicking on Finish opens the 1st opportunity record. We can also see the summary added to it.
On setting all the options to Automatic,
we can see the below details in the Qualify lead dialog, all as read-only information.
Using the SuppressCallbackRegistrationExpanderJob optional parameter in our Request object, we can bypass the execution of the flows registered against that particular event/trigger.
We have a flow registered on the update event of the lead record.
Now let us create a lead record through SDK.
We can see our flow triggered.
Now let us add the optional parameter SuppressCallbackRegistrationExpanderJob and check
This time as expected our lead record was created but the flow was not triggered.
Below is our sample record for a custom table custom_contract for which we will be creating the folder in SharePoint and associated it with this record using the SharePoint Document Location record. We’d use the same naming pattern that CRM uses i.e. Name + ”_” + ”GUID”
Below we have passed the details of the record along with CrmServiceClient and SharePoint’s client context.
The below code will first check if there is already a SharePoint Document Location created by CRM using ends with GUID condition. If it doesn’t exist, we are using the name field plus GUID of the record to create the folder in SharePoint and then associate using SharePoint Document Location record. Also we have used Regex to make sure we replace the special characters with “-“ similar to what CRM does. We are also checking if the Folder exists with that name in SharePoint before creating one.
Below is the GUID / record we have specified while creating the SharePoint Document Location record, the SharePoint Document location configured for that particular table.
The sample code –
var authManager = new AuthenticationManager(applicationId, certPath, password, domain);
ClientContext clientContext = authManager.GetContext(siteUrl);
var serviceClient = new CrmServiceClient(connection);
if (serviceClient.IsReady == true)
{
var myCRMRecord = serviceClient.Retrieve("custom_contract",
new Guid("f655304e-045b-ef11-bfe2-000d3a9b4e06"), new ColumnSet("custom_name"));
CheckIfShareDocLocationExistElseCreateandAssociate(myCRMRecord, serviceClient, clientContext);
}
private static void CheckIfShareDocLocationExistElseCreateandAssociate(Entity myCrmRecord, CrmServiceClient serviceClient, ClientContext clientContext)
{
var querySharePointDocumentLocation = new QueryExpression("sharepointdocumentlocation");
querySharePointDocumentLocation.ColumnSet.AddColumns("name", "relativeurl", "parentsiteorlocation", "regardingobjectid");
querySharePointDocumentLocation.Criteria.AddCondition("regardingobjectid", ConditionOperator.Equal, myCrmRecord.Id);
querySharePointDocumentLocation.Criteria.AddCondition("relativeurl", ConditionOperator.EndsWith, myCrmRecord.Id);
var result = serviceClient.RetrieveMultiple(querySharePointDocumentLocation);
if (result.Entities.Count == 0)
{
var folderName = myCrmRecord.Attributes["custom_name"].ToString();
// this makes sure we replace the special characters with - similar to what CRM does when autocreating the record.
string regexPattern = @"[~#%&*:<>?/\\{|}.""-]";
string replacement = "-";
folderName = Regex.Replace(folderName, regexPattern, replacement) + "_" + myCrmRecord.Id.ToString().ToUpper();
var folderRelativeURL = "custom_contract/" + folderName;
Web currentWeb = clientContext.Web;
var folderExists = currentWeb.DoesFolderExists(folderRelativeURL);
if (!folderExists)
{
var list = clientContext.Web.Lists.GetByTitle("Documents");
list.RootFolder.Folders.Add(folderRelativeURL);
clientContext.ExecuteQuery();
}
var documentLocation = new Entity("sharepointdocumentlocation");
documentLocation["name"] = "Documents on Default Site 1";
documentLocation["relativeurl"] = folderName;
// specifying the GUID of the SharePoint Document Location configured for that table
documentLocation["parentsiteorlocation"] = new EntityReference("sharepointdocumentlocation", new Guid("cf97e6d8-0b4e-ef11-a317-6045bdd74f46"));
documentLocation["regardingobjectid"] = new EntityReference("custom_contract", myCrmRecord.Id);
serviceClient.Create(documentLocation);
}
}