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

problem with Trigger after delete

I've written a trigger that works perfectly after inserts and after updates.  The problem is after delete which can only use Trigger.old.


The trigger fires on the OpportunityLineItem and sets a value on the opportunity when a line item is inserted or updated.  I want to add the same process to fire when a line item is deleted, so the field value on the opportunity is updated correctly.


I've tried using if (Trigger.IsDelete) and looking at the trigger old values, but the obvious problem is it is still looking at the old values and not the new values that no longer includes the deleted line item.


For example, the trigger sets a value on the opportunity based on specific criteria defined by precedence (which criteria occurs first).  If that line item is deleted, then all the line items need to be iterated again to set the appropriate value on the opportunity (what is the new criteria that occurs first) after the line item has been deleted.


Is there a way to make this happen in the trigger?  It would seem, that I need to re-fire the  trigger after a line item has been deleted, so that I can examine the new line items after the delete occurred.


Is it possible to do this thru a workflow maybe?


Here is my existing trigger without the after delete:


trigger OpportunityLineItemVertical on OpportunityLineItem (after insert, after update) { OpportunityLineItem oli = [Select Opportunity.Id, Opportunity.Account.Id From OpportunityLineItem Where Id in Limit 1]; List<OpportunityLineItem> olis = [Select Id, PricebookEntry.Product2.Name, PricebookEntry.Product2.Product_Line__c From OpportunityLineItem Where OpportunityId = :oli.Opportunity.Id]; for (OpportunityLineItem x : olis) { system.debug('Name = ' + x.PricebookEntry.Product2.Name); system.debug('Product Line = ' + x.PricebookEntry.Product2.Product_Line__c); } system.debug('Opportunity Id = ' + oli.Opportunity.Id); system.debug('Account Id = ' + oli.Opportunity.Account.Id); List<Id> pbeIds = new List<Id>(); for (Integer i = 0; i < Trigger.size; i++) { pbeIds.add([i].PricebookEntryId); } for (Id i : pbeIds) { system.debug('Pricebook Entry Ids = ' + pbeIds); } List<PricebookEntry> pbes = [Select Product2Id From PricebookEntry Where Id in :pbeIds]; List<Id> prodIds = new List<Id>(); for (PricebookEntry pbe : pbes) { prodIds.add(pbe.Product2Id); } for (Id i : prodIds) { system.debug('Product Ids = ' + prodIds); } List<Product2> products = [Select Name, Product_Line__c From Product2 Where Id in :prodIds]; Account acct = [Select Local_Region__c, Market_Segment__c, Industry From Account Where Id = :oli.Opportunity.Account.Id]; Opportunity op = [Select OwnerId From Opportunity Where Id = :oli.Opportunity.Id]; system.debug('Owner Id = ' + op.OwnerId); User u = [Select ProfileId From User Where Id = :op.OwnerId]; system.debug('Profile Id = ' + u.ProfileId); Profile p = [Select Name From Profile Where Id = :u.ProfileId]; Boolean foundMarketplace = false; Boolean foundTeleAtlas = false; Boolean foundStoreSystems = false; Boolean foundLogisticsMgmt = false; try { for (OpportunityLineItem oppLine : { for (OpportunityLineItem o : olis) { if (o.PricebookEntry.Product2.Name == 'JDA Marketplace Replenish') { FoundMarketPlace = true; } } for (OpportunityLineItem o : olis) { if (o.PricebookEntry.Product2.Name == 'Tele Atlas') { foundTeleAtlas = true; } } for (OpportunityLineItem o : olis) { if (o.PricebookEntry.Product2.Product_Line__c == 'Store Operations') { foundStoreSystems = true; } } for (OpportunityLineItem o : olis) { if (o.PricebookEntry.Product2.Product_Line__c == 'Logistics Management') { foundLogisticsMgmt = true; } } if (p.Name.contains('Alliance')) { op.JDA_Vertical__c = 'VAR'; update op; break; } else if (acct.Local_Region__c != null && acct.Local_Region__c.contains('Latin America')) { op.JDA_Vertical__c = 'Latin America'; update op; break; } else if (foundMarketplace) { op.JDA_Vertical__c = 'Marketplace'; update op; break; } else if (foundTeleAtlas) { op.JDA_Vertical__c = 'Transportation'; update op; break; } else if (foundStoreSystems) { op.JDA_Vertical__c = 'Store Systems'; update op; break; } else if (foundLogisticsMgmt) { op.JDA_Vertical__c = 'Transportation'; update op; break; } else if (acct.Market_Segment__c != null && acct.Market_Segment__c.startsWith('Grocery')) { op.JDA_Vertical__c = 'Grocery/Drug'; update op; break; } else if (acct.Market_Segment__c != null && acct.Market_Segment__c.contains('Hardlines')) { op.JDA_Vertical__c = 'Retail Hardlines'; update op; break; } else if (acct.Market_Segment__c != null && acct.Market_Segment__c.contains('Softlines')) { op.JDA_Vertical__c = 'Retail Softlines'; update op; break; } else if (acct.Industry != null && acct.Industry == 'Manufacturing') { op.JDA_Vertical__c = 'Consumer Goods'; update op; break; } else if (acct.Industry != null && acct.Industry == 'Wholesale/Distribution') { op.JDA_Vertical__c = 'Wholesale/Distribution'; update op; break; } else { op.JDA_Vertical__c = 'Unknown'; update op; break; } } } catch (Exception ex) { Opportunity opp = [Select Error_Message__c From Opportunity Where Id = :oli.Opportunity.Id]; opp.Error_Message__c = ex.GetMessage(); update opp; return; } }



Can't you use "before Delete" rather then using "after Delete"?



I tried using BeforeDelete as well and was not able to get that to work either.  BeforeDelete only has Trigger.old available as well.


Any thoughts?


I've run into this "recalc after delete" problem myself.  I think the only way to do this is in the after trigger for lineitem, and in this way: use a relationship query to select all the opps that were affected by the line item deletions and at the same time select all the existing line items for each opp.  Like this:


select Id, FieldToUpdate, (select Id, PrecedenceField from OpportunityLineItem) from Opportunity where ID in :Trigger.oldMap.values()


Then you loop through the lineitems for each opp, calc the new value, and update the opp field.

You don't need to re-fire the trigger because you're doing all the recalcuation within the trigger.


You might run into governor limits because of the query size or the looping, so you might have to use asynchronous mode (@future) or batch Apex.


Message Edited by dmcheng on 12-03-2009 01:55 PM

Hi dmcheng,


Thanks for the help.  So, when I run the relationship query, Trigger.oldMap.values() will retrieve the opportunities from the line items including the deleted line items, or will it not include the deleted line items?  I was trying to write the relationship query in my trigger like this:


if (Trigger.IsDelete) { List<OpportunityLineItem> olis = [select Id, JDA_Vertical__c, (select Id, from OpportunityLineItem) from Opportunity where ID in :Trigger.oldMap.values()]; }


I'm getting the error: unexpected token: 'from'


Do you have an example, I could take a look at? 






In this case, you have a trailing comma in your field list, so you get the error message. But that's not really what you're trying to do anyways. Instead, construct a list of Opportunity ID values (i.e. Set<Id> ), query the opportunities and their line items, iterate through those items to find the new values, and then call Database.update() to set the new values in the opportunities.


Alternatively, you might consider just moving your trigger to the Opportunity level (therefore, a recalc would occur each time the opportunity is updated), and then create a "stub" trigger that simply invokes the opportunity trigger. That would work a lot like this:



trigger updateOpps on Opportunity (before update) { List<Opportunity> opps = [select id, (select id, from opportunitylineitems) from opportunity where id in]; for(Opportunity o:opps) { for(opportunitylineitem oli:o.opportunitylineitems) { if( conditions ) { Trigger.newMap.get(oli.opportunityid).Field__c = newValue; // do updates here. } } } } trigger updateOLI on OpportunityLineItem (after insert, after update, after delete) { Map<Id,Opportunity> opps = new Map<id,opportunity>(); if(Trigger.isDelete) { for(opportunitylineitem oli:trigger.old) { opps.put(oli.opportunityid,new opportunity(id=oli.opportunityid)); } } else { for(opportunitylineitem { opps.put(oli.opportunityid,new opportunity(id=oli.opportunityid)); } } update opps; }

This consolidates your logic to the upper level (the opportunity), to allow you to consistently update your opportunities. Do note that the Critical Updates might cause your trigger to run twice, so you may want to test that condition.


Also, I don't understand why you have multiple consecutive for-loops working on the same "olis" object.  Is there some reason you can't do all those if statements within a single for-loop?