function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Benny Stevens 1Benny Stevens 1 

Trigger OLI after updating parent Opportunity record

Hi,

I have configured WFR Email Alerts on Opportunity Line Items (Opportunity Products) that sends email notifications when certain criteria are met.
The WFR works fine, but the criteria also includes conditions for the Opportunity, so, the WFR on Opportunity Product doesn't work when the parent Opportunity is being updated.

That's why I would like to add a trigger on the Opportunity that updates all related Opportunity Products (OLI's). It is only necessary for one specific record type (called "EU Opportunity") and it should not update any specific information on the OLI, I only want the "last modified date" to update and then WFR Email Alerts will work if the conditions are met.

1) Would this be a good solution for my situation?
2) I have tried to right a trigger and test class, but was not able to do it. Could someone please help me?

Please let me know if you'd require further information.

Many thanks in advance,
Benny
Best Answer chosen by Benny Stevens 1
Jen BennettJen Bennett
Do you have a workflow rule or approval process that updates a field on the Opportunity? If you are setting a field with a value that violates any data constraints you can get this error, see a similar issue here:
https://developer.salesforce.com/forums?id=906F000000093RaIAI

All Answers

Jen BennettJen Bennett
I know you said you didn't want the trigger to update the children records, but what if you created a field just for the purpose of triggering the workflow rule. Something like EU Opportunity Criteria Last Change Date. You could then create a set of opportunity ids and loop through the children records and if they meet your criteria, add the id to your set, then select the children record ids and new date field (where the opportunity is in that set) and assign those to a list, loop through the list set the date then update the children records? I'm not sure if it's the best solution either but it should do the trick.
Benny Stevens 1Benny Stevens 1

Thanks for your quick respons jjbenneett530. OK, a new (hidden) field to update would not be an issue.

In my example the WFR Email Alert on Opportunity Product is triggered when the Opportunity has the Record Type = 'EU Opportunity' AND Stage is Quotation and when the Opportunity Product has a UnitPrice > 10.

Now, when the Opportunity Stage is moved to Quotation (while it is an EU Opportunity and UnitPrice > 10) the WFR won't work. How should the trigger look like with the new custom field on Opportunity Product?

Jen BennettJen Bennett
Sorry Benny just saw your follow up question were you able to figure this out yet or what do you have so far on your trigger? 
Benny Stevens 1Benny Stevens 1

Hi,

I tried to write my trigger and test class (I am just starting as an admin) based on the following blog post:
http://knthornt.wordpress.com/2011/07/29/updating-products-on-opportunity-close-date-change/

I wanted to start the trigger in case of changes in the Stage. I created a Stage field on OLI (Opp Product), the trigger should update the picklist whenever it is changed on the Parent Opportunity.

The trigger is working, that's great, but I can't right the test class and therefor I am not able to push it into Production.
Could the issue be that the error/issue is related to (old) API version? (blog post is from 2011/07/29). I already added "(SeeAllData = true)" but not solved yet ... Failed Class Test, but code coverage is 43% (7/16)

Thanks, Benny

My trigger: 

trigger Opportunity_StageName_Update on Opportunity (after update) {

    //get the Ids of our EU AOE record type and add them to a set
    Set<Id> recTypeIds = new Set<Id>();
    for(RecordType r : [select Id, Name from RecordType where (Name='EU AOE Opportunity') AND SobjectType='Opportunity']){
        recTypeIds.add(r.id);
    }
     
    //check the opportunities in the trigger to see if the StageName changed AND any are of the RecordTypes above. 
    //if so add them to a Set
    Set<Id> EUAOEOppIds = new Set<Id>();
    for(Integer i=0;i<trigger.new.size();i++){
        if(trigger.new[i].StageName <> trigger.old[i].StageName && recTypeIds.contains(trigger.new[i].RecordTypeId)){
            EUAOEOppIds.add(Trigger.new[i].id);
        }
    }
     
    //Process any opps that have changed
    if(EUAOEOppIds.size() > 0){
        //get all of the opportunity Line items tied to opportunities in this trigger
        List<Opportunity> oliList = [select Id, Name, StageName, (select Id, Opportunity_Stage__c, OpportunityId from OpportunityLineItems) from Opportunity where Id IN :EUAOEOppIds];
         
        //create list to hold opportunity line items that need updating.
        List<OpportunityLineItem> oliUpdateList = new List<OpportunityLineItem>();
         
        //loop through opportunites and update all of the opportunity line items. add line items to update list.
        for(Opportunity o : oliList){
            for(OpportunityLineItem oli : o.OpportunityLineItems){
                oli.Opportunity_Stage__c = o.StageName;
                oliUpdateList.add(oli);
            }
        }
         
        //update the line items
        if(!oliUpdateList.isEmpty()){
            update oliUpdateList;
        }
    }
}


