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
E.J.RE.J.R 

Apex trigger only fires when developer console is open or trace flags are set.

This has happened on several occasions in the past and it's happening again today.

I created a fairly simple 'before insert' trigger for opportunity products. When I test the trigger under the administrator account, with the developer console on, the code runs perfectly as intended. But when I close the developer console window, the trigger no longer fires.

The same thing happens for different users as well. If I set a trace flag for a particular user, the trigger fires just fine when logged in as that user. But after deleting the trace flag, the trigger stops firing. I can duplicate this behavior back and forth over and over.

If anyone could point me in the right direction, I would really appreciate it. Right now it's a struggle finding any relevant info.

Note sure if this will help but here's the code for the trigger:
trigger OpportunityProductInsert on OpportunityLineItem (before insert)
{
    // List of all line items IDs before being inserted
    List<Id> oliIds = new List<Id>();
    
    // List of all opportunity IDs of the line items
    List<Id> oliOppIds = new List<Id>();
    
    // List of all product IDs associated to the line items
    List<Id> oliProductIds = new List<Id>();
    
    for ( OpportunityLineItem newOli : trigger.new )
    {
        oliIds.add(newOli.Id);
        oliOppIds.add(newOli.OpportunityId);
        oliProductIds.add(newOli.Product2Id);
    }

    // Map to retrieve the account ID for each opportunity
    Map<Id, Id> oliAccountIdMap = new Map<Id, Id>();    
    List<Opportunity> opps = [SELECT Id, AccountId FROM Opportunity WHERE Id IN: oliOppIds];
    for ( Opportunity opp : opps )
    {
        oliAccountIdMap.put(opp.Id, opp.AccountId);
    }
    
    // List of all product IDs that are inventory items
    List<Id> inventoryProductIds = new List<Id>();    
    List<Product2> products = [SELECT Id FROM Product2 WHERE IsNonInventory__c = false AND Id IN: oliProductIds];
    for ( Product2 product : products )
    {
        inventoryProductIds.add(product.Id);
    }
    
    // Map to retrieve a list of available products for each account
    Map<Id, List<Id>> accProductMap = new Map<Id, List<Id>>();
    List<Id> accProductIds;
    List<AccountProduct__c> accProducts = [SELECT Account__c, Product__c FROM AccountProduct__c WHERE Account__c IN: oliAccountIdMap.values()];
    for ( AccountProduct__c accProduct : accProducts )
    {
        accProductIds = new List<Id>();
        if ( accProductMap.containsKey(accProduct.Account__c) )
        {
            accProductIds = accProductMap.get(accProduct.Account__c);
        }
        accProductIds.add(accProduct.Product__c);
        accProductMap.put(accProduct.Account__c, accProductIds);
    }
    
    // Check that the product is available on the account
    for ( OpportunityLineItem checkOli : trigger.new )
    {
        accProductIds = new List<Id>();        
        Id accId;
        
        // Retrieve the account ID from the line item ID        
        if ( oliAccountIdMap.containsKey(checkOli.OpportunityId) )
        {
            accId = oliAccountIdMap.get(checkOli.OpportunityId);            
        }    
            
        if ( accId != null )
        {
            // If the product ID on the line item is in the list of inventory items
            if ( inventoryProductIds.contains(checkOli.Product2Id) )
            {                
                accProductIds = new List<Id>();
                
                // Retrieve list of available products on the account
                if ( accProductMap.containsKey(accId) )
                {
                    accProductIds = accProductMap.get(accId);         
                }
                
                // If the product ID is not in the list of account product IDs            
                if ( !accProductIds.contains(checkOli.Product2Id) )
                {
                    checkOli.addError('This product isn\'t available for this account.');
                }                                    
            }
        }
    }
}
Best Answer chosen by E.J.R
Abdul KhatriAbdul Khatri
I like your approach on shortening the code but it contains too many nexted for loops which amy cause for Heap for bulkification data. Here is my version see if this can help you out 

My code is little longer but it is bulkified and must work in case of massive data. Take a look and let me know if you see any issue anywhere.
 
