Thursday, 6 November 2014

Syncing custom fields between quotes and opportunities (Custom Quote Sync)



Some time we need to sync Opportunity Line Item and Quote Line Item.When we create Quote automatically  Opportunity line item created as Quote Line Item. But Some time we also need to sync custom field.

Requirement :-

I would like to sync between opportunity line item and quote line item. I have a few custom fields defined in addition to standard fields in both quote line item and opportunity line item objects. Custom fields in both objects are exact replica.

Solution :-
For above requirement I have created the below trigger :-

/*
***********************************************************************
Created By    :- Amit Chaudhary
Created Date  :- 06 NOV 2014
Desc          :- Syncing custom fields between quotes and opportunities 
***********************************************************************
*/

trigger QuoteLineItemTrigger on QuoteLineItem (after insert){


    map<string,string> mapQuoteOppty=new map<string,string>();
    string JSONContent=Json.Serialize(Trigger.New);
    JSONParser parser =JSON.createParser(JSONContent);
    list<string> OpptyLineId=new list<string>();
    list<string> QuoteLineId=new list<string>();
    System.debug('parser-------->'+parser );
    
    while (parser.nextToken() != null) 
    {
        if(parser.getCurrentToken()==JSONToken.VALUE_STRING && parser.getCurrentName()=='OpportunityLineItemId')
        OpptyLineId.add(parser.getText());
            if(parser.getCurrentToken()==JSONToken.VALUE_STRING && parser.getCurrentName()=='Id')
                QuoteLineId.add(parser.getText());
                parser.nextToken();
                    if(parser.getCurrentToken()==JSONToken.VALUE_STRING && parser.getCurrentName()=='OpportunityLineItemId')
                        OpptyLineId.add(parser.getText());
                        if(parser.getCurrentToken()==JSONToken.VALUE_STRING && parser.getCurrentName()=='Id')
                            QuoteLineId.add(parser.getText());
    }

    System.debug('OpptyLineId-------->'+OpptyLineId);
    System.debug('QuoteLineId-------->'+QuoteLineId);
    
    integer iCount=0;
    for(string strOppLineId : OpptyLineId)
    {
        string iQuoteLineId=QuoteLineId[iCount];
        mapQuoteOppty.put(iQuoteLineId,strOppLineId);
        iCount++;
    }
    Set<Id> SetOppId=new Set<Id>();
    Set<Id> SetQuoteId=new Set<Id>();
    for(QuoteLineItem QLI:trigger.new)
    {
        SetQuoteId.add(QLI.QuoteId);
    }
    List<Quote> Lstquotes =[select id, OpportunityId, isSyncing from Quote where Id in :SetQuoteId];
    for(Quote Qt:Lstquotes)
    {
        SetOppId.add(Qt.OpportunityId);
    }
    
    List<OpportunityLineItem> lstOLI=[select Id, OpportunityId,Negotiated__c,End_Date__c,Start_Date__c from OpportunityLineItem where OpportunityId in:SetOppId];
    
    Map<Id,OpportunityLineItem> MapOLI=new Map<Id,OpportunityLineItem>([select Id,Negotiated__c, OpportunityId, End_Date__c,Start_Date__c  from OpportunityLineItem where OpportunityId in:SetOppId]);
    Map<Id,QuoteLineItem > MapQLI=new map<Id,QuoteLineItem>([Select Id, Negotiated__c, End_Date__c,Start_Date__c from QuoteLineItem where QuoteId in:SetQuoteId]);
    
    list<QuoteLineItem> updateQuoteLineItem =new list<QuoteLineItem >();
    for(QuoteLineItem qli:MapQLI.values())
    {
       System.debug('&&&&'+mapQuoteOppty);
       if(mapQuoteOppty.get(qli.id)!=null)
       {
          String OppID = mapQuoteOppty.get(qli.id);
          OpportunityLineItem OLI = MapOLI.get(OppID);
          
          qli.End_Date__c=OLI.End_Date__c;
          qli.Start_Date__c=OLI.Start_Date__c;   
          qli.Negotiated__c= OLI.Negotiated__c;

          updateQuoteLineItem.add(qli);
            
       }
    }
    update updateQuoteLineItem;
    
}






Related Link :-

Check this in AppExchange
https://sites.secure.force.com/appexchange/listingDetail?listingId=a0N30000003IlfVEAS
Custom Quote Sync
App by Force.com Labs 11/2/2010


Thanks
Amit Chaudhary

