1

I have a trigger that executes on after insert and after create on the Opportunity that updates fields on the OpportunityLineItem Object. Code works but the test class opportunity does not appear to successfully add a product so the code can not go into a IF statement. I am sure this is something simple but dang if I can figure it out.

Trigger

trigger UpdateOTRev on Opportunity (after insert, after update) {
Set<id> triggerIds = new Set<id>();
List<OpportunityLineItem> lstOLIUpdate = new List<OpportunityLineItem>();
//See if Trigger has only been run once
if(RecursiveTriggerHandler.isFirstTime){
    RecursiveTriggerHandler.isFirstTime = false;
     //Put all trigger opp ids into a set
     for(Opportunity Opps : Trigger.new){
        triggerIds.add(Opps.Id);
     }//END for (Opportunity Opps)
     //Get all the Opp and Products
     List<Opportunity> lstOpps = [SELECT id, CloseDate, (SELECT id, TotalPrice, One_Time_Revenue__c FROM OpportunityLineItems) FROM Opportunity 
                                  WHERE id in: triggerIds];
         //Loop through Opps
         for(Opportunity opp: lstOpps){
             //Loop through Products in Opps
             for(OpportunityLineItem oli : opp.OpportunityLineItems){
                 if(oli.One_Time_Revenue__c == true){
                    //Apply the logic of which fields to update
                    if(opp.CloseDate > Date.valueOf('2017-12-31') && opp.CloseDate < Date.valueOf('2018-04-01')){
                        oli.One_Time_Rev_Q1_2018__c = oli.TotalPrice;
                        oli.One_Time_Rev_Q2_2018__c = 0;
                        oli.One_Time_Rev_Q3_2018__c = 0;
                        oli.One_Time_Rev_Q4_2018__c = 0;
                        oli.One_Time_Rev_Q1_2019__c = 0;
                        oli.One_Time_Rev_Q2_2019__c = 0;
                        oli.One_Time_Rev_Q3_2019__c = 0;
                        oli.One_Time_Rev_Q4_2019__c = 0;
                        lstOLIUpdate.add(oli);              
                    }//END if 2018 Q1
                    if(opp.CloseDate > Date.valueOf('2018-03-31') && opp.CloseDate < Date.valueOf('2018-07-01')){
                        oli.One_Time_Rev_Q1_2018__c = 0;
                        oli.One_Time_Rev_Q2_2018__c = oli.TotalPrice;
                        oli.One_Time_Rev_Q3_2018__c = 0;
                        oli.One_Time_Rev_Q4_2018__c = 0;
                        oli.One_Time_Rev_Q1_2019__c = 0;
                        oli.One_Time_Rev_Q2_2019__c = 0;
                        oli.One_Time_Rev_Q3_2019__c = 0;
                        oli.One_Time_Rev_Q4_2019__c = 0;
                        lstOLIUpdate.add(oli);              
                    }//END if 2018 Q2
                    if(opp.CloseDate > Date.valueOf('2018-06-30') && opp.CloseDate < Date.valueOf('2018-10-01')){
                        oli.One_Time_Rev_Q1_2018__c = 0;
                        oli.One_Time_Rev_Q2_2018__c = 0;
                        oli.One_Time_Rev_Q3_2018__c = oli.TotalPrice;
                        oli.One_Time_Rev_Q4_2018__c = 0;
                        oli.One_Time_Rev_Q1_2019__c = 0;
                        oli.One_Time_Rev_Q2_2019__c = 0;
                        oli.One_Time_Rev_Q3_2019__c = 0;
                        oli.One_Time_Rev_Q4_2019__c = 0;
                        lstOLIUpdate.add(oli);              
                    }//END if 2018 Q3
                    if(opp.CloseDate > Date.valueOf('2018-09-30') && opp.CloseDate < Date.valueOf('2019-01-01')){
                        oli.One_Time_Rev_Q1_2018__c = 0;
                        oli.One_Time_Rev_Q2_2018__c = 0;
                        oli.One_Time_Rev_Q3_2018__c = 0;
                        oli.One_Time_Rev_Q4_2018__c = oli.TotalPrice;
                        oli.One_Time_Rev_Q1_2019__c = 0;
                        oli.One_Time_Rev_Q2_2019__c = 0;
                        oli.One_Time_Rev_Q3_2019__c = 0;
                        oli.One_Time_Rev_Q4_2019__c = 0;
                        lstOLIUpdate.add(oli);              
                    }//END if 2018 Q4
                    if(opp.CloseDate > Date.valueOf('2018-12-31') && opp.CloseDate < Date.valueOf('2019-04-01')){
                        oli.One_Time_Rev_Q1_2018__c = 0;
                        oli.One_Time_Rev_Q2_2018__c = 0;
                        oli.One_Time_Rev_Q3_2018__c = 0;
                        oli.One_Time_Rev_Q4_2018__c = 0;
                        oli.One_Time_Rev_Q1_2019__c = oli.TotalPrice;
                        oli.One_Time_Rev_Q2_2019__c = 0;
                        oli.One_Time_Rev_Q3_2019__c = 0;
                        oli.One_Time_Rev_Q4_2019__c = 0;
                        lstOLIUpdate.add(oli);              
                    }//END if 2018 Q1
                    if(opp.CloseDate > Date.valueOf('2019-03-31') && opp.CloseDate < Date.valueOf('2019-07-01')){
                        oli.One_Time_Rev_Q1_2018__c = 0;
                        oli.One_Time_Rev_Q2_2018__c = 0;
                        oli.One_Time_Rev_Q3_2018__c = 0;
                        oli.One_Time_Rev_Q4_2018__c = 0;
                        oli.One_Time_Rev_Q1_2019__c = 0;
                        oli.One_Time_Rev_Q2_2019__c = oli.TotalPrice;
                        oli.One_Time_Rev_Q3_2019__c = 0;
                        oli.One_Time_Rev_Q4_2019__c = 0;
                        lstOLIUpdate.add(oli);              
                    }//END if 2019 Q2
                    if(opp.CloseDate > Date.valueOf('2019-06-30') && opp.CloseDate < Date.valueOf('2019-10-01')){
                        oli.One_Time_Rev_Q1_2018__c = 0;
                        oli.One_Time_Rev_Q2_2018__c = 0;
                        oli.One_Time_Rev_Q3_2018__c = 0;
                        oli.One_Time_Rev_Q4_2018__c = 0;
                        oli.One_Time_Rev_Q1_2019__c = 0;
                        oli.One_Time_Rev_Q2_2019__c = 0;
                        oli.One_Time_Rev_Q3_2019__c = oli.TotalPrice;
                        oli.One_Time_Rev_Q4_2019__c = 0;
                        lstOLIUpdate.add(oli);              
                    }//END if 2019 Q3
                    if(opp.CloseDate > Date.valueOf('2019-09-30') && opp.CloseDate < Date.valueOf('2020-01-01')){
                        oli.One_Time_Rev_Q1_2018__c = 0;
                        oli.One_Time_Rev_Q2_2018__c = 0;
                        oli.One_Time_Rev_Q3_2018__c = 0;
                        oli.One_Time_Rev_Q4_2018__c = 0;
                        oli.One_Time_Rev_Q1_2019__c = 0;
                        oli.One_Time_Rev_Q2_2019__c = 0;
                        oli.One_Time_Rev_Q3_2019__c = 0;
                        oli.One_Time_Rev_Q4_2019__c = oli.TotalPrice;
                        lstOLIUpdate.add(oli);              
                    }//END if 2019 Q4
                    if(opp.CloseDate > Date.valueOf('2019-12-31')){
                        oli.One_Time_Rev_Q1_2018__c = 0;
                        oli.One_Time_Rev_Q2_2018__c = 0;
                        oli.One_Time_Rev_Q3_2018__c = 0;
                        oli.One_Time_Rev_Q4_2018__c = 0;
                        oli.One_Time_Rev_Q1_2019__c = 0;
                        oli.One_Time_Rev_Q2_2019__c = 0;
                        oli.One_Time_Rev_Q3_2019__c = 0;
                        oli.One_Time_Rev_Q4_2019__c = 0;

                        lstOLIUpdate.add(oli);
                    }

                 }//END if(oli.One_Time_Revenue__c == true)                     
             }//END For(OpportunityLineItem)
         }//END for(Opportunity)
//Update Products if there are any to update
if(lstOLIUpdate.size()>0){
    update lstOLIUpdate;
}
}//END if(RecusiveTriggerHandler.isFirstTime)
}//END Class

Test Class (Never enters the If Statement):

@isTest
public class UpdateOTRevTest {

    public static testMethod void testOppOneTimeRevenue(){

        Product2 prod2= new Product2(Name = 'Test Product - OneTime',
                                     Product_Gross_Margin__c = 10,
                                     Delivery_Market__c = 'none' ,
                                     Delivery_Region__c = 'ABCD',
                                     Delivery_Site__c = 'none' ,
                                     Family = 'XTCD',
                                     Description = 'Test Product',
                                     IsActive = true,
                                     Project_Seat_Count__c = 'false',
                                     One_Time_Revenue__c = true);
        insert prod2;
        Id pricebookId = Test.getStandardPricebookId();
        PricebookEntry pbEntry = new PricebookEntry(Pricebook2Id = pricebookId,
                                                    product2Id = prod2.Id,
                                                    isActive = true,
                                                    UnitPrice = 1000);
        insert pbEntry;

        Opportunity opp = new Opportunity(Name = 'Test Account - Opportunity',
                                          StageName = '1 - Prospect',
                                          Type = 'New Logo', 
                                          CloseDate = System.Date.today(),
                                          LeadSource = 'BDD/AM/OPS Development',
                                          Referral_Channel__c = 'None',
                                          Auto_Generate_Quarterly_Forecast__c = 'Yes',
                                          Contract_Length_Months__c = 24,
                                          Pricebook2Id = pricebookId,
                                          Amount = 10000000);
        insert opp;

        OpportunityLineItem oli = new OpportunityLineItem(OpportunityId = opp.id,
                                                          PricebookEntryId = pbEntry.id,
                                                          Product2Id = prod2.id,
                                                          Quantity = 10, 
                                                          Unit_Price2__c = 20,
                                                          Adjusted_Gross_Margin_Percentage__c = 10,
                                                          Service_Channel__c = 'WIDGET',
                                                          Service_Type__c = 'Care',
                                                          One_Time_Revenue__c = true);
        insert oli;

        System.debug('Opportunity Product Total Price '+ oli.TotalPrice);
        System.debug('OpportunityLineItem Id ' +oli.Id);
        System.debug('Product 2 Id '+ oli.Product2Id);
        System.debug('Opportunity Line Item Name : '+ oli.Name);
        System.debug('One Time Revenue : '+oli.One_Time_Revenue__c);
        Date closeDate = System.today();
        opp.CloseDate = Date.newInstance(2018, 01, 25);

        update opp;
        System.debug('Opp Close Date : '+opp.CloseDate);
    }//END Method
}//END Class