trigger OpportunityProductInsert on OpportunityLineItem (before insert) {

    Map<Id, Id> oppAcctMap = new Map<Id, Id>();
    Map<Id, List<Id>> oppProductMap = new Map<Id, List<Id>>();
    Map<Id, Set<Id>> acctProductMap = new Map<Id, Set<Id>>();
    Map<String, OpportunityLineItem> incomingProductMap = new Map<String, OpportunityLineItem>();
    
	for ( OpportunityLineItem newOli : trigger.new )
	{
        incomingProductMap.put(string.valueOf(newOli.OpportunityId) + String.ValueOf(newOli.Product2Id), newOli);
            
        if(!OppProductMap.containsKey(newOli.OpportunityId)) 
            OppProductMap.put(newOli.OpportunityId, new List<id>{ newOli.Product2Id });
        else 
        {        
         	List<id> idList = OppProductMap.get(newOli.OpportunityId);
         	idList.add(newOli.Product2Id);
         	OppProductMap.put(newOli.OpportunityId, idList);
        }
       
	}    
    
    for(Opportunity oppRecord : [SELECT Id, AccountId FROM Opportunity WHERE Id IN :OppProductMap.keySet()]) {
		OppAcctMap.put(oppRecord.Id, oppRecord.AccountId);
    }
    
    for(AccountProduct__c accProducts : [SELECT Account__c, Product__c FROM AccountProduct__c WHERE Account__c IN :oppAcctMap.values()]) {

        if(!acctProductMap.containsKey(accProducts.Account__c)) 
            acctProductMap.put(accProducts.Account__c, new Set<Id>{ accProducts.Product__c });
        else 
        {        
         	Set<id> idSet = acctProductMap.get(accProducts.Account__c);
         	idSet.add(accProducts.Product__c);
         	acctProductMap.put(accProducts.Account__c, idSet);
        }
        
    }

	for(Id idOpp : OppProductMap.keySet()) 
    {
        for(Id idProd : OppProductMap.get(idOpp))
        {
           	Id AccountId = OppAcctMap.get(idOpp);
            if(!acctProductMap.get(accountId).Contains(idProd))
                incomingProductMap.get(String.valueOf(idOpp) + String.valueOf(idProd)).addError('This product isn\'t available for this account.');
            	
        }
    }
}



 

All Answers

Abdul KhatriAbdul Khatri
Please let me know if the below image make sense to you

User-added image
E.J.RE.J.R
Thanks a lot Abdul, that does make sense. Though it still confuses me why the trigger seems to work fine when the developer console or trace flag is set. I'll fix the trigger by avoiding the use of that ID, and will let you know if that fixes the problem.
Abdul KhatriAbdul Khatri

Sure

Not sure what you mean it work fine through developer console. What are you trying in Developer Console what make you feel it is workign fine. Can you share the code?

E.J.RE.J.R

The issue is still happening after fixing the problem with the ID above. The trigger only fires when the trace flag is set for the user. Please see the screenshots below.

1) Without the trace flag set on the user, I'm able to add the product just fine and the trigger doesn't seem to fire:
User-added image

2) Here you can see the product was added to the opportunity:
User-added image

3) After that, I added the trace flag for the user:
User-added image

4) Once the trace flag is set, the trigger fires properly and prevents the product from being added to the opportunity:
User-added image
Here's the updated trigger code:

trigger OpportunityProductInsert on OpportunityLineItem (before insert)
{
	// List of all opportunity IDs of the line items
	List<Id> oliOppIds = new List<Id>();
	
	// List of all product IDs associated to the line items
	List<Id> oliProductIds = new List<Id>();
	
	for ( OpportunityLineItem newOli : trigger.new )
	{
		oliOppIds.add(newOli.OpportunityId);
		oliProductIds.add(newOli.Product2Id);
	}

	// Map to retrieve the account ID for each opportunity
	Map<Id, Id> oppAccountIdMap = new Map<Id, Id>();	
	List<Opportunity> opps = [SELECT Id, AccountId FROM Opportunity WHERE Id IN: oliOppIds];
	for ( Opportunity opp : opps )
	{
		oppAccountIdMap.put(opp.Id, opp.AccountId);
	}
	
	// List of all product IDs that are inventory items
	List<Id> inventoryProductIds = new List<Id>();	
	List<Product2> products = [SELECT Id FROM Product2 WHERE IsNonInventory__c = false AND Id IN: oliProductIds];
	for ( Product2 product : products )
	{
		inventoryProductIds.add(product.Id);
	}
	
	// Map to retrieve a list of available products for each account
	Map<Id, List<Id>> accProductMap = new Map<Id, List<Id>>();
	List<Id> accProductIds;
	List<AccountProduct__c> accProducts = [SELECT Account__c, Product__c FROM AccountProduct__c WHERE Account__c IN: oppAccountIdMap.values()];
	for ( AccountProduct__c accProduct : accProducts )
	{
		accProductIds = new List<Id>();
		if ( accProductMap.containsKey(accProduct.Account__c) )
		{
			accProductIds = accProductMap.get(accProduct.Account__c);
		}
		accProductIds.add(accProduct.Product__c);
		accProductMap.put(accProduct.Account__c, accProductIds);
	}
	
	// Check that the product is available on the account
	for ( OpportunityLineItem checkOli : trigger.new )
	{
		accProductIds = new List<Id>();		
		Id accId;
		
		// Retrieve the account ID from the line item ID		
		if ( oppAccountIdMap.containsKey(checkOli.OpportunityId) )
		{
			accId = oppAccountIdMap.get(checkOli.OpportunityId);			
		}	
			
		if ( accId != null )
		{
			// If the product ID on the line item is in the list of inventory items
			if ( inventoryProductIds.contains(checkOli.Product2Id) )
			{				
				accProductIds = new List<Id>();
				
				// Retrieve list of available products on the account
				if ( accProductMap.containsKey(accId) )
				{
					accProductIds = accProductMap.get(accId);		 
				}
				
				// If the product ID is not in the list of account product IDs			
				if ( !accProductIds.contains(checkOli.Product2Id) )
				{
					checkOli.addError('This product isn\'t available for this account.');
				}									
			}
		}
	}
}
E.J.RE.J.R
I still have no explanation for the behavior above, but I was able to fix the problem by rewriting the trigger to use loops instead of maps. My guess is that my original code was flawed in some way that caused an issue with the trace flags.