My test class:

@isTest (SeeAllData = true)
private class Test_Opportunity_StageName_Update {
 
    static testMethod void myUnitTest() {
        //create a dummy account
        Account a = new Account(Name='Test Account');
        insert a;
                 
        //Get the Standard PriceBook and make sure it is active
        PriceBook2 pb = [select Id from PriceBook2 where IsStandard=True];
        pb.IsActive=true;
        update pb;
         
        //create two products 
        List<Product2> prodList = new List<Product2>();
        prodList.add(new Product2(Name='Prod1'));
        prodList.add(new Product2(Name='Prod1'));
        insert prodList;
         
        //Create pricebook entries for the products
        List<PricebookEntry> pbEntryList = new List<PricebookEntry>();
        pbEntryList.add(new PricebookEntry(Pricebook2Id=pb.Id, Product2Id=prodList[0].id, UnitPrice=100, IsActive=true));
        pbEntryList.add(new PricebookEntry(Pricebook2Id=pb.Id, Product2Id=prodList[1].id, UnitPrice=200, IsActive=true));
        insert pbEntryList; 
         
        //Get the record type we care about plus 3 other ones.
        List<RecordType> recTypeList = [select Id, Name from RecordType where (Name='EU AOE Opportunity' OR Name='MEX Opportunity' OR Name='NA Opportunity' OR Name='SA Opportunity') AND SobjectType='Opportunity']; 
         
        //Create 200 new opportunities.
        List<Opportunity> newOppList = new List<Opportunity>();
        for(Integer i=0; i<200; i++){
            Opportunity o = new Opportunity(Name='Test Oppty ' + i, AccountId=a.id, StageName='Prospecting', CloseDate=Date.Today()+1, Probability=0.5);
            //We grabbed four record types above, so set 1/4 of opportunities to each record type. 
            o.RecordTypeId=recTypeList[Math.mod(i,4)].Id;
            newOppList.add(o);
        }
                  
        //insert our new opportunties
        insert newOppList;
         
        //Now add both products to all of the opportunities
        List<OpportunityLineItem> oliList = new List<OpportunityLineItem>();
        for(Opportunity o : newOppList){
            oliList.add(new OpportunityLineItem(OpportunityId=o.id, PriceBookEntryId=pbEntryList[0].id,Quantity=1, Opportunity_Stage__c='Prospecting',UnitPrice=100));
            oliList.add(new OpportunityLineItem(OpportunityId=o.id, PriceBookEntryId=pbEntryList[1].id,Quantity=1, Opportunity_Stage__c='Prospecting',UnitPrice=200));
        }
         
        //insert the opportunity line items
        insert oliList;
         
        //Now that everything has been set up, we can begin the actual test
        Test.startTest();
        
        //change the Stage for all 200 opportunities
        for(Opportunity o : newOppList){
             o.StageName = 'Discovery';
        }
         
        //Update the opportuntiies
        update newOppList;

                  
        //get all of the products tied to the opportunites, there should be 400 total
        oliList.clear();
        oliList = [select Id, Opportunity_Stage__c from OpportunityLineItem where OpportunityId IN :newOppList];
        System.assertEquals(400,oliList.size());
         
        //Now check that the number of Opportunity Line items with Stage Name of Discovery is 100
        //since 1 out of 4 the opportunities should have fired product stage updates
        Integer DiscoveryCounter = 0;
        Integer ProspectingCounter = 0;
        for(OpportunityLineItem oli : oliList){
            if(oli.Opportunity_Stage__c == 'Discovery'){
                //increase the count by 1
                DiscoveryCounter++;             
            }
            if(oli.Opportunity_Stage__c == 'Prospecting'){
                ProspectingCounter++;
            }
        }
        
        //make sure the counts are as expected
        System.AssertEquals(100,DiscoveryCounter);
        System.AssertEquals(300,ProspectingCounter);
        Test.stopTest();
        
        
    }
}



The error:

Class: Test_Opportunity_StageName_Update
Method Name: myUnitTest
Pass/Fail: Fail

Error Message	:

System.DmlException: Insert failed. First exception on row 0; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, A workflow or approval field update caused an error when saving this record. Contact your administrator to resolve it.
common.exception.SfdcSqlException: ORA-20067: OPPORTUNITY_LINEITEM.QUANTITY
ORA-06512: at "DOPEY.SOPPLINEITEM", line 1242
ORA-06512: at line 1


{call sOppLineItem.update_opplineitems(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)}

{call sOppLineItem.update_opplineitems(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)}: []

Stack Trace
Class.Test_Opportunity_StageName_Update.myUnitTest: line 49, column 1


Thanks, Benny
 

Jen BennettJen Bennett
I ran the trigger and test in my dev org and didn't have any errors. I found this link on the stackexchange: http://salesforce.stackexchange.com/questions/17913/java-sql-sqlexception-error-caused-by-opportunitylineitem-dml-request
Jen BennettJen Bennett
Curious how you made out with your trigger and test?  Sent via the Samsung Galaxy Note® 3, an AT&T 4G LTE smartphone
Benny Stevens 1Benny Stevens 1

Hi jjbennett530, so you have added the exact same APEX Trigger and Test Class and it worked for you? Did you get 100% (16/16) code coverage?

Thanks for the link, I wanted to log a case also, but I got the message that I should get support on this forum for developer support :-)
(Assistance with Developing (coding, APEX, visualforce: "Developer support for standard customers and partners is supported directly through our community. If you have a developer support question, please visit our Developer Forum")

Jen BennettJen Bennett
I simplified the test a little since I only setup 2 record types: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 @isTest private class OpptyTriggerTest {     static testMethod void myUnitTest() {         //create a dummy account         Account a = new Account(Name='Test Account');         insert a;                          //Get the Standard PriceBook and make sure it is active         PriceBook2 pb = [select Id from PriceBook2 where IsStandard=True];         pb.IsActive=true;         update pb;                  //create two products         List prodList = new List();         prodList.add(new Product2(Name='Prod1'));         prodList.add(new Product2(Name='Prod1'));         insert prodList;                  //Create pricebook entries for the products         List pbEntryList = new List();         pbEntryList.add(new PricebookEntry(Pricebook2Id=pb.Id, Product2Id=prodList[0].id, UnitPrice=100, IsActive=true));         pbEntryList.add(new PricebookEntry(Pricebook2Id=pb.Id, Product2Id=prodList[1].id, UnitPrice=200, IsActive=true));         insert pbEntryList;                  //Get the record type we care about plus 3 other ones.         List recTypeList = [select Id, Name from RecordType where (Name='test' OR Name='test2') AND SobjectType='Opportunity'];                  //Create 200 new opportunities.         List newOppList = new List();         for(Integer i=0; i 0) {               o.RecordTypeId=recTypeList[0].Id;             } else {               o.RecordTypeId=recTypeList[1].Id;             }             newOppList.add(o);         }                           //insert our new opportunties         insert newOppList;                  //Now add both products to all of the opportunities         List oliList = new List();         for(Opportunity o : newOppList){             oliList.add(new OpportunityLineItem(OpportunityId=o.id, PriceBookEntryId=pbEntryList[0].id,Quantity=1, Opportunity_Stage__c='Prospecting',UnitPrice=100));             oliList.add(new OpportunityLineItem(OpportunityId=o.id, PriceBookEntryId=pbEntryList[1].id,Quantity=1, Opportunity_Stage__c='Prospecting',UnitPrice=200));         }                  //insert the opportunity line items         insert oliList;                  //Now that everything has been set up, we can begin the actual test         Test.startTest();                 //change the Stage for all 200 opportunities         for(Opportunity o : newOppList){              o.StageName = 'Discovery';         }                  //Update the opportuntiies         update newOppList;                           //get all of the products tied to the opportunites, there should be 400 total         oliList.clear();         oliList = [select Id, Opportunity_Stage__c from OpportunityLineItem where OpportunityId IN :newOppList];         System.assertEquals(400,oliList.size());                 Test.stopTest();                     } } Sent via the Samsung Galaxy Note® 3, an AT&T 4G LTE smartphone
Jen BennettJen Bennett
Sorry replying via email didn't format this very well...
@isTest
private class OpptyTriggerTest {
 
    static testMethod void myUnitTest() {
        //create a dummy account
        Account a = new Account(Name='Test Account');
        insert a;
                 
        //Get the Standard PriceBook and make sure it is active
        PriceBook2 pb = [select Id from PriceBook2 where IsStandard=True];
        pb.IsActive=true;
        update pb;
         
        //create two products 
        List<Product2> prodList = new List<Product2>();
        prodList.add(new Product2(Name='Prod1'));
        prodList.add(new Product2(Name='Prod1'));
        insert prodList;
         
        //Create pricebook entries for the products
        List<PricebookEntry> pbEntryList = new List<PricebookEntry>();
        pbEntryList.add(new PricebookEntry(Pricebook2Id=pb.Id, Product2Id=prodList[0].id, UnitPrice=100, IsActive=true));
        pbEntryList.add(new PricebookEntry(Pricebook2Id=pb.Id, Product2Id=prodList[1].id, UnitPrice=200, IsActive=true));
        insert pbEntryList; 
         
        //Get the record type we care about plus 3 other ones.
        List<RecordType> recTypeList = [select Id, Name from RecordType where (Name='test' OR Name='test2') AND SobjectType='Opportunity']; 
         
        //Create 200 new opportunities.
        List<Opportunity> newOppList = new List<Opportunity>();
        for(Integer i=0; i<200; i++){
            Opportunity o = new Opportunity(Name='Test Oppty ' + i, AccountId=a.id, StageName='Prospecting', CloseDate=Date.Today()+1, Probability=0.5);
            //We grabbed four record types above, so set 1/4 of opportunities to each record type. 
            if (Math.mod(i,2) > 0) {
            	o.RecordTypeId=recTypeList[0].Id;
            } else {
            	o.RecordTypeId=recTypeList[1].Id;
            }
            newOppList.add(o);
        }
                  
        //insert our new opportunties
        insert newOppList;
         
        //Now add both products to all of the opportunities
        List<OpportunityLineItem> oliList = new List<OpportunityLineItem>();
        for(Opportunity o : newOppList){
            oliList.add(new OpportunityLineItem(OpportunityId=o.id, PriceBookEntryId=pbEntryList[0].id,Quantity=1, Opportunity_Stage__c='Prospecting',UnitPrice=100));
            oliList.add(new OpportunityLineItem(OpportunityId=o.id, PriceBookEntryId=pbEntryList[1].id,Quantity=1, Opportunity_Stage__c='Prospecting',UnitPrice=200));
        }
         
        //insert the opportunity line items
        insert oliList;
         
        //Now that everything has been set up, we can begin the actual test
        Test.startTest();
        
        //change the Stage for all 200 opportunities
        for(Opportunity o : newOppList){
             o.StageName = 'Discovery';
        }
         
        //Update the opportuntiies
        update newOppList;

                  
        //get all of the products tied to the opportunites, there should be 400 total
        oliList.clear();
        oliList = [select Id, Opportunity_Stage__c from OpportunityLineItem where OpportunityId IN :newOppList];
        System.assertEquals(400,oliList.size());
        
        Test.stopTest();
        
        
    }
}


Benny Stevens 1Benny Stevens 1
I get following error when I run your test class:
Error Message: System.QueryException: List has no rows for assignment to SObject
Stack Trace: Class.OpptyTriggerTest.myUnitTest: line 10, column 1


Jen BennettJen Bennett
Forgot to add the (SeeAllData = true) after @isTest.
Benny Stevens 1Benny Stevens 1
Thanks, but now I get
Error Message: System.ListException: List index out of bounds: 1
Stack Trace: Class.OpptyTriggerTest.myUnitTest: line 37, column 1


Jen BennettJen Bennett
Did you change the WHERE clause in the recordType query?
//Get the record type we care about plus 3 other ones.
        List<RecordType> recTypeList = [select Id, Name from RecordType where (Name='test' OR Name='test2') AND SobjectType='Opportunity'];
Benny Stevens 1Benny Stevens 1
No, I copied yours completely.
Jen BennettJen Bennett
Ok yea, I created a test and test2 record type to just try and test your trigger/test. So you can change line 27 to:
List<RecordType> recTypeList = [select Id, Name from RecordType where (Name='EU AOE Opportunity' OR Name='MEX Opportunity' ) AND SobjectType='Opportunity'];


Benny Stevens 1Benny Stevens 1
Hmm, obvious, sorry, still learning ... Dopey is back:
Error Message	:
System.DmlException: Insert failed. First exception on row 2; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, A workflow or approval field update caused an error when saving this record. Contact your administrator to resolve it.
common.exception.SfdcSqlException: ORA-20067: OPPORTUNITY_LINEITEM.QUANTITY
ORA-06512: at "DOPEY.SOPPLINEITEM", line 1242
ORA-06512: at line 1


{call sOppLineItem.update_opplineitems(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)}

{call sOppLineItem.update_opplineitems(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)}: []

Stack Trace: 
Class.Opportunity_StageName_Update.myUnitTest: line 53, column 1


Jen BennettJen Bennett
Do you have a workflow rule or approval process that updates a field on the Opportunity? If you are setting a field with a value that violates any data constraints you can get this error, see a similar issue here:
https://developer.salesforce.com/forums?id=906F000000093RaIAI
This was selected as the best answer
Benny Stevens 1Benny Stevens 1
Hi,

I have deactivated the WFR on OLI which updates the Quantity field with the value of another number field.
I was able to get a passed test and 100% code coverage!

I updated the WFR field update with the criteria that the number field should be greater or equal to 1, before it can be triggered.
I have activated the WFR again, and ran a succesfull test.

Thank you so much for your time and efforts!