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
Ben Allington 7Ben Allington 7 

CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, LineItemCreation: maximum trigger depth exceeded

I have created two triggers to affect the opportunity line item and opprtunity. Keep getting thrown that error and unsure where my bulkification has gone wrong. 

LineItemCreation Trigger
trigger LineItemCreation on OpportunityLineItem (after insert, after update) {
    
    Product2 productsRef = new Product2();
    Opportunity oppRef = new Opportunity();
    String oliIdd;
    Decimal OliDayPrice;
    Integer daysDiff;
    List<OpportunityLineItemSchedule> OLISlist = new List<OpportunityLineItemSchedule>();
    
    for(OpportunityLineItem OLI :Trigger.new){
        
        
 	OLIidd = OLI.Id;
                try{
            productsRef = [SELECT Id, Basis__c, Family FROM Product2 WHERE
  							Id =:OLI.Product2Id]; 
                }
                catch(Exception e){
                     System.debug('The following exception has occurred: ' + e.getMessage());
                }
            oppRef = [SELECT Id, Contract_Start_Date__c, Go_Live_Date__c,
                                  Contract_End_Date__c FROM Opportunity
                                  WHERE Id=:OLI.OpportunityId];
        
            

            daysDiff = oppRef.Contract_Start_Date__c.daysBetween(oppRef.Contract_End_Date__c);
            
            System.Debug('DaysDifference = ' + daysDiff);
            OliDayPrice = (OLI.TotalPrice / daysDiff);
			System.Debug('Dailys Schedule Record Price = ' + oliDayPrice);
            
            Decimal totalPaid = 0;
    }
            for(Integer i=0; i < daysDiff; i++){
                
            if((productsRef.Basis__c =='Per Site Per Month' || productsRef.Basis__c =='Per Device Per Month') && productsRef.Family=='Licensing'){
                	OpportunityLineItemSchedule OLISl = new OpportunityLineItemSchedule(
                    OpportunityLineItemId=OLIidd,
                    Revenue=OliDayPrice,
                    ScheduleDate=(oppRef.Contract_Start_Date__c + i), //issue with it adding on revenue to saleprice
                    Type='Revenue');
                    OLISlist.add(OLISl);
            }   
            }
    
        if(trigger.isInsert || trigger.isUpdate){
                if(!OLISlist.isEmpty()){
            	insert OLISlist;
                System.debug('List Inserted:' + OLISlist); 
                }
            }
    }

OpportunityUpdate Trigger
 
trigger OpportunityUpdateTrigger on Opportunity (after update) {
    
    List<Product2> getProducts = new List<Product2>();
    List<Opportunity> oldOpp = new List<Opportunity>();
    List<Opportunity> newOpp = new List<Opportunity>();
    List<OpportunityLineItemSchedule> getSchedules = new List<OpportunityLineItemSchedule>();
     List<OpportunityLineItem> OLIlist = new  List<OpportunityLineItem>();
    Date conStartNew;
    Date conStartOld;
    Date conEndNew;
    Date conEndOld;
    Date goLiveNew;
    Date goLiveOld;
    
        set<Id> getOLI = new set<Id>();
        set<Id> OLIid = new set<Id>();
    
	for(Opportunity old : trigger.old){
        //get oli and add to list
        conStartOld = old.Contract_Start_Date__c;
        conEndOld = old.Contract_End_Date__c;
        goLiveOld = old.Go_Live_Date__c;
        for(OpportunityLineItem OLI : [SELECT Id, Product2Id, Quantity FROM OpportunityLineItem WHERE 
                                       OpportunityId=:old.Id]){
            getOLI.add(OLI.Product2Id);
            OLIid.add(OLI.Id);
            OLIlist.add(OLI); 
                                   
                                       }
        //GET PRODUCTS ADD TO LIST
	for(Product2 p : [SELECT Id, Basis__c, Family FROM Product2 WHERE 
                      Id=:getOLI AND Family='Licensing' AND (Basis__c='Per Device Per Month'
                      OR Basis__c='Per Site Per Month')]){
                          
                getProducts.add(p);
                System.debug('products : ' + getProducts);
                          
                              }
	//GET ALL REVENUE SCHEDULES RELATED TO OLI
    for(OpportunityLineItemSchedule s : [SELECT Id, ScheduleDate FROM OpportunityLineItemSchedule
                                         WHERE OpportunityLineItemId = :OLIid]){
                                             System.debug('OLI ID =' + OLIid);
                                             getSchedules.add(s);
                                             System.debug('Schedules =' + getSchedules);
                                         }
    }
                                                                            
                                           
    System.debug('s = ' + getSchedules);
            
                for(Opportunity o : trigger.new){
                    conStartNew = o.Contract_Start_Date__c;
                    conEndNew = o.Contract_End_Date__c;
    				goLiveNew = o.Go_Live_Date__c;
                }
    
    if(Trigger.isUpdate){
            if(conStartNew != conStartOld || goLiveNew != goLiveOld ||
               conEndNew != conEndOld){
                System.debug('Date changed!');
				Delete getSchedules;
                System.debug('Schedules Removed');
                for(OpportunityLineItem x : [SELECT Id FROM OpportunityLineItem 
                                             WHERE Id=:OLIid]){
                                              update x;    
                                             }   
                   }  
                   
        }
}

 
NagendraNagendra (Salesforce Developers) 
Hi Ben,

