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
Nathan Wylder 6Nathan Wylder 6 

Optimize trigger calculating difference between Lead stage in business hours

Help! I'm new to coding and am probably doing this all wrong. How could I optimize the code block below.

What I need to acheive is calculating the difference in business hours that a lead spends in each lead stage from New,  Working, Nurturing, and lastly, Qualified. I wrote the trigger which works but is incomplete (needs to prevent negative values)

I want the trigger (before update) to calls a class & method that does all of this whenever a lead record is updated, but I don't know how. How do I package all of the logic into an apex class that I can call with this trigger?
 
trigger LeadTracking on Lead (before update) {

    //Selecting default business hours (BH) record
    BusinessHours defaultBH = [SELECT Id FROM BusinessHours WHERE IsDefault = true Limit 1];
	//Making sure BH record exists
    if(defaultBH != NULL){
        for(Lead myLead : trigger.new ){
            
            //CALCULATE NEW calculateNewDateTime()
            ////Making sure that New Date Time and Working Date Time are not null
            if(myLead.New_Date_Time__c != NULL ) {
             	if(myLead.Working_Date_Time__c != NULL) {
				//For BH method we assign (BH record id, start time field, end time field)
				decimal result = BusinessHours.diff(defaultBH.Id, myLead.New_Date_Time__c, myLead.Working_Date_Time__c );
				//Result from the method is divided by 60*60*100 (milliseconds to be then converted into hours)
				Decimal resultingHours = result/(60*60*1000);
				//Populating result into our custom field & setting number of decimals
				myLead.New_Business_Hours_2__c = resultingHours.setScale(2); 
            
            //calculates difference between New Date Time and Nurturing Date Time
            	} else if (myLead.New_Date_Time__c != NULL && myLead.Working_Date_Time__c == NULL && myLead.Nurturing_Date_Time__c != NULL) {
                decimal result = BusinessHours.diff(defaultBH.Id, myLead.New_Date_Time__c, myLead.Nurturing_Date_Time__c );
				Decimal resultingHours = result/(60*60*1000);
				myLead.New_Business_Hours_2__c = resultingHours.setScale(2);  
            
            //Calculates difference between New Date Time and Qualified
            	} else if (myLead.New_Date_Time__c != NULL && myLead.Nurturing_Date_Time__c == NULL && myLead.Working_Date_Time__c == NULL && myLead.Qualified_Date_Time__c != NULL) {
                decimal result = BusinessHours.diff(defaultBH.Id, myLead.New_Date_Time__c, myLead.Qualified_Date_Time__c );
				Decimal resultingHours = result/(60*60*1000);
				myLead.New_Business_Hours_2__c = resultingHours.setScale(2);
            
            //if New Date Time is not null but all the other Date Times are Null - then have the calculation be made from NOW()
            	} else if (myLead.New_Date_Time__c != Null && myLead.Nurturing_Date_Time__c == NULL && myLead.Working_Date_Time__c == NULL && myLead.Qualified_Date_Time__c == NULL) {
                decimal result = BusinessHours.diff(defaultBH.Id, myLead.New_Date_Time__c, datetime.now());
				Decimal resultingHours = result/(60*60*1000);
				myLead.New_Business_Hours_2__c = resultingHours.setScale(2);
            	} //else //if (myLead.New_Date_Time__c == NULL) 
            	//{ myLead.New_Business_Hours_2__c = NULL;
				//}
			}
                
            
            //CALCULATE WORKING
            //calculates difference between working and nurturing if both are not null
            if (myLead.Working_Date_Time__c != Null ){
                if(myLead.Nurturing_Date_Time__c != Null) {
                decimal result = BusinessHours.diff(defaultBH.Id, myLead.Working_Date_Time__c, myLead.Nurturing_Date_Time__c);
				Decimal resultingHours = result/(60*60*1000);
				myLead.Working_Business_Hours_2__c = resultingHours.setScale(2);
            //calculates difference between working and Qualified if Nurturing is null but Working and Qualified are not null   
            } else if (myLead.Working_Date_Time__c != Null && myLead.Qualified_Date_Time__c != Null && myLead.Nurturing_Date_Time__c == NULL) {
				decimal result = BusinessHours.diff(defaultBH.Id, myLead.Working_Date_Time__c, myLead.Qualified_Date_Time__c);
				Decimal resultingHours = result/(60*60*1000);
				myLead.Working_Business_Hours_2__c = resultingHours.setScale(2);
            //calculates difference between working and Now() if working is not null but nurturing and qualified are 
            } else if (myLead.Working_Date_Time__c != Null && myLead.Nurturing_Date_Time__c == Null && myLead.Qualified_Date_Time__c == Null) {
                decimal result = BusinessHours.diff(defaultBH.Id, myLead.Working_Date_Time__c, datetime.now());
				Decimal resultingHours = result/(60*60*1000);
				myLead.Working_Business_Hours_2__c = resultingHours.setScale(2);
            } //else // (myLead.Working_Date_Time__c == Null) 
            	//{myLead.Working_Business_Hours_2__c = Null; 
                //}
            }
            
            //CALCULATE NURTURING    
            //calculates difference between nurturing and qualified if both are not null
            if (myLead.Nurturing_Date_Time__c != Null) {
                if(myLead.Qualified_Date_Time__c != Null) {
            	decimal result = BusinessHours.diff(defaultBH.Id, myLead.Nurturing_Date_Time__c, myLead.Qualified_Date_Time__c);
				Decimal resultingHours = result/(60*60*1000);
				myLead.Nurturing_Business_Hours_2__c = resultingHours.setScale(2);
            //calculates difference between nurturing and Now() if qualified is null
            	} else if (myLead.Nurturing_Date_Time__c != Null && myLead.Qualified_Date_Time__c == Null) {
                decimal result = BusinessHours.diff(defaultBH.Id, myLead.Nurturing_Date_Time__c, datetime.now());
				Decimal resultingHours = result/(60*60*1000);
				myLead.Nurturing_Business_Hours_2__c = resultingHours.setScale(2);
           		} //else //(myLead.Nurturing_Date_Time__c == NULL) 
                //{myLead.Nurturing_Business_Hours_2__c = 0.00;
            	//}  
        	}
            //if new, working or nurturing date times are null, there is no calculation of business hours for it
            if (myLead.New_Date_Time__c == NULL) {
                myLead.New_Business_Hours_2__c = NULL;
            }
            if (myLead.Working_Date_Time__c == NULL) {
                myLead.Working_Business_Hours_2__c = NULL;
            }
            if (myLead.Nurturing_Date_Time__c == NULL){
                myLead.Nurturing_Business_Hours_2__c = NULL;
            }
            
    	}    
	}
}



 
 
pconpcon
I think you can simplify this if you create a utility class (called BHUtils in my code below) and then reorder some of your checks.
 
public class BHUtils {
    @TestVisible
    private static BusinessHours defaultBH {
        get {
            if (defaultBH == null) {
                defaultBH = [
                    select Id
                    from BusinessHours
                    where IsDefault = true
                    limit 1
                ];
            }

            return defaultBH;
        }
    }

    public static Decimal getTimeBetween(Datetime d1, Datetime d2) {
        if (d1 == null || d2 == null) {
            return null;
        }

        Decimal difference = BusinessHours.diff(defaultBH.Id, d1, d2);
        Decimal resultingHours = result / (60 * 60 * 1000);
        return resultingHours.setScale(2);
    }

    public static Boolean hasDefaultBH() {
        return defaultBH != null;
    }
}

Then update your trigger to be
 
trigger LeadTracking on Lead (before update) {
    if (!BHUtils.hasDefaultBH()) {
        return;
    }

    for (Lead myLead : trigger.new) {
        Boolean hasNew = myLead.New_Date_Time__c != null;
        Boolean hasWorking = myLead.Working_Date_Time__c != null;
        Boolean hasNurturing = myLead.Nurturing_Date_Time__c != null;
        Boolean hasQualified = myLead.Qualifed_Date_Time__c != null;


        if (!hasNew) {
            myLead.New_Business_Hours_2__c = null;
        } else {
            DateTime startTime = myLead.New_Date_Time__c;
            DateTime endTime;

            if (hasWorking) {
                endTime = myLead.Working_Date_Time__c;
            } else (hasNurturing) {
                endTime = myLead.Nurturing_Date_Time__c;
            } else (hasQualified) {
                endTime = myLead.Qualified_Date_Time__c
            }

            myLead.New_Business_Hours_2__c = BHUtils.getTimeBetween(startTime, endTime);
        }

        if (!hasWorking) {
            myLead.Working_Business_Hours_2__c = null;
        } else {
            DateTime startTime = myLead.Working_Date_Time__c;
            DateTime endTime;

            if (hasNurturing) {
                endTime = myLead.Nurturing_Date_Time__c;
            } else (hasQualified) {
                endTime = myLead.Qualified_Date_Time__c
            }

            myLead.Working_Business_Hours_2__c = BHUtils.getTimeBetween(startTime, endTime);
        }   

        if (!hasNurturing) {
            myLead.Nurturing_Business_Hours_2__c = null;
        } else {
            DateTime startTime = myLead.Working_Date_Time__c;
            DateTime endTime;
            
            if (hasQualified) {
                endTime = myLead.Qualified_Date_Time__c
            }   
                
            myLead.Nurturing_Business_Hours_2__c = BHUtils.getTimeBetween(startTime, endTime);
        }       
    }
}
NOTE: The code above has not been tested and may contain typographical and / or logical errors.
 
Nathan Wylder 6Nathan Wylder 6
@pcon, thank you for taking the time to reply! I see what you're doing here. How do I structure an apex class so that I can wrap ALL of the calculating (all of the if statements and being done here) into its own class so that the trigger is even more bare than this? I ask because 1. it would greatly simplify the trigger 2. I'd like to have a reusable class for when I may need to do this kind of calculation on another object in the future.

I've read that trigger best practices are to have only 1 trigger per object, and if we have other triggers to add to leads in the future, it would be nice to have them all be on oneso that we can control the order in which they fire.