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
NewGirlNewGirl 

Trigger - multiple iterations when inserting multiple line items at once to opportunitylineitems

I have written a trigger to update the amount and quantity on the opportunitylineitems table.

It works fine if I am insertingonly one product to the opportunitylineitem.  However, if I am writting 2 or more products (line items) it runs through tthe trigger multiple times and I end up with incorrect data.

 

Any help would be appreciated!!

thanks,

Karen

 

 

 

 

trigger opplineitemstest on OpportunityLineItem (before insert, before update){

Set<Id> OrderLineIds= new Set<Id>();                
Set<Id> LineItemIds= new Set<Id>();
    
Set<String> PBEIdSet = new Set<String>();
Set<String> Prod2IdSet = new Set<String>();
       
    for (OpportunityLineItem insertedlineitem : system.Trigger.new)   
    {   

System.debug('INSIDE OPP FOR LOOP!!!'); 

        if (insertedlineitem.PricebookEntryId  != null) {
System.debug(insertedlineitem.PricebookEntryId );
            PBEIdSet.add(insertedlineitem.PricebookEntryId );
        }
        List<PricebookEntry> ParentPricebookEntry = [SELECT Product2Id           
                                            from PricebookEntry                            
                                            where id in :PBEIdSet];
        for(PricebookEntry pbes: ParentPricebookEntry) {            
            if (pbes.Product2Id != null) {
                System.debug('PROD ID ' + pbes.Product2Id);
                Prod2IdSet.add(pbes.Product2Id);   
            }
            List<Product2> ParentProd = [SELECT ProductCode
                                                   from Product2
                                                   where id in :Prod2IdSet];                                            
            for(Product2 prods: ParentProd) {
                System.debug('PROD CODE ' + prods.ProductCode);
                if(prods.ProductCode == 'AMS300' || prods.ProductCode == 'AMS305' || prods.ProductCode == 'AMS310-5PK' || prods.ProductCode == 'AMS350-5PK' || prods.ProductCode == 'AMS505' || prods.ProductCode == 'MDS405') {
                   System.debug('BEFORE QTY ' + insertedlineitem.quantity);
                   insertedlineitem.quantity = insertedlineitem.quantity * 5;
                   insertedlineitem.unitprice = insertedlineitem.unitprice / 5;
                    System.debug('AFTER QTY ' + insertedlineitem.quantity);
                                    
                }
         
                if(prods.ProductCode == 'AMS300-10PK' || prods.ProductCode == 'AMS305-10PK' || prods.ProductCode == 'AMS600' || prods.ProductCode == 'AMS604' || prods.ProductCode == 'AMS607' || prods.ProductCode == 'AMS612'|| prods.ProductCode == 'AMS700') {
                   insertedlineitem.quantity = insertedlineitem.quantity * 10;
                   insertedlineitem.unitprice = insertedlineitem.unitprice / 10;
            }
System.debug('77'); 
        }
        }
        system.debug('END OF OPPLINEITEM LOOP');
        }
        system.debug('end!!!!!');
        }

MarkWaddleMarkWaddle

The problem is that you are initializing the sets outside of the loop and then adding to them on every iteration of the loop. For item 1 the sets will have the IDs from item 1, for item 2 they will have the IDs for item 1 and 2, and so on.

 

You could move the intiialization of the sets into the for loop, however you will hit limits on the number of SOQL queries that can be run. I recommend refactoring a bit.

 

