3

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

11
  • Please provide the criteria and I didnot find what field on what object the work flow is updating.
    – Suresh
    Jul 15, 2015 at 16:44
  • You're correct, the roll-up summary field is updated after the field updates, that were triggered by the workflow rules, made their changes. See Triggers and Order of Execution.
    – Alex S
    Jul 15, 2015 at 16:44
  • Wow - Thank you for quick responses. Suri - if i strip it back the workflow just fires on Create or Edit of a Opportunity Product LineItem and the Action is a Field update to the Sales Price (UnitPrice) field on the opportunity Product lineItem. The field update is a formula, which is a % multiplication of a custom Roll-up summary field on the Opportunity object of some of the Sales Prices across the Opportunity Product LineItems
    – Jonny
    Jul 15, 2015 at 16:52
  • Alex S - thank you for your confirmation too. Have you any thoughts on what I could do to get around this issue without using Apex code (not something I'm in to, at least yet)?
    – Jonny
    Jul 15, 2015 at 16:57
  • Have you thought about multiplying the sales price of each opportunity Product lineItem by your % and then rolling up the values from the UnitPrice fields instead?
    – Alex S
    Jul 15, 2015 at 17:17

2 Answers 2

1

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).
    }
}
4
  • Thanks again for your earlier responses. I've been on Holiday the past 2 weeks but will revisit this again soon. I plan to read and learn the Apex Tutorials you copied a link too, but this may take me sometime to get to and learn of course......
    – Jonny
    Aug 3, 2015 at 14:12
  • ...Final thought that may help, is that I'm finding the workaround for now is to get users to simply 'Edit All' and Save again. Then the Calculated Maintenance is using the previous Roll-up amount on the Opportunity that hasn't changed and as such is also the current and thus correct. When I can dedicate more time I'm hoping I can get the calc to fire after save or maybe just have it save again (not ideal but possibly an easier opt until I learn more). Thanks again and to everyone else who responded including Ankz Davé (when I learn Apex much further this will likely help too :-)
    – Jonny
    Aug 3, 2015 at 14:13
  • Just wanted to visit back and say a big thank you for pointing out those tutorial back in Aug. I've since learned a lot and been applying to some solutions. Still lots to learn though :-) - Jonny
    – Jonny
    Mar 8, 2016 at 20:05
  • @Jonny you're welcome and thanks for the extra feedback, that's great to hear! David Liu's blog is still my favorite resource but since you posted your question Salesforce created Trailhead which is definitely worth checking out too..
    – Alex S
    Mar 8, 2016 at 20:18
0

I didn't get much about fields from question but got little basic, Try this logic:

Trigger:

trigger OpportunityReScheduling on Opportunity (after update, before update, after insert) 
{
    for (Opportunity o: Trigger.new)
    {
        if (Trigger.isAfter || Trigger.isInsert)
        {
            OpportunitySchedulingHandler.ScheduleServiceDateUpdate(o.id);   
        }
    }
} 

Class:

public with sharing class OpportunitySchedulingHandler {


    public static void ScheduleServiceDateUpdate(String oppid)
    {
        List<Opportunity> oliList = [SELECT Id, Name, Amount, (SELECT Id, UnitPrice, OpportunityId from OpportunityLineItems) from Opportunity where Id =:oppid];
        List<OpportunityLineItem> oliUpdateList = new List<OpportunityLineItem>();
        for(Opportunity x : oliList)
        {
            for(OpportunityLineItem oli : x.OpportunityLineItems)
            {
                oli.UnitProce = x.Amount;
                oliUpdateList.add(oli);
            }
        }
        if(!oliUpdateList.isEmpty()) 
        {
            update oliUpdateList;
        }
    }
}   
1
  • I don't think you want to do it this way - you are doing SOQL and DML inside of a for loop (the loop on Trigger.new)
    – cropredy
    Jul 24, 2015 at 0:28

You must log in to answer this question.

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