Background:
Quotes in Salesforce represent a proposal of prices for your company’s products and services to a particular prospect. If the Quote object is enabled on your page layout, a Sales Rep can generate a Quote from an Opportunity and its Line Items (OLIs). Each Opportunity can have multiple associated Quotes, and any one of them can be synced with the Opportunity. When a Quote and an Opportunity are synced together, any change to product line items on the Quote syncs with Products on the Opportunity, and vice versa. All this is standard Salesforce functionality.
Problem:
However, this sync only happens for standard fields on the line items, not for custom fields. Since product pricing tends to be complex and requires flexibility for most businesses, this means that many OLIs have custom calculation fields that are central to a business’s pricing offers. Some examples where custom fields might be lurking are annualised pricing calculations for products sold at a monthly unit price, custom descriptions or names to display to customers on the Quote document, or custom date ranges for ongoing services.
Let’s take a look at an example of this issue in action.
Here’s an Opportunity record with no products or quotes initially:
Let’s add a Quote to this Opportunity record by clicking “New Quote”:
And add some products to the Quote (i.e Quote Line Items):
This will return the synced Opportunity Line Item ID on the Quote Line Item and let our quote find and link the objects.
{
public static void syncQuotes(List newLineItems)
{
// get quote ids we need to query for
Set quoteIds = new Set();
for (QuoteLineItem qli : newLineItems)
{
if (qli.QuoteId != null)
{
quoteIds.add(qli.QuoteId);
}
} // Linking quote line item with Opportunity Line Items
Map<ID,ID> mapQuoteLineItemSortOrder= returnDefaultLinking(quoteIds);//Fetch opportunity line item for sync
Map<ID,OpportunityLineItem> mapOppLineItems=new Map<ID,OpportunityLineItem>();
for(OpportunityLineItem oli:[select id, Renewal__c from OpportunityLineItem where Opportunity.SyncedQuoteId in :quoteIds])
{
mapOppLineItems.put(oli.id,oli);
}
List lstOppotunityToUpdate = new List();
for (QuoteLineItem qli : newLineItems) {
OpportunityLineItem oli = mapOppLineItems.get(mapQuoteLineItemSortOrder.get(qli.Id));
if (oli != null ) {
oli.Renewal__c=qli.Renewal__c;
//update more fields….
lstOppotunityToUpdate.add(oli);
}
}
update lstOppotunityToUpdate;
}
private static Map<ID,ID> returnDefaultLinking(Set poIds)
{
Map<ID,ID> mapSortOrder= new Map<ID,ID>();
String query=’select id, name,(select id, Opportunity_Line_Item_ID__c from QuoteLineItems ) from Quote where id in :poIds’;
List lstQuotesWithLineItems=Database.query(query);
for(Quote q: lstQuotesWithLineItems)
{
if(q.QuoteLineItems !=null)
{
for(QuoteLineitem qli : q.QuoteLineItems)
{
if(qli.Opportunity_Line_Item_ID__c!=null)
{
//map quote line item id with respective opportunity line item id
mapSortOrder.put(qli.Id,ID.valueOF(qli.Opportunity_Line_Item_ID__c));
}
}
}
}
return mapSortOrder;
}
}
trigger QuoteLineItemTrigger on QuoteLineItem (after update) { QuoteLineItemTriggerHandler.syncQuotes(Trigger.new); }
public class OpportunityLineItemTriggerHandler{ public static Boolean isTriggerFire = true; public static void sync(Set oliIds ){ List lstQLIUpdate = new List(); for(QuoteLineItem qli: [Select id, Renewal__c, Opportunity_Line_Item_ID__c from QuoteLineItem WHERE pp_dev3__Opportunity_Line_Item_ID__c= :oliIds]){ lstQLIUpdate.add(new OpportunityLineItem(Id=qli.Opportunity_Line_Item_ID__c, Renewal__c=qli.Renewal__c)); } if(!lstQLIUpdate.isEmpty()){ isTriggerFire = false; update lstQLIUpdate; isTriggerFire = true; } } }
trigger OpportunityLineItemTrigger on OpportunityLineItem (after insert) { if(Trigger.isInsert && Trigger.isAfter && OpportunityLineItemTriggerHandler.isTriggerFire){ Set qliIds = Trigger.newMap.keyset(); OpportunityLineItemTriggerHandler.sync(qliIds); } }
I am getting an error when I try to save the trigger. Would someone be able to help me out with this?
Thanks for your blog.. Its very useful for me…
I am trying to follow through with the trigger and trigger handlers provided with my own custom field references, but there seems to be some syntactical errors with the sample code provided. Can you kindly advise?