Our company works in print media and when we sell ads for our magazine, our reps fill the fulfillment information out at the Quote Line Item object.

Our accounting software can only read from the Opportunity Product level.

I have created a trigger that achieves that with one catch, it doesn't recognize when a rep needs to sync the primary quote with the Opportunity.

The button is a standard Salesforce button, meaning it comes with Salesforce and I don't see how to customize it. (Start and Stop Sync button)

If I were to update a record at the Quote Line Item object after syncing the Quote, the trigger works as expected.

Question: How can I code the trigger to recognize when the Start Sync button is clicked to transfer this information to the Opportunity Product field?


trigger UpdateOpportunityProductMonthsToFulfill on QuoteLineItem (after insert, after update) {
    String monthsToFulfill = '';
    map<String, String> updateMap = new map<String, String>();

    // This is where the text for the Months_To_Fulfill__c field is built
    for(QuoteLineItem qli : Trigger.new){
        // To make a spelled-out list of months & years instead of the older "start through end"

        List<String> yearOneMonths = qli.Months_Served_Y1__c.replaceAll('None(;)?', '').split(';');
        for(String month : yearOneMonths){
            monthsToFulfill += ( monthsToFulfill.length()==0 ? '' : '; ' ) + month + ' ' + qli.Year_Served_First__c;
        if(!(qli.Year_Served_Second__c == null || qli.Year_Served_Second__c.equals('None'))){
            List<String> yearTwoMonths = qli.Months_Served_Y2__c.replaceAll('None(;)?', '').split(';');
            for(String month : yearTwoMonths){
                monthsToFulfill += '; ' + month + ' ' + qli.Year_Served_Second__c;

        updateMap.put(qli.QuoteId, monthsToFulfill);

    for(String quoteID : updateMap.keySet()){
        // This is the calculated value we saved from looping through the Trigger.new Collection
        monthsToFulfill = updateMap.get(quoteID);

        // Get the OpportunityLineItems associated with this QuoteId
        List<OpportunityLineItem> oliList = [Select Id, OpportunityId, Months_To_Fulfill__c 
                                             From OpportunityLineItem Where OpportunityId In (Select OpportunityId 
                                                                                              From Quote Where Id = :quoteID )];

        List<OpportunityLineItem> olisToUpdate = new List<OpportunityLineItem>();

        if(oliList.size() > 0){
            for (OpportunityLineItem oli : oliList){
                // Populate this olisToUpdate list because we may 
                // want to get smarter about what's actually updated 
                // in the future.  For now, everything is updated.
                oli.Months_To_Fulfill__c = monthsToFulfill;

            // Batch update the OpportunityLineItems we identified earlier
            if(olisToUpdate.size() > 0){
                update olisToUpdate;

1 Answer 1


If I understand you correctly, your reps press the button located in the Quote.

Then you should run the trigger on OpportunityLineItem instead of QuoteLineItem.

Because pressing 'Sync' creates/updates OpportunityLineItems identical to your QuoteLineItems, i.e. your QuoteLineItems aren't being updated or insterted, so your trigger won't fire.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .