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
Benny Stevens 1Benny Stevens 1 

Trigger OLI creates duplicate record

Hi,

I think I have this simple trigger on an Opportunity Product with a Status of Approved, to create a record in a custom object FPS, but when it meets the criteria, it creates 2 records.

Strange thing is that after that, when the Opportunity Product is updated again and it still meets the criteria, that it then creates only 1 record. Also when I have removed the first 2 records that have been created ... It is only the first time the trigger fires for that Opportunity Product, that it will create 2 records.

Thanks,
Benny

trigger CreateFPS on OpportunityLineItem (after update) {

    for (OpportunityLineItem u : Trigger.new) {
        if (u.Status__c == 'Approved') {
            FPS__c x = new FPS__c();
            x.Opportunity__c = u.OpportunityId;
            x.Sales_Price__c = u.UnitPrice;
            x.Phase__c = 'Nomination';
            
        insert x;
        
        }
    }
}
Best Answer chosen by Benny Stevens 1
bob_buzzardbob_buzzard
You need to stop the trigger executing again while in the same transction.  This recipe shows how to achieve this :

http://developer.force.com/cookbook/recipe/controlling-recursive-triggers

All Answers

bob_buzzardbob_buzzard
That sounds like you have another trigger or workflow field update that only fires the first time that the OLI is updated.  Workflow field update feels like the most likely to me, as its a straightforward thing to set up a rule that only fires when the record is in a particular state.

The debug log should help in this situation - it will show you all the triggers/workflow etc that are being evaluated for the OLI.

Out of curiosity - do you want to create another record each time the OLI is updated - I would have thought you just wanted it when the OLI transitions to the 'Approved' status, rather than every time it is edited and the status equals 'Approved'.
Shyam BhundiaShyam Bhundia
Do you have a trigger on the FPS__c object which may be updating the OpportunityLineItem and calling the trigger again?

Also, you shouldn't have a DML statement in a for loop.. With your code above, if you had 200 new OpportunityLineItem inserted in one go (e.g. via dataloader) you will very quickly hit governor limits.

You can improve the code like this:

trigger CreateFPS on OpportunityLineItem (after update) {
	//create a list of FPS to insert
	List<FPS__c> fpsToInsert = new List<FPS__c>();
    
    for (OpportunityLineItem u : Trigger.new) {
        if (u.Status__c == 'Approved') {
            FPS__c x = new FPS__c();
            x.Opportunity__c = u.OpportunityId;
            x.Sales_Price__c = u.UnitPrice;
            x.Phase__c = 'Nomination';

            //add new FPS to list
            fpsToInsert.add(x);
        }
    }

    //insert list if not empty
    if(!fpsToInsert.isEmpty()){
    	insert fpsToInsert;
    }
}


Benny Stevens 1Benny Stevens 1
Thanks bob_buzzard, I will look in my WFR's  more tomorrow and check the debug log.
No, I only want to create a record the first time it reaches that Approved Status.
I just started with APEX since a few weeks and it was going to be the next thing to find out :-)

Thanks Shyam, great help!
I don't have any triggers so far on the FPS object.
Benny Stevens 1Benny Stevens 1
Hi bob_buzzard,

You are correct, I have 4 WFR's on OLI that are updating fields when the status is equal to Approved and some other criteria.
I have deactivated them and then it only create 1 record for the custom object.

I still need those WFR's. How can I avoid that the trigger creates multiple records, while I still have the WFR active?

I am looking into "if(trigger.isupdate) and oldmap" to avoid that it triggers everytime it is updated when the status is Approved.

Thanks,
Benny
Benny Stevens 1Benny Stevens 1
I avoided to create a record each time the OLI is updated
trigger CreateFPS on OpportunityLineItem (after update) {

    //create a list of FPS to insert
    List<FPS__c> FPSToInsert = new List<FPS__c>();
    
    if(trigger.isUpdate){
    
    for (OpportunityLineItem u : Trigger.new) {
        
        if (u.Status__c == 'Approved' && u.Status__c != trigger.oldmap.get(u.id).Status__c){
     
            FPS__c x = new FPS__c();
            x.Opportunity__c = u.OpportunityId;
            x.Sales_Price__c = u.UnitPrice;
            x.Phase__c = 'Nomination';

            //add new FPS to list
            FPSToInsert.add(x);
                   
        }
         
    }
    
    }
    
    //insert list if not empty
    if(!FPSToInsert.isEmpty()){
        insert FPSToInsert;
        
    }    

}

I need to have the WFR with field updates active. How can I avoid that a duplicate record is update because of the active WFR's?

Thanks, Benny

bob_buzzardbob_buzzard
You need to stop the trigger executing again while in the same transction.  This recipe shows how to achieve this :

http://developer.force.com/cookbook/recipe/controlling-recursive-triggers
This was selected as the best answer
Benny Stevens 1Benny Stevens 1
Thanks to the recipe (and some info on stackexchange) I got it working after two days :-)

One more hurdle to take - I hope - to get the trigger working correctly ... 
There is a workflow rule that updates the unit price of the OLI, but the record for the custom object FPS is added before the workflow rule is updating the OLI.
So the FPS record is taking the old unit price, while it should take over the WFR updated unit price!

I have chosen AFTER UPDATE, but this doesn't prevent that the record is added before the WFR is updating the Sales Price field.

Trigger

trigger AutoCreateFPS on OpportunityLineItem (after update) {

    if (!FPSHelper.hasAlreadyRunMethod()) {

    List<FPS__c> FPSToInsert = new List<FPS__c>();
    
        if(trigger.isUpdate){
        
        for (OpportunityLineItem u : Trigger.new) {             
            if (u.Status__c == 'Approved' && u.Status__c != trigger.oldmap.get(u.id).Status__c){    
                FPS__c x = new FPS__c();
                x.Opportunity__c = u.OpportunityId;
                x.Sales_Price__c = u.UnitPrice;
                x.Phase__c = 'Nomination';
    
                FPSToInsert.add(x);

            }
        }
        }
    
        FPSHelper.setAlreadyRunMethod();
        insert FPSToInsert;
        
    }    

}



Class

public class FinancialProjectStatusHelper {

    public static boolean hasAlreadyRun = false;
    
    public static boolean hasAlreadyRunMethod(){
        return hasAlreadyRun;
    }
    public static void setAlreadyRunMethod(){
        hasAlreadyRun = true;
    }

}


bob_buzzardbob_buzzard
Yeah, that's a tough one - the trigger will have the view of the records before the field update, as the order of execution states that triggers run before field updates.  The field update will make triggers run again, but you'd be trying to skip the first update on the assumption that there will be a second one. If someone then deactivates the workflow rule your trigger wouldn't take any action.  You might be better moving the field update into the trigger.