Test Coverage:

TEST Class Code Coverage

It is almost as if the test class does not recognize that there are products on the Opportunity, but there is a OpportunityId on the OpportunityLineItem Record.

1 Answer 1

1

This is an issue with your recursion control method.

Your Opportunity trigger is being run twice in your unit test. Once when you insert your test Opportunity, and a second time when you update your test Opportunity. As this happens in the same transaction (the unit test method), your static boolean is not reset, and when you update your test opp, you're skipping the entire trigger because you've already tripped your recursion protection.

To make your test work as intended, you'll need to re-set that RecursiveTriggerHandler.isFirstTime to true sometime after inserting your Opportunity, but before updating it.

+edit:
As an extra note, I'm not sure if using test.startTest() and test.stopTest() would be of any help here.

That said, this problem illustrates another issue with using a single static boolean value to control recursion. What happens if you're loading data in bulk, and have more than 200 Opportunities to insert or update?

In that case, your recursion control will allow the first trigger group of 200 records through, and block execution for all other records.

A more robust (but still imperfect) solution is to use a static Set<Id>. As records are processed, you add their Id into the set. Before executing code, check to see which Ids have already been processed, and only continue with the unprocessed records.

Set<Id> unprocessedIds = new Set<Id>();
for(Opportunity opp :trigger.new){
    if(!RecursionControl.alreadyProcessedIds.contains(opp.Id)){
        unprocessedIds.add(opp.Id);
    }
}

RecursionControl.alreadyProcessedIds.addAll(unprocessedIds);

for(Id oppId :unprocessedIds){
    Opportunity newMapOpp = trigger.newMap.get(oppId);

    // Other trigger code here

}

You must log in to answer this question.

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