[SalesForce] Updating Opportunity Product field when Roll-Up Opportunity field is changed

Can anyone please help with a possible solution?

I have a Workflow on the Opportunity Products (Opportunity LineItems) that has one significant issue, which I can't workout how to solve.

The workflow seems to fire when desired and updates the Sales Price ('UnitPrice') field based on a calculation and certain criteria, BUT it is always one step behind i.e. it updates with the correct answer for the previous calculation, so if I then just 'Edit All' and re-save without amending anything it then has the correct answer (kind of catching up so to speak). The formula uses a custom roll-up summary field, which is a subset value of the total Amount on the related Opportunity object. What I think is happening is that the workflow is taking the Roll-up summary value before it is updated by the new total value of all the Opportunity LineItems and as a result it is applying the prior value.

Thank you in advance of your help,
Jonny

Best Answer

The roll-up summary field is only updated after the field updates, triggered by your workflow rules, have been completed. See Triggers and Order of Execution.

I'm not aware of a workaround which you could implement, without using code. So have a go at adapting the below.

Note - This is an After trigger because changing the Opportunity Product's UnitPrice will cause the Opportunity Amount field to recalculate, causing the recursive trigger error.

This will update the Sales Price of all Opportunity Products which are related to the Opportunity which has just had it's roll-up field update. I'm guessing that that's what you'll want but if not, let me know.

Trigger

trigger UnitPriceUpdate on Opportunity (after update) {

    Map<Id,Decimal> softwareRevenue = new Map<Id,Decimal>();
    List<Opportunity> upOpps = new List<Opportunity>();
    List<OpportunityLineItem> upOppProds = new List<OpportunityLineItem>();
    Decimal oppExpRev;

    for(Opportunity o : Trigger.new){
        Opportunity oldOpp = Trigger.oldMap.get(o.Id);
        //change Software_Line_Item__c to roll-up field name
        if(oldOpp.Software_Line_Item__c != o.Software_Line_Item__c){
            //do the same here
            softwareRevenue.put(o.Id,o.Software_Line_Item__c);
            upOpps.add(o);
        }
    }
    //replace prcntge_of_maintenance__c with formula field containing % on Opportunity Product
    for(OpportunityLineItem oli : [SELECT Id, prcntge_of_maintenance__c, OpportunityId FROM OpportunityLineItem
                                    WHERE OpportunityId IN :upOpps])
    {
        oppExpRev = softwareRevenue.get(oli.OpportunityId);
        //replace prcntge_of_maintenance__c with formula field containing % that you want to apply on Opportunity Product
        oli.UnitPrice = oppExpRev*oli.prcntge_of_maintenance__c;
        upOppProds.add(oli);
    }
    update upOppProds;
}

Test Class

@isTest
public class UnitPriceUpdateTest {

    @testSetup
    static void dataSetup() {

        Product2 p = new Product2();
        p.Name = 'TestProduct';
        insert p;

        Id pricebookId = Test.getStandardPricebookId();

        PricebookEntry pbe = new PricebookEntry(
            Pricebook2Id = pricebookId,
            Product2Id = p.Id,
            UnitPrice = 10000,
            IsActive = true);
        insert pbe;

        List<Opportunity> opps = new List<Opportunity>();

        for (Integer i = 0; i < 200; i++) {

            Opportunity o = new Opportunity(
                Name = 'Test Opp' + i,
                CloseDate = date.today() + 1,
                StageName = 'Prospecting');
            opps.add(o);
        }
        insert opps;

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

        for(Opportunity o : opps) {
            OpportunityLineItem oli = new OpportunityLineItem(
                OpportunityId = o.Id,
                PricebookEntryId = pbe.Id,
                UnitPrice = 100,
                Quantity = 1,
                Sales_Price1__c = 100,
                Description = '<0>');
            oppProds.add(oli);
        }
        insert oppProds;
    }

    static testMethod void testNewSoftRev() {

        List<OpportunityLineItem> oppProds1 = [SELECT Id, Sales_Price1__c FROM OpportunityLineItem WHERE Description = '<0>'];

        test.startTest();

        for(OpportunityLineItem oli : oppProds1) {
            oli.Sales_Price1__c = 200;
        }
        update oppProds1;

        test.stopTest();

    // Retrieve the updated Opportunity Products using an SOQL query here
    // then use system.assert to check that the correct UnitPrice has been calculated.
    // This is not necessary to achieve 100% code coverage but it ensures that your code
    // has done what it's supposed to (and hasn't been impacted by changes in your org in the future).
    }
}
Related Topic