Working trigger code:
trigger OpportunityProductInsert on OpportunityLineItem (before insert)
{
	List<Id> oppIds = new List<Id>();
	List<Id> productIds = new List<Id>();
	for ( OpportunityLineItem newOli : trigger.new )
	{
		oppIds.add(newOli.OpportunityId);
		productIds.add(newOli.Product2Id);				
	}
	
	List<Id> accIds = new List<Id>();
	List<Opportunity> opps = [SELECT AccountId FROM Opportunity WHERE Id IN: oppIds];
	for ( Opportunity opp : opps )
	{
		accIds.add(opp.AccountId);
	}
		
	List<Product2> products = [SELECT Id FROM Product2 WHERE Id IN: productIds AND IsNonInventory__c = false];
	List<AccountProduct__c> accProducts = [SELECT Account__c, Product__c FROM AccountProduct__c WHERE Account__c IN: accIds];
	
	Boolean error = false;
	for ( OpportunityLineItem oli : trigger.new )
	{
		for ( Product2 product : products )
		{
			if ( product.Id == oli.Product2Id )
			{
				error = true;
				for ( AccountProduct__c accProduct : accProducts )
				{
					if ( product.Id == accProduct.Product__c )
					{
						error = false;	
					}
				}
			}
		}	
		if ( error )
		{
			oli.addError('This product isn\'t available for this account.');		
		}			
	}	
}


 
Abdul KhatriAbdul Khatri
I like your approach on shortening the code but it contains too many nexted for loops which amy cause for Heap for bulkification data. Here is my version see if this can help you out 

My code is little longer but it is bulkified and must work in case of massive data. Take a look and let me know if you see any issue anywhere.
 
trigger OpportunityProductInsert on OpportunityLineItem (before insert) {

    Map<Id, Id> oppAcctMap = new Map<Id, Id>();
    Map<Id, List<Id>> oppProductMap = new Map<Id, List<Id>>();
    Map<Id, Set<Id>> acctProductMap = new Map<Id, Set<Id>>();
    Map<String, OpportunityLineItem> incomingProductMap = new Map<String, OpportunityLineItem>();
    
	for ( OpportunityLineItem newOli : trigger.new )
	{
        incomingProductMap.put(string.valueOf(newOli.OpportunityId) + String.ValueOf(newOli.Product2Id), newOli);
            
        if(!OppProductMap.containsKey(newOli.OpportunityId)) 
            OppProductMap.put(newOli.OpportunityId, new List<id>{ newOli.Product2Id });
        else 
        {        
         	List<id> idList = OppProductMap.get(newOli.OpportunityId);
         	idList.add(newOli.Product2Id);
         	OppProductMap.put(newOli.OpportunityId, idList);
        }
       
	}    
    
    for(Opportunity oppRecord : [SELECT Id, AccountId FROM Opportunity WHERE Id IN :OppProductMap.keySet()]) {
		OppAcctMap.put(oppRecord.Id, oppRecord.AccountId);
    }
    
    for(AccountProduct__c accProducts : [SELECT Account__c, Product__c FROM AccountProduct__c WHERE Account__c IN :oppAcctMap.values()]) {

        if(!acctProductMap.containsKey(accProducts.Account__c)) 
            acctProductMap.put(accProducts.Account__c, new Set<Id>{ accProducts.Product__c });
        else 
        {        
         	Set<id> idSet = acctProductMap.get(accProducts.Account__c);
         	idSet.add(accProducts.Product__c);
         	acctProductMap.put(accProducts.Account__c, idSet);
        }
        
    }

	for(Id idOpp : OppProductMap.keySet()) 
    {
        for(Id idProd : OppProductMap.get(idOpp))
        {
           	Id AccountId = OppAcctMap.get(idOpp);
            if(!acctProductMap.get(accountId).Contains(idProd))
                incomingProductMap.get(String.valueOf(idOpp) + String.valueOf(idProd)).addError('This product isn\'t available for this account.');
            	
        }
    }
}



 
This was selected as the best answer
E.J.RE.J.R
Yes, I knew bulkfication would be a concern in the future but I wanted something that just worked at the time (without a trace flag set). However, I dropped your code in and made a few minors modifications and everything is bulkified. Thanks for taking time to write this for me. I'm still unclear on the exact cause of the behaviour before but I went ahead and marked your answer as the correct one, since it did indeed solve the problems.