You need to sign in to do that
Don't have an account?
Qingsong Li
the trigger in line items is:
and then apex class is:
the problems is when I change the stage to lost or cancelled (the probability 0%), the trigger will be fired twice, below is the debug information:
so this problem is caused the lost/cancelled opportunity can not have line items more than 50, my question is there any reason to cause the trigger been fire twice just for lost/canclled oppotunity? how to skip duplicated fire trigger, any suggestion on the code improvement? Thanks a lot
Why 0% Prabability Opportunity Trigger been fired twice
I have a trigger on opportunity level, and then a trigger on opportunity line item level, the trigger is working well for all pipeline stage with non 0% prabability when the line items less than 100 because the DML update statement limited,
The trigger in opportunity level is:
Code:
trigger OpportuntiStageUpdate on Opportunity (after update) { //Determine the opportuntiy been updated ID[] sid=New ID[trigger.new.size()]; for(Opportunity s : trigger.new){ sid.add(s.id); } OpportunityAfterTrigger.OppsUpdate(sid); }
the trigger in line items is:
Code:
trigger OpportuntiLineItemUpdate on OpportunityLineItem (before update, before insert) { // Determine the ooportuntiyLineItem been updated // Get the opportuntiy ID which will be updated Set<ID> pbeIds = new Set<ID>(); Set<ID> prodIds = new Set<ID>(); Set<String> currCode = new Set<String>(); for (OpportunityLineItem oli1 : Trigger.new){ pbeIds.add(oli1.OPPORTUNITYID); prodIds.add(oli1.pricebookentryid); currCode.add(oli1.CURRENCYISOCODE); } //Now map the closedate,probability,channels Map<Id, Opportunity> opps= new Map<Id, Opportunity>( [select CLOSEDATE, PROBABILITY, Channel__c, Manual_or_Derived__c from Opportunity where id In : pbeIds]); Map<Id, PricebookEntry> entries = new Map<Id, PricebookEntry> ( [select product2.Description from pricebookentry where id In : prodIds]); Map<String, FX__C> fxTable = new Map<String, FX__C> (); for (FX__C newFX :[select Name, Exchange_Rate__C from FX__C where To_Currency__C In : currCode]){ fxTable .put(newFX.Name,newFX); } for (OpportunityLineItem oli : Trigger.new){ //Pending for DRI Release 1.0 //Set BP Number to Null if Non-Derived Opps if (Trigger.isInsert && opps.get(oli.OPPORTUNITYID).Manual_or_Derived__c=='M'){ oli.BP_Number__C=''; } if (oli.Applicaton__C > 100 || oli.Business__C>100){ oli.Applicaton__C.addError('Application % and Business Number % must less than or equal to 100%'); } //Update Quantity for Derived Opps If (opps.get(oli.OPPORTUNITYID).Manual_or_Derived__c=='D' && oli.Vechile_Volume__C <>0 && oli.Applicaton__C<>Null && oli.Business__C <>Null && oli.Number_Per_Application__C <>0){ oli.Quantity=math.Round(oli.Vechile_Volume__C * oli.Applicaton__C * oli.Business__C /10000*oli.Number_Per_Application__C) ; } //Below code should be disable when DRI release to production if (Trigger.isInsert){ oli.BP_Number__C=''; } //Update Description oli.Description = entries.get(oli.pricebookentryid).Product2.Description; if (oli.BOOK_EXPECTED_CLOSED_DATE__C == null) { oli.BOOK_EXPECTED_CLOSED_DATE__C = opps.get(oli.OPPORTUNITYID).CLOSEDATE; } //If no probability, pull the probability from header level if (oli.PROBABILITY__C == null) { oli.PROBABILITY__C = opps.get(oli.OPPORTUNITYID).PROBABILITY; } //Set All Probability to 100% when marked to win Set<String> won = new Set<String>(); won.add('Win'); won.add('Win-Carryover'); won.add('Win-Conquest'); won.add('Win-Extended Bookings'); if (won.contains(oli.STAGE__C)){ oli.PROBABILITY__C=100; //Check Project OI for non penetration channel if (oli.Projected_OI__c == null) { if (opps.get(oli.OPPORTUNITYID).Channel__c <> 'EEA - Penetration') { oli.Projected_OI__c.addError('Project OI is required when close a opportunity!'); } } } //Set All Probability to 0% when marked to lost/canceled/no quote Set<String> lost= new Set<String>(); lost.add('Lost'); lost.add('Lost-Business'); lost.add('Lost-Opportunity'); lost.add('Canceled'); lost.add('No Quote'); if (lost.contains(oli.STAGE__C)){ oli.PROBABILITY__C=0; } //Lost opportunity sets Set<String> lose= new Set<String>(); lose.add('Lost'); lose.add('Lost-Business'); lose.add('Lost-Opportunity'); if (lose.contains(oli.STAGE__C) && oli.Other_Competitor__c == null){ oli.Other_Competitor__c.addError('Other competitor must be fulfilled for lost opportunity'); } if (lose.contains(oli.STAGE__C) && oli.Winner__c == null){ oli.Winner__c.addError('Winner must be fulfilled for lost opportunity'); } //Win opportunity sets Set<String> win = new Set<String>(); win.add('Win'); win.add('Win-Carryover'); win.add('Win-Conquest'); win.add('Win-Extended Bookings'); if (win.contains(oli.STAGE__C) && oli.Other_Competitor__c == null){ oli.Other_Competitor__c.addError('Other competitor must be fulfilled for won opportunity'); } if (win.contains(oli.STAGE__C) && oli.Projected_OI__c == null){ oli.Projected_OI__c.addError('Project OI % must be fulfilled for won opportunity'); } //Non open stage sets Set<String> open = new Set<String>(); open.add('Win'); open.add('Win-Carryover'); open.add('Win-Conquest'); open.add('Win-Extended Bookings'); open.add('Lost'); open.add('Lost-Business'); open.add('Lost-Opportunity'); open.add('Canceled'); open.add('No Quote'); if (oli.Decision_Factors_1__c == null && open.contains(oli.STAGE__C)) { oli.Decision_Factors_1__c.addError('Close a opportunity must have Decision factor'); } if (oli.Incumbent_Supplier__c == null && open.contains(oli.STAGE__C)) { oli.Incumbent_Supplier__c.addError('Close a opportunity must have Incumbent Supplier'); } oli.CLOSE_REASON_DESCRIPTION__C = 'trigger from items'; //Update Exchange Rate base on the Calendar Year if (oli.CURRENCYISOCODE =='USD'){ oli.Exchange_Rate__C = 1.0; } else { String fromcurrCode ='USD'; String tocurrCode = oli.CURRENCYISOCODE; String calendarYear = '2017'; If (oli.CALENDAR_YEAR__C.year()<=2017) { calendarYear = ''+(oli.CALENDAR_YEAR__C.year()); } String fxName = fromcurrCode + '-' + tocurrCode + '-' + calendarYear; //FX__C[] fx = [Select Exchange_Rate__C FROM FX__C Where From_Currency__C=: fromcurrCode and To_Currency__C =: tocurrCode and Year__C =: calendarYear]; //if (fx.size()>0) { // oli.Exchange_Rate__C = 1 / fx[0].Exchange_Rate__C; //} if(fxTable.containsKey(fxName)){ oli.Exchange_Rate__C = 1 / fxTable.get(fxName).Exchange_Rate__C; } } } }
and then apex class is:
Code:
public class OpportunityAfterTrigger{ public static Boolean inLineItemTrigger; Static testMethod void sampleOppsTestMethod() { Opportunity testOpps = [SELECT Id FROM Opportunity LIMIT 1]; ID[] sid=New ID[]{testOpps.Id}; //sid.add(testOpps.id[0]); OpportunityAfterTrigger.OppsUpdate(sid); } //@future (callout = true) public static void OppsUpdate(ID[] sid){ //Service__c sd = new Service__c(); for(Opportunity nw : [Select Id,StageName,Probability,CloseDate,Additional_Comments__C, INCUMBENT_SUPPLIER__C,OTHER_COMPETITOR__C,WINNER__C,DECISION_FACTORS_1__C,PROJECTED_OI__C From Opportunity Where Id in :sid]){ //Get Opportunity ID Set<ID> iOppsID = New Set<ID>(); iOppsID.add(nw.Id); //Lost stage sets Set<String> lost= new Set<String>(); lost.add('Lost'); lost.add('Lost-Business'); lost.add('Lost-Opportunity'); lost.add('Canceled'); lost.add('No Quote'); //Win stage sets Set<String> won = new Set<String>(); won.add('Win'); won.add('Win-Carryover'); won.add('Win-Conquest'); won.add('Win-Extended Bookings'); //Anticipated stage set Set<String> Anticipated= new Set<String>(); Anticipated.add('Anticipated'); //Open Line Items for update OpportunityLineItem[] items = [select STAGE__C,PROBABILITY__C,Id, OpportunityID,PRICEBOOKENTRYID,NUMBER_PER_APPLICATION__C, Applicaton__c,BUSINESS__C, INCUMBENT_SUPPLIER__C, OTHER_COMPETITOR__C,WINNER__C,DECISION_FACTORS_1__C,PROJECTED_OI__C FROM OpportunityLineItem where OpportunityId In : iOppsID]; for (OpportunityLineItem a: items){ //Set all product line items to lost/canceled/no quote if header level marked to lost if (lost.contains(nw.StageName)){ a.STAGE__C = nw.StageName; a.PROBABILITY__C = nw.Probability; a.CLOSE_REASON_DESCRIPTION__C = nw.Additional_Comments__C; a.BOOK_EXPECTED_CLOSED_DATE__C = nw.CloseDate; } //Set open product line itmes to win if header level marked to win else if (won.contains(nw.StageName)){ if (a.STAGE__C == 'Anticipated') { a.STAGE__C = nw.StageName; a.PROBABILITY__C = nw.Probability; a.CLOSE_REASON_DESCRIPTION__C = nw.Additional_Comments__C; a.BOOK_EXPECTED_CLOSED_DATE__C = nw.CloseDate; } } //Set open product line items else if (a.STAGE__C == 'Anticipated') { a.PROBABILITY__C = nw.Probability; a.BOOK_EXPECTED_CLOSED_DATE__C = nw.CloseDate; } if (a.INCUMBENT_SUPPLIER__C == null && nw.INCUMBENT_SUPPLIER__C != null) {a.INCUMBENT_SUPPLIER__C=nw.INCUMBENT_SUPPLIER__C;} if (a.OTHER_COMPETITOR__C == null) {a.OTHER_COMPETITOR__C=nw.OTHER_COMPETITOR__C;} if (a.WINNER__C == null) {a.WINNER__C=nw.WINNER__C;} if (a.DECISION_FACTORS_1__C == null) {a.DECISION_FACTORS_1__C=nw.DECISION_FACTORS_1__C;} if (a.PROJECTED_OI__C == null) {a.PROJECTED_OI__C=nw.PROJECTED_OI__C;} } try { update items; } catch (System.StringException e){ System.debug(e); } } } }
the problems is when I change the stage to lost or cancelled (the probability 0%), the trigger will be fired twice, below is the debug information:
Code:
*** Beginning OpportuntiBPFlagUpdate on Opportunity trigger event BeforeUpdate for 006S0000002FF3p 20081103142806.509:Trigger.OpportuntiBPFlagUpdate: line 6, column 5: SelectLoop:LIST:SOBJECT:Opportunity Cumulative resource usage: Resource usage for namespace: (default) Number of SOQL queries: 0 out of 20 Number of query rows: 0 out of 1000 Number of SOSL queries: 0 out of 0 Number of DML statements: 0 out of 20 Number of DML rows: 0 out of 100 Number of script statements: 46 out of 10200 Maximum heap size: 0 out of 100000 Number of callouts: 0 out of 10 Number of Email Invocations: 0 out of 10 Number of fields describes: 0 out of 10 Number of record type describes: 0 out of 10 Number of child relationships describes: 0 out of 10 Number of picklist describes: 0 out of 10 Number of future calls: 0 out of 10 Number of find similar calls: 0 out of 0 Number of System.runAs() invocations: 0 out of 0 Total email recipients queued to be sent : 0 *** Ending OpportuntiBPFlagUpdate on Opportunity trigger event BeforeUpdate for 006S0000002FF3p *** Beginning OpportuntiStageUpdate on Opportunity trigger event AfterUpdate for 006S0000002FF3p ......................... No profiling information for SOSL operations. 1 most expensive DML operations: Class.OpportunityAfterTrigger.OppsUpdate: line 83, column 9: Update: LIST:SOBJECT:OpportunityLineItem: executed 2 times in 449 ms 1 most expensive method invocations: Class.OpportunityAfterTrigger: line 11, column 24: public static void OppsUpdate(LIST:Id): executed 2 times in 525 ms
so this problem is caused the lost/cancelled opportunity can not have line items more than 50, my question is there any reason to cause the trigger been fire twice just for lost/canclled oppotunity? how to skip duplicated fire trigger, any suggestion on the code improvement? Thanks a lot