Avoid Hidden Mapping
Views (4)
Hello everyone !
I'm back from my holidays :)
For today's post I'll talk about hidden mappings.
For those which doesn't know what it is, I explain :
When you convert a quote to an order the quote products are copied to the order as order products. If you have customized the Quote Product and the Order Product entity adding a new field, this new field won't be filled during the conversion to an order.
In order to copy the field there is a solution commonly named hidden mapping, which allow you to set how custom fields will be copied from a quote product to and order product. It's the same process for the other convert actions : Opportunity to Quote, Quote to Order, Order to Invoice.
I will not explain the technique because it is not supported by Microsoft and I don't like to share unsupported ways to customize the CRM ;)
BUT ! I will show you another way to achieve this.
In the examples below, I'll take the conversion of a Quote to an Order action but the process will be sensibly the same for the other Convert actions.
- First of all, create each field for the 2 entities with the same data type.
- Then, create a plugin on the Order Detail triggered on the CreateMessage.
/// <summary>
/// Initializes a new instance of the <see cref="SalesOrderDetailPlugin"/> class.
/// </summary>
public SalesOrderDetailPlugin()
: base(typeof(SalesOrderDetailPlugin))
{
base.RegisteredEvents.Add(new Tuple<int, string, string, Action<LocalPluginContext>>(20, "Create", "salesorderdetail", new Action<LocalPluginContext>(ExecuteSalesOrderDetail)));
// Note : you can register for more events here if this plugin is not specific to an individual entity and message combination.
// You may also need to update your RegisterFile.crmregister plug-in registration file to reflect any change.
} - Check for the Parent Context Message to be ConvertQuoteToSalesOrder and replace the copy the data from the Quote Product
protected void ExecuteSalesOrderDetail(LocalPluginContext localContext)
{
if (localContext == null)
{
throw new ArgumentNullException("localContext");
}
var context = localContext.PluginExecutionContext;
Service = localContext.OrganizationService;
// Check the parent message
if (context.ParentContext != null && context.ParentContext.MessageName == "ConvertQuoteToSalesOrder")
{
// Get the source quote ID
var quoteId = (Guid)context.ParentContext.InputParameters["QuoteId"];
// Get the Sales Order Detail which will be created
var currentSalesOrderDetail = context.InputParameters["Target"] as Entity;
if (currentSalesOrderDetail == null) return;
var currentLineItemNumber = currentSalesOrderDetail.GetAttributeValue<int>("lineitemnumber");
// Retrieve the data from the quote detail
var relativeQuoteDetail = getQuoteDetail(quoteId, currentLineItemNumber);
if (relativeQuoteDetail == null) return;
// Copy the data from the quote. Notice the data type.
currentSalesOrderDetail["new_field1"] = relativeQuoteDetail.GetAttributeValue<string>("new_field1");
currentSalesOrderDetail["new_field2"] = relativeQuoteDetail.GetAttributeValue<DateTime?>("new_field2");
}
}
// <copyright file="SalesOrderDetail.cs" company="">Here is a table synthesizing the modification between the different actions :
// Copyright (c) 2015 All Rights Reserved
// </copyright>
// <author></author>
// <date>11/06/2015 09:43:20</date>
// <summary>Implements the SalesOrderDetail Plugin.</summary>
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.1
// </auto-generated>
using System.Collections.Generic;
using BackToTheCrm.Entities.Extension;
namespace BackToTheCrm.Plugins
{
using System;
using System.ServiceModel;
using Microsoft.Xrm.Sdk;
using System.Linq;
/// <summary>
/// SalesOrderDetail Plugin.
/// </summary>
public class SalesOrderDetailPlugin : Plugin
{
#region attributes
IOrganizationService Service = null;
#endregion
/// <summary>
/// Initializes a new instance of the <see cref="SalesOrderDetailPlugin"/> class.
/// </summary>
public SalesOrderDetailPlugin()
: base(typeof(SalesOrderDetailPlugin))
{
base.RegisteredEvents.Add(new Tuple<int, string, string, Action<LocalPluginContext>>(20, "Create", "salesorderdetail", new Action<LocalPluginContext>(ExecuteSalesOrderDetail)));
// Note : you can register for more events here if this plugin is not specific to an individual entity and message combination.
// You may also need to update your RegisterFile.crmregister plug-in registration file to reflect any change.
}
/// <summary>
/// Executes the plug-in.
/// </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 CRM 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 void ExecuteSalesOrderDetail(LocalPluginContext localContext)
{
if (localContext == null)
{
throw new ArgumentNullException("localContext");
}
var context = localContext.PluginExecutionContext;
Service = localContext.OrganizationService;
// Check the parent message
if (context.ParentContext != null && context.ParentContext.MessageName == "ConvertQuoteToSalesOrder")
{
// Get the source quote ID
var quoteId = (Guid)context.ParentContext.InputParameters["QuoteId"];
// Get the Sales Order Detail which will be created
var currentSalesOrderDetail = context.InputParameters["Target"] as Entity;
if (currentSalesOrderDetail == null) return;
var currentLineItemNumber = currentSalesOrderDetail.GetAttributeValue<int>("lineitemnumber");
// Retrieve the data from the quote detail
var relativeQuoteDetail = getQuoteDetail(quoteId, currentLineItemNumber);
if (relativeQuoteDetail == null) return;
// Copy the data from the quote. Notice the data type.
currentSalesOrderDetail["new_field1"] = relativeQuoteDetail.GetAttributeValue<string>("new_field1");
currentSalesOrderDetail["new_field2"] = relativeQuoteDetail.GetAttributeValue<DateTime?>("new_field2");
}
}
/// <summary>
/// Get the data of the quote product
/// </summary>
/// <param name="quoteId">ID of the parent Quote</param>
/// <param name="currentLineItemNumber">Line number</param>
/// <returns></returns>
private Entity getQuoteDetail(Guid quoteId, int currentLineItemNumber)
{
string fetchXml =
string.Format(@"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>
<entity name='quotedetail'>
<attribute name='productid' />
<attribute name='new_field1' />
<attribute name='new_field2' />
<filter type='and'>
<condition attribute='quoteid' operator='eq' value='{0}' />
<condition attribute='lineitemnumber' operator='eq' value='{1}' />
</filter>
</entity>
</fetch>",
quoteId.ToString(),
currentLineItem
);
List<Entity> results = Service.RetrieveMultiple(new FetchExpression(fetchXml));
Entity quoteDetailToReturn = null;
if (results != null)
quoteDetailToReturn = results.FirstOrDefault();
return quoteDetailToReturn;
}
}
}
Message | Source entity | Target entity |
GenerateQuoteFromOpportunity | OpportunityProduct | QuoteDetail |
GenerateSalesOrderFromOpportunity | OpportunityProduct | SalesOrderDetail |
GenerateInvoiceFromOpportunity | OpportunityProduct | InvoiceDetail |
ConvertQuoteToSalesOrder | QuoteDetail | SalesOrderDetail |
ConvertSalesOrderToInvoice | SalesOrderDetail | InvoiceDetail |
This was originally posted here.
*This post is locked for comments