33 comments:

  1. Very good solution ...Tricky one :)

    ReplyDelete
  2. This code needs to paste in which object? opportunity or quote

    ReplyDelete
  3. I created a trigger on QuoteLineItem object. You can also create one button on Quote to Sync and use above code

    ReplyDelete
  4. It looks like you can achieve something similar using a formula field. You won't be able to create it using the wizard, but you can manually enter the "OpportunityLineItem.Id" as the formula, for a formula field on QuoteLineItem.

    Original source: Lex's comment on http://salesforce.stackexchange.com/questions/47343/does-quote-line-item-reference-back-to-which-oli-it-relates-to.

    ReplyDelete
    Replies
    1. Good Catch. But still to syn QuoteLineIteam and OpportunityLineItem you need to write Trigger or apex code.

      Delete
    2. That is very strange.. I created this FF and it works nice, but no idea how it works! As per data model, there isn't a way you can do this traversal and there is many to many involved. Any idea HOW it works?

      Delete
    3. Yes, With the formula field you will get the Opportunity line item ID on Quote line item. After that to Sync Opportunity Line Item with Quote Line Item you can create a trigger Quote line item.

      Above Trigger will help you Pull the custom field values on Quote line Item. If you want to pull the values from Quote line item to Opportunity line Item then you need to create one more trigger to Sync Quote Line Item with Opportunity line Item.
      Thank
      Amit Chaudhary

      Delete
    4. But how can I get the OpportunityLineItemId with formula field?

      Delete
    5. Go to Quote line Item and type OpportunityLineItemId directly

      Delete
    6. But this field OpportunityLineItemId doesnt exist on QuoteLineItem . I am creating the formula on quoteLineItem

      Delete
    7. This is the only tick here .Just create one formula field -> Then select text -> the type below formula :-

      OpportunityLineItem.Id

      If you need any help then please let me know

      Thanks
      Amit Chaudhary
      amit.salesforce21@gmai.com

      Delete
    8. Hello Amit, I sent you an email. Check your inbox

      Delete
  5. Nice Post Amit. This Trigger saved my day

    ReplyDelete
  6. Thanks for this: I wondered What is the benefit of this trigger compared with the custom sync app from Force.com?

    ReplyDelete
    Replies
    1. Some time we need to sync Opportunity Line Item and Quote Line Item. For same we have Sync button on Quote. But with that button we can sync only Salesforce standard fields. Some time we also need to sync custom fields. In that case we need to create trigger for same.

      Delete
    2. In App Exchange we have one app also for same. but that is manage package. you will not able to see code. But according to above trigger you can also add your own logic also.

      Check this in AppExchange
      https://sites.secure.force.com/appexchange/listingDetail?listingId=a0N30000003IlfVEAS

      Delete
  7. How can you replicate this in Testing? Without a way to simulate the QLI creation from the OpptyLineItems, you lose the linkage.

    ReplyDelete
  8. Right..
    For same you can maintain the opportunity line item ID on QLI with the help of trigger.

    ReplyDelete
    Replies
    1. Here's my problem and maybe you can give me some ideas... I created the Formula Field named RelatedOpptyLineItem using your idea, setting the formula to OpptorunityLineItem.id. So, in the real world events, when the user creates a quote from an Oppty with OpptyLineItems, that field is populated for the QLIs created from the OLIs. So my syncing trigger can easily match the QLI with the OLI.

      However, this linkage is never created, since you can't (AFAIK) simulate this auto creation of QLIs in Apex.

      My only thought, would be to add an additional field on the QLI which I can populate in my test environment, and add a few isTest clauses in my trigger, to use that field.

      If you had any other ideas, I open for it. Thanks!

      Delete
    2. Yes. When ever you will create QLI from OLI ( Opportunity line Item ) then you will always get OLI Id in formula field.

      I am not able to understand your 2nd point auto creation of QLI in APEX. If i am not wrong then you are creating QLI directly by Apex code ( you are not using standard button) . If Yes then you will not get Opportunity line item ID in formula field. In that case you need to create one text(18) field on QLI and need to populate opportunity line item ID in QLI custom field.

      If you are using any code then please provide your code so that i can help you.

      Thanks,
      Amit Chaudhary
      Email :- amit.salesforce21@gmail.com

      Delete
    3. I think I found a decent workaround...and it sounds like your suggestion. Interestingly, this can't be done in a Field Update WF. So, I still have my formula field to grab the related OpptyLineItem on my QLI. I also created another field on the QLI, named RelatedOpptyLineItemID. I added a before insert trigger on the QLI, to populate the new text field RelatedOpptyLineItemID with the value in the formula field RelatedOpptyLineItem.

      My sync trigger now exclusively uses the new field for matching, and in my test code I can populate that field when I create the QLI.

      for(QuoteLineItem qli : newQLIs){
      //Could not do this in a WorkFlow
      if(qli.RelatedOpptyLineItem__c!=null){
      qli.RelatedOpptyLineItemID__c=qli.RelatedOpptyLineItem__c;
      }
      }

      Delete
    4. Ohh Great.

      I hope your problem is resolved with new custom field. Please let me know if are still facing this issue.

      Delete
  9. Excellent post Amit. Thanks

    ReplyDelete
  10. This comment has been removed by the author.

    ReplyDelete
  11. Really helpful. Can you please post test class for this?

    ReplyDelete
  12. Excellent Post - thanks for your help :)

    ReplyDelete
  13. I'm trying to sync a custom field on my Quote Line Item called "License Start Date" and sync it to the Opportunity Product Line Item. Would this work for me and where would I implement it?

    ReplyDelete