In pseudo code:

  1. Initialize PBEID set
  2. For loop through the line items. For each item, add PriceBookEntryID to PBEIdSet
  3. Close loop
  4. Create a set containing the product codes of concern (AMS300, etc.) for your two line item modifications. One for 5x modifications and one for 10x modifications.
  5. Query for matching product codes of concern directly from PriceBookEntry. For example: Map<Id, PriceBookEntry> fiveXEntries = new Map<Id, PriceBookEntry>([select Id from PriceBookEntry where Product2__r.ProductCode in :fiveXCodeSet and Id in :PBEIdSet ]); Map<Id, PriceBookEntry> tenXEntries = new Map<Id, PriceBookEntry>([select Id from PriceBookEntry where Product2__r.ProductCode in :tenXCodeSet and Id in :PBEIdSet ]);
  6. For loop through line items again. For each line item see if it is in one of the maps, and if so, modify the line item. For example: if (fiveXEntries.containsKey(insertedlineitem.PriceBookEntryId)) { // fivex modification logic here; } else if (tenXEntries.containsKey(insertedlineitem.PriceBookEntryId) { // tenx modification logic here; }
  7. Close loop

This will allow you to handle more than one item, and also limit your SOQL queries down to 2, instead of 2 * [number of line items].

 

Regards,

Mark

NewGirlNewGirl

Thanks for your help!! 

 

Just for a quick test I moved the initializing inside the for loop.  When there are multiple lines it processes the first line item once - (correctly), the second line item 2X (values are multiplied and divided by 5 - one too many times) and the third line item 3X -  (values are multiplied and divided by 5 - two too many times)

 

I wanted to test this before I start refactoring.

I have only written a few apex triggers so your experience is vary helpful!

 

Thanks again!

Karen

 

 

 

MarkWaddleMarkWaddle

Hi Karen,

 

After reading through your code again, I can see that each line item will only have one PriceBookEntry, and each PriceBookEntry will only have one Product2. This means there is no need for Sets in your "non-bulkified" trigger. So here is a cut at the non-bulkified version:

 

 

trigger opplineitemtest on OpportunityLineItem (before insert, before update) {
	for (OpportunityLineItem insertedlineitem : system.Trigger.new)   
	{
		System.debug('INSIDE OPP FOR LOOP!!!'); 

		if (insertedlineitem.PricebookEntryId  != null) {
			PricebookEntry pbe = [SELECT Product2Id           
									from PricebookEntry                            
									where id = :insertedlineitem.PricebookEntryId][0];
			if (pbe.Product2Id != null) {
				Product2 prods = [SELECT ProductCode
								   from Product2
								   where id = :pbes.Product2Id][0];
				System.debug('PROD CODE ' + prods.ProductCode);
				
				if(prods.ProductCode == 'AMS300' || prods.ProductCode == 'AMS305' || prods.ProductCode == 'AMS310-5PK' || prods.ProductCode == 'AMS350-5PK' || prods.ProductCode == 'AMS505' || prods.ProductCode == 'MDS405') {
				   System.debug('BEFORE QTY ' + insertedlineitem.quantity);
				   insertedlineitem.quantity = insertedlineitem.quantity * 5;
				   insertedlineitem.unitprice = insertedlineitem.unitprice / 5;
					System.debug('AFTER QTY ' + insertedlineitem.quantity);
				}
		 
				if(prods.ProductCode == 'AMS300-10PK' || prods.ProductCode == 'AMS305-10PK' || prods.ProductCode == 'AMS600' || prods.ProductCode == 'AMS604' || prods.ProductCode == 'AMS607' || prods.ProductCode == 'AMS612'|| prods.ProductCode == 'AMS700') {
				   insertedlineitem.quantity = insertedlineitem.quantity * 10;
				   insertedlineitem.unitprice = insertedlineitem.unitprice / 10;
				}
				System.debug('77'); 
			}
		}
		system.debug('END OF OPPLINEITEM LOOP');
	}
	system.debug('end!!!!!');
}

 

 

And here is a cut at a bulkified version:

 

trigger BulkifiedOpportunityLineItemTrigger on OpportunityLineItem (before insert, before update) {
	Map<Id, OpportunityLineItem> mapPBEIdToLineItem = new Map<Id, OpportunityLineItem>();
		   
	for (OpportunityLineItem insertedlineitem : system.Trigger.new)   
	{
		if (insertedlineitem.PricebookEntryId  != null) {
			mapPBEIdToLineItem.put(insertedlineitem.PricebookEntryId, insertedlineitem);
		}
	}
	
	if (!mapPBEIdToLineItem.isEmpty()) {
		Set<String> fiveXCodes = new Set<String>();
		fiveXCodes.add('AMS300');
		fiveXCodes.add('AMS305');
		fiveXCodes.add('AMS310-5PK');
		fiveXCodes.add('AMS350-5PK');
		fiveXCodes.add('AMS505');
		fiveXCodes.add('MDS405');
		
		Set<String> tenXCodes = new Set<String>();
		tenXCodes.add('AMS300-10PK');
		tenXCodes.add('AMS305-10PK');
		tenXCodes.add('AMS600');
		tenXCodes.add('AMS604');
		tenXCodes.add('AMS607');
		tenXCodes.add('AMS612');
		tenXCodes.add('AMS700');
	
		Map<Id, PricebookEntry> fiveXEntries = new Map<Id, PricebookEntry>([SELECT Id
												from PricebookEntry                            
												where id = :mapPBEIdToLineItem.keySet()
												and Product2__r.ProductCode in :fiveXCodes]);
												
		Map<Id, PricebookEntry> tenXEntries = new Map<Id, PricebookEntry>([SELECT Id
												from PricebookEntry                            
												where id = :mapPBEIdToLineItem.keySet()
												and Product2__r.ProductCode in :tenXCodes]);
		
		OpportunityLineItem item;
		for (Id pbeid : fiveXEntries.keySet()) {
			if (mapPBEIdToLineItem.containsKey(pbeId)) {
				System.debug('BEFORE QTY ' + item.quantity);
				item = mapPBEIdToLineItem.get(pbeId);
				item.quantity = item.quantity * 5;
				item.unitprice = item.unitprice / 5;
				System.debug('AFTER QTY ' + item.quantity);
			}
		}
		
		for (Id pbeid : tenXEntries.keySet()) {
			if (mapPBEIdToLineItem.containsKey(pbeId)) {
				System.debug('BEFORE QTY ' + item.quantity);
				item = mapPBEIdToLineItem.get(pbeId);
				item.quantity = item.quantity * 10;
				item.unitprice = item.unitprice / 10;
				System.debug('AFTER QTY ' + item.quantity);
			}
		}
	}
	system.debug('end!!!!!');
}

 
One problem still remaining with both of these solutions is that whenever you add new 5x or 10x product codes, you will have to update the trigger. You could get around that by adding an integer field directly on the Product2 object. The field could store the quantity multiplier for that Product2, and that multiplier could be used by the trigger. On the other hand, if you don't anticipate alot of product changes, having the codes hard-coded in the trigger might be fine for now.

 

Regards,

Mark

 

Rahul S.ax961Rahul S.ax961

Hi karen,

 

As per my understanding i have done the coding, avoiding queries inside the loop.

Hope it help and would be as per your requirement.

I dint understand the req. properly, was not able to test the code for you.

 

Trigger:

 

 

trigger opplineitemstest on OpportunityLineItem (before insert, before update)
{
	list<OpportunityLineItem> lstOppLineItem = new list<OpportunityLineItem>(); 
	Set<String> PBEIdSet = new Set<String>();
	Set<String> setProduct2 = new Set<String>();
	if(Trigger.isInsert)
	{
		for(OpportunityLineItem objOppLI: [Select PricebookEntryId From OpportunityLineItem where PricebookEntryId != null])
			PBEIdSet.add(objOppLI.PricebookEntryId);
	}
	if(Trigger.isUpdate && Trigger.new != Trigger.old)
	{
		for(OpportunityLineItem objOppLI: [Select PricebookEntryId From OpportunityLineItem where Id in: Trigger.new and PricebookEntryId != null])
			PBEIdSet.add(objOppLI.PricebookEntryId);
	}
	if(!PBEIdSet.isEmpty())
	{
		for(PricebookEntry objPBE : [Select Product2Id From PricebookEntry where Id in: PBEIdSet])
		{
			if(objPBE.Product2Id != null)
			{
				setProduct2.add(objPBE.Product2Id);
			}
		}
	}
	if(!setProduct2.isEmpty())
	{
		for(Product2 objP2:[SELECT Id, ProductCode from Product2 where Id in: setProduct2])
		{
			OpportunityLineItem insertedlineitem = new OpportunityLineItem();
			if(objP2.ProductCode == 'AMS300' || objP2.ProductCode == 'AMS305' || objP2.ProductCode == 'AMS310-5PK' || objP2.ProductCode == 'AMS350-5PK' || objP2.ProductCode == 'AMS505' || objP2.ProductCode == 'MDS405') 
			{
				insertedlineitem.quantity = insertedlineitem.quantity * 5;
				insertedlineitem.unitprice = insertedlineitem.unitprice / 5;		
			}

			if(objP2.ProductCode == 'AMS300-10PK' || objP2.ProductCode == 'AMS305-10PK' || objP2.ProductCode == 'AMS600' || objP2.ProductCode == 'AMS604' || objP2.ProductCode == 'AMS607' || objP2.ProductCode == 'AMS612'|| objP2.ProductCode == 'AMS700') 
			{
				insertedlineitem.quantity = insertedlineitem.quantity * 10;
				insertedlineitem.unitprice = insertedlineitem.unitprice / 10;
			}
			lstOppLineItem.add(insertedlineitem);
		}
	}
	if(!lstOppLineItem.isEmpty())
		update lstOppLineItem;
}

 

Hope it helps, and Let me know if you face any issue's