Please check with below solution which was posted on stack exchange community for similar issue.

After staring at your triggers for a fair amount of time, I think I've come up with a plausible explanation.

Background:
The relationship between OpportunityLineItem (OLI for short) and Opportunity isn't quite a Master-Detail relationship (Salesforce does some funky stuff with standard objects), but it allows you to create roll-up summary fields on Opportunity that summarize a field on OpportunityLineItem.

From the documentation on trigger order of execution (emphasis mine):

If the record contains a roll-up summary field or is part of a cross-object workflow, performs calculations and updates the roll-up summary field in the parent record. Parent record goes through save procedure.

The Problem:

It breaks down to the fact that you have an implicit loop in your two triggers. An update to one updates the other.

So, say we're starting with an update to an OLI. Things run as expected, until we get to step 16 in the order of execution. Now, the parent Opportunity goes through a save, kicking off its trigger.

Where things go wrong is your Opportunity trigger then causes an update on OLIs. Thus, we have a loop, and it continues until you've hit the max trigger depth (16 levels of nested triggers, if memory serves).

The Solution:
The easiest way out of this problem is to remove the DML update of OLIs in your Opportunitytrigger. This would cut the feedback loop, ensuring that your triggers only flow one way (i.e. from OLIto Opportunity). This probably isn't possible in your situation, as it appears that you're using dates on the Opportunity to drive logic on OLI.

The next easiest solution is to track which OLIs have already been updated using a separate apex class with a static Set<Id>.
The extra class only needs to be 3 lines long.
 
public class OLITracker{
    public static Set<Id> alreadyUpdated = new Set<Id>();
}
The reason why this will work is that a static variable inside of a class will retain its data throughout the entire transaction (so if we set the data in the OLI trigger, it'll still be available in the Opportunity trigger). We must use a separate class for this, as static variables in triggers behave differently (and are nearly useless, imo).

Once you have that, you'll need to make the following changes to your triggers:
 
OLI trigger:
trigger LineItemCreation on OpportunityLineItem (after insert, after update) {

    Product2 productsRef = new Product2();
    Opportunity oppRef = new Opportunity();
    String oliIdd;
    Decimal OliDayPrice;
    Integer daysDiff;
    List<OpportunityLineItemSchedule> OLISlist = new List<OpportunityLineItemSchedule>();

    // If this is an update, we want to note which OLIs we've seen.
    // We won't be using this information in this trigger, but it's important to 
    //   do this here.
    if(Trigger.isUpdate){
        OLITracker.alreadyUpdated.addAll(trigger.newMap.keySet());
    }

    // Code below here remains the same
}
Opportunity Trigger:
trigger OpportunityUpdateTrigger on Opportunity (after update) {

    // code before this point remains the same

    Map<Id, OpportunityLineItem> olisToUpdate;
    if(Trigger.isUpdate){
        if(conStartNew != conStartOld || goLiveNew != goLiveOld || conEndNew != conEndOld){
            System.debug('Date changed!');
            Delete getSchedules;
            System.debug('Schedules Removed');

            // DML in a loop is as bad as a query in a loop.
            // Store the query in a map.
            // This will make it easy to remove the OLIs that have already been
            //   updated.
            olisToUpdate = new Map<Id, OpportunityLineItem>([SELECT Id FROM OpportunityLineItem WHERE Id=:OLIid]);

            // Removing a key from a map will remove the value for that key
            //   from the map as well.
            // This could be done in the query above, but it would require a
            //   'NOT IN', which I believe makes the query non-selective.
            // Doing things this way should never cause issues.
            olisToUpdate.keySet().removeAll(OLITracker.alreadyUpdated);

            // Finally, we can run the update.
            update olisToUpdate.values();
        }  

    }
}
Please mark this as solved so that it gets removed from the unanswered queue and others can get benefitted out of that who are looking with similar issue.

Thanks,
Nagendra.