0

I have built following apex trigger code and isAfter code is not executing because I have used recursive method in this trigger.

Is it possible to run isAfter while trigger call first time ?

Please see below code apex trigger code and please tell me what I have did wrong in this code :

trigger CreateOrderForOpportunity on Opportunity (before insert, after insert) {
    Map<ID,Schema.RecordTypeInfo> rt_Map = Opportunity.sObjectType.getDescribe().getRecordTypeInfosById();    
    if(checkRecursive.checkOneTime()){

        //Map<ID,Schema.RecordTypeInfo> rt_Map = Opportunity.sObjectType.getDescribe().getRecordTypeInfosById();

        for(Opportunity opp : trigger.New){

            if(rt_map.get(opp.recordTypeID).getName().contains('CONECTIVIDAD') && opp.Coming_from_Portal__c == True){
                if(trigger.isBefore){
                    if(opp.Owner_Email_Address_coming_from_portal__c != Null){
                        List<user> uList = [select Id, Email from user where Email =: opp.Owner_Email_Address_coming_from_portal__c Limit 1];
                        if(uList.size() > 0)
                            opp.OwnerId = uList.get(0).Id;
                    }
                    if(opp.Portal_Account_Name__c != Null){
                        List<Account> AccList = [select Id, Name from Account where Name =: opp.Portal_Account_Name__c Limit 10000];
                        if(AccList.size() > 0){
                            opp.AccountId = AccList.get(0).Id;
                        }
                    }
                }

            }

        }

    }
    for(Opportunity oppAfter : trigger.New){
        if(rt_map.get(oppAfter.recordTypeID).getName().contains('CONECTIVIDAD') && oppAfter.Coming_from_Portal__c == True){
            if(trigger.isAfter){
                SBQQ__Quote__c quote = new SBQQ__Quote__c();
                quote.Quote_Type__c = 'Final';
                quote.SBQQ__Opportunity2__c = oppAfter.Id;
                quote.Requerimientos_del_cliente__c = 'PORTAL';
                quote.Primary_Contact__c = [Select Id from Contact where AccountId =: oppAfter.AccountId Order By createdDate Limit 1].Id;
                insert quote;

                if(oppAfter.Quote_line_Product_Name__c != Null){
                    for(Product2 p : [select Id, Name from Product2 where Name =: oppAfter.Quote_line_Product_Name__c]){
                        SBQQ__QuoteLine__c qli = new SBQQ__QuoteLine__c();
                        qli.SBQQ__Product__c = p.Id;
                        qli.SBQQ__SpecialPrice__c = oppAfter.Product_Special_Price_coming_from_portal__c;
                        qli.SBQQ__Quote__c = quote.Id;
                        insert qli;
                    }

                }
                SBQQ__QuoteLine__c qliNew = new SBQQ__QuoteLine__c();
                qliNew.SBQQ__Product__c = '01t7000000Wt7xj';
                qliNew.SBQQ__Quote__c = quote.Id;
                insert qliNew;
            }
        }
    }
}

Recursive Class :

public Class checkRecursive{
    private static boolean check = true;

    public static boolean checkOneTime(){
        if(check){
            check = false;
            return true;
        }
        else{
            return check;
        }
    }

}
2
  • 1
    can you post the code behind checkRecursive.checkOneTime() method Commented Sep 3, 2017 at 20:25
  • @SantanuBoral updated post with code behind recursion. Commented Sep 4, 2017 at 6:59

1 Answer 1

2

AfterInsert contexts in triggers will ALWAYS be called in transactions following a BeforeInsert operation. If you want the After portion of code in your method to be called, simply relocate where you have the if(checkRecursive.checkOneTime()) to where it will only cover the AfterInsert code and not the code for the entire trigger.

It's important to understand that recursive calls occur in an After context not in a Before context (unless it's a Before Update) since you're not inserting new records, but instead, you're only updating them.

To solve the issue, move the recursion check from where it's located now and relocate it as follows:

// end of for(Opportunity opp : trigger.New) loop
}
if(checkRecursive.checkOneTime()){
for(Opportunity oppAfter : trigger.New){
    if(rt_map.get(oppAfter.recordTypeID).getName().contains('CONECTIVIDAD') && oppAfter.Coming_from_Portal__c == True){
    //...

That having been said, your code isn't bulk safe and could easily fail because of too many SOQL queries. You're going to want to create maps to bulkify your code with and consolidate your queries to make it more efficient.

As an example, in your initial loop, you have the following repeated query to return users to assign an opportunity ownerId:

        if(rt_map.get(opp.recordTypeID).getName().contains('CONECTIVIDAD') && opp.Coming_from_Portal__c == True){
            if(trigger.isBefore){
                if(opp.Owner_Email_Address_coming_from_portal__c != Null){
                    List<user> uList = [select Id, Email from user where Email =: opp.Owner_Email_Address_coming_from_portal__c Limit 1];
                    if(uList.size() > 0)
                        opp.OwnerId = uList.get(0).Id;
                }

In this situation, you'll want to create list<string>userEmail to which you add all opp.Owner_Email_Address_coming_from_portal__c != null. Once you have that, you can then run a query that looks like this:

map<Id,user>userId2user = new map<Id,user>([SELECT Email FROM User WHERE Email IN: userEmail]);

Once you have the above map, you can easily directly convert it to a map of userId2userEmail if you want to by using a for loop on the userId2user.keyset().

You can use the same approach to bulkifying the other loop that contains Account as well as for the queries in your afterInsert code.

Edit - in response to comments...

The issue would appear to be caused by the way your class handles the change from true to false in checkOneTime(). You initially have the boolean check set to true. Your method tests to see if it's true. If it is, it sets the value of check equal to false and then return true. Instead, it needs to be returning the current value of check (true) so the the trigger can run once before it changes the value of check to false. Once it does that, the else statement will always return check as equal to 'false'.

Your Class/method needs to look more like this:

public Class checkRecursive{
    private static boolean check = true;

    public static boolean checkOneTime(){
        if(check){
            check = false;
            return check;
        }else{
            return check;
        }
    }
}

Or (from sfdcfox in another answer)

public Class checkRecursive{
    static Boolean flag = true;

    public static Boolean checkOneTime() {
        return flag && !(flag = false);
    }
}
4
  • I tried the way you explain to get solved but not sure why still isAfter is not working. i put debug log to check it is going inside the if(trigger.isAfter) and it's not going inside the condition. can you please help. if you want me to show my code then i will update my question and put my updated code. can i ?? Commented Sep 4, 2017 at 12:31
  • See the edits to my answer. If that doesn't resolve your issue, then edit your question with your revised code.
    – crmprogdev
    Commented Sep 4, 2017 at 14:23
  • @crmprogdev - Moved your check = false above the return statement and added code from sfdcfox that seems more concise. Feel free to remove if you feel it is not appropriate.
    – Eric
    Commented Sep 4, 2017 at 14:50
  • @Eric Not an issue for me. I was using the OP's pattern so he'd be able to easily follow the logic error I was trying to correct. Personally, that's not a construct I'm particularly fond of, but to each their own. Just pleased he understood and hopefully is moving forward.
    – crmprogdev
    Commented Sep 4, 2017 at 20:41

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .