Dynamics 365 For Sales: How to merge Accounts if the subordinate record is associated with one or more active quotes?

Merging records on Dynamics 365 CRM is a very powerful feature that prevents duplicate records. I was asked to study this feature for a client who wanted to clean up his customer records, and we reached a point where we had to merge accounts that were related to active quotes. If you didn’t know it, Dynamics 365 For Sales disallows this operation by showing this error message:

After some investigations, I realized that an internal plugin of the Sales module prevents this operation. Actually, it is about the “Microsoft.Dynamics.Sales.Plugins.PreOperationAccountMerge” plugin.

This plugin is internal to Dynamics 365. I could not find out more from the default solution or from the Plugin registration tool. But it’s ok, everything can be shown on the “sdkmessageprocessingstep” table. I used the following fetchXml to see more details:

<fetch>
  <entity name="sdkmessageprocessingstep" >
    <link-entity name="plugintype" from="plugintypeid" to="eventhandler" >
      <filter>
        <condition attribute="name" operator="eq" value="Microsoft.Dynamics.Sales.Plugins.PreOperationAccountMerge" />
      </filter>
    </link-entity>
  </entity>
</fetch>

According to the result, I noticed that this plugin is running in pre-operation in order 1 for the Merge message:

Solution proposal

So I tried to trick the merge validation plugin by using a new plugin that will run first, before the validation plugin. Its logic involves moving the related active offer from the subaccount to the Master account.

With this approach, the standard plugin will not find any active quotes on the subaccount. So the error will not be generated. Active quotes will be linked to the Master account, the merge action will then move the rest of the non-active quotes, and everyone will be happy!

This is possible by registering the new plugin with the following configuration:

  • Message: Merge
  • Event Pipeline Stage of Execution: PreOperation
  • Execution Order: 0

As you can see, the plugin is executed on behalf of a specific user, not the calling user. Indeed, the plugin needs to update quotes that are active. This is only possible using the System Administrator security role only. You can see more details in the following documentation: Dynamics 365 Sales & GDPR

The code for the used plugin is as follows, please refer to the comments for more details.

// <copyright file="PreOperationAccountMerge.cs" company="">
// Copyright (c) 2021 All Rights Reserved
// </copyright>
// <author></author>
// <date>12/17/2021 6:04:07 PM</date>
// <summary>Implements the PreOperationAccountMerge Plugin.</summary>
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.1
// </auto-generated>
using System;
using System.ServiceModel;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
namespace MergePoc.Plugins
{
/// <summary>
/// PreOperationAccountMerge Plugin.
/// </summary>
public class PreOperationAccountMerge: PluginBase
{
/// <summary>
/// Initializes a new instance of the <see cref="PreOperationAccountMerge"/> class.
/// </summary>
/// <param name="unsecure">Contains public (unsecured) configuration information.</param>
/// <param name="secure">Contains non-public (secured) configuration information.
/// When using Microsoft Dynamics 365 for Outlook with Offline Access,
/// the secure string is not passed to a plug-in that executes while the client is offline.</param>
public PreOperationAccountMerge(string unsecure, string secure)
: base(typeof(PreOperationAccountMerge))
{
// TODO: Implement your custom configuration handling.
}
/// <summary>
/// Main entry point for he business logic that the plug-in is to execute.
/// </summary>
/// <param name="localContext">The <see cref="LocalPluginContext"/> which contains the
/// <see cref="IPluginExecutionContext"/>,
/// <see cref="IOrganizationService"/>
/// and <see cref="ITracingService"/>
/// </param>
/// <remarks>
/// For improved performance, Microsoft Dynamics 365 caches plug-in instances.
/// The plug-in's Execute method should be written to be stateless as the constructor
/// is not called for every invocation of the plug-in. Also, multiple system threads
/// could execute the plug-in at the same time. All per invocation state information
/// is stored in the context. This means that you should not use global variables in plug-ins.
/// </remarks>
protected override void ExecuteCdsPlugin(ILocalPluginContext localContext)
{
if (localContext == null)
{
throw new InvalidPluginExecutionException(nameof(localContext));
}
// Obtain the tracing service
ITracingService tracingService = localContext.TracingService;
try
{
// Obtain the execution context from the service provider.
IPluginExecutionContext context = (IPluginExecutionContext)localContext.PluginExecutionContext;
// Obtain the organization service reference for web service calls.
IOrganizationService currentUserService = localContext.CurrentUserService;
if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is EntityReference)
{
EntityReference entityReferenceMaster = (EntityReference)context.InputParameters["Target"];
Guid GuidSubOrdinate = (Guid)context.InputParameters["SubordinateId"];
//Define Condition Values
//active quotes
var query_statecode = 1;
//related to the subAccount
var query_msdyn_account = GuidSubOrdinate;
//Instantiate QueryExpression query
var query = new QueryExpression("quote");
//!Add all columns to query.ColumnSet
query.ColumnSet.AllColumns = false;
// Define filter query.Criteria
query.Criteria.AddCondition("statecode", ConditionOperator.Equal, query_statecode);
query.Criteria.AddCondition("msdyn_account", ConditionOperator.Equal, query_msdyn_account);
var quotes = currentUserService.RetrieveMultiple(query);
foreach (var quote in quotes.Entities)
{
//move accounts from the subAccount to the Master Account
quote["msdyn_account"] = new EntityReference("account", entityReferenceMaster.Id);
quote["customerid"] = new EntityReference("account", entityReferenceMaster.Id);
//Update Before the merge validation
currentUserService.Update(quote);
}
}
}
// Only throw an InvalidPluginExecutionException. Please Refer https://go.microsoft.com/fwlink/?linkid=2153829.
catch (Exception ex)
{
tracingService?.Trace("An error occurred executing Plugin MergePoc.Plugins.PreValidationaccountMerge : {0}", ex.ToString());
throw new InvalidPluginExecutionException("An error occurred executing Plugin MergePoc.Plugins.PreValidationaccountMerge .", ex);
}
}
}
}

Note that I use Power Platform Tools to generate and deploy the plugin… Hope it helps…

5 thoughts on “Dynamics 365 For Sales: How to merge Accounts if the subordinate record is associated with one or more active quotes?

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 )

Connecting to %s