+ Start a Discussion
JitendraJitendra 

How to write a piece of code which will run after specific Time

Hi,

I need to write a piece of code which will get the List of Opportunity difrentiated by some criteria and want to email those records every weekends.

Its like Time Based coading.

I have read about time dependent Workflow triggers but it doesnt resolves my problem because it does not provide flexibility to write the Apex code.

I want to run the code once in every week.

Can anyone give some suggestions?

Thanks in Advance!


Best Answer chosen by Admin (Salesforce Developers) 
jgrenfelljgrenfell
Jitendra,

I've been able to combine workflow rules and Apex code to accomplish what you're looking for.  I created a custom object called Scheduled Jobs which I use to set off the workflow rule.  The workflow rule is triggered by a checkbox field called "Schedule". One of the fields in Scheduled Jobs is "Next Run Datetime" and there's a workflow rule that is set to run 0 hours after that datetime.  When it runs, all it does is update a "Run Job" field which sets off the Apex code.  Each time a job is run, in addition to whatever code I want run, I have Apex insert a new Scheduled Job record with the Next Run Datetime and Schedule = true, so the process starts over again.  Since I have a few different jobs running this way, the Scheduled Job includes fields to indicate which method to run, the time it should be set off (I store it as military time and convert it to a datetime in the code).  I even have a job which deletes old Scheduled Job records so it doesn't get too cluttered. 

I included the code, minus some irrelevent bits, below.  The intMagicNum is something I had to include because some of the methods I run have to be run in chunks otherwise they hit Apex governor limits.  So, for instance, I have jobs that send out emails but I can only send out 10 emails at a time.  Each time it's run, the method is tracking which users have been emailed and only emails people that haven't received it yet. So when it's done, it returns how many emails it actually sent and if that number = 10, the job is scheduled to run again immediately (there's generally a 15 minute delay) until everyone has been emailed (less than 10 returned).  It's a workaround since Salesforce doesn't yet provide cron job-like functionality, but it works for now!  Maybe more information than you were looking for, but I hope this helps.

Jessie

Code:
trigger InsertUpdateScheduledJobAfter on Scheduled_Job__c (after insert, after update) {
 Integer intReturn;
 Integer intMagicNum;
 List<Scheduled_Job__c> sjList = new List<Scheduled_Job__c>();
 Datetime dte;
 
 for (Scheduled_Job__c sj : Trigger.new){
  if (sj.Run_Job__c == true){
   
   //Create Site Log records for 7 business days in the future, if needed
   if (sj.Method_To_Execute__c == 'CreateSiteLogs'){
    intReturn = clsScheduledJobs.CreateSiteLogs(null);
    //This job is run in batches of 1 site invoice (the max that can be processed
    //before hitting the governor limit, sadly), so less than 1 means it's done
    intMagicNum = 1;
   } 
   
   //Send out Job Loss Report
   if (sj.Method_To_Execute__c == 'JobLossReport'){
    intReturn = clsScheduledJobs.JobLossReport(null);
    //Single email limited to 10 at a time
    intMagicNum = 10;
   }
      
   if (sj.Method_To_Execute__c == 'ScheduledJobCleanup'){
    intReturn = clsScheduledJobs.ScheduledJobCleanup();
    //This job always returns 0, shouldn't need to be re-run anytime soon
    intMagicNum = 1;
   }
   
   Scheduled_Job__c new_sj = new Scheduled_Job__c(
    Method_To_Execute__c = sj.Method_To_Execute__c,
    Name = sj.Name,
    Run_Job__c = false,
    Schedule__c = true,
    Time_to_Start__c = sj.Time_to_Start__c);
   
   //If the number returned by the method is less than the Magic Number, 
   //create a Scheduled Job record to run it again tomorrow night 
   if (intReturn < intMagicNum){
    //Tomorrow, 12am
    dte = Datetime.newInstance(System.today().year(), System.today().month(), System.today().day()).addDays(1);
    
    //Add on hour/minutes from Time To Start field
    dte = dte.addhours(Integer.valueOf(sj.Time_to_Start__c.substring(0,2)));
    dte = dte.addminutes(Integer.valueOf(sj.Time_to_Start__c.substring(2,4)));
    
    //Sometimes if there are multiple scheduled jobs around the same time,
    //SF processes them in bulk and the governor limits are hit, so make sure 
    //they're all spaced out at least 15 minutes
    sjList = [Select Next_Run_Datetime__c from Scheduled_Job__c
       where Next_Run_Datetime__c >=: dte and  
       Next_Run_Datetime__c <: dte.addMinutes(30) and  
       Schedule__c = true and Run_Job__c = false
       order by Next_Run_Datetime__c DESC
       LIMIT 1];
    dte = (sjList.size() > 0 — sjList[0].Next_Run_Datetime__c.addMinutes(15) : dte);
    //Reset Scheduled Job record to midnight of tomorrow
    new_sj.Next_Run_Datetime__c = dte;
    
   //Otherwise, there are more records to process, so create a record 
   //to run it again immediately
   } else {
    //Make sure this is offset from other jobs so they don't run in bulk
    sjList = [Select Next_Run_Datetime__c from Scheduled_Job__c
       where Next_Run_Datetime__c >=: System.now().addMinutes(-15) and 
       Next_Run_Datetime__c <: System.now().addMinutes(30) and  
       Schedule__c = true and Run_Job__c = false
       order by Next_Run_Datetime__c DESC
       LIMIT 1];
    new_sj.Next_Run_Datetime__c = (sjList.size() > 0 – sjList[0].Next_Run_Datetime__c.addMinutes(15) : System.now());
    
   }
   
   insert new_sj;
  }
  
 }
}

 


All Answers

jgrenfelljgrenfell
Jitendra,

I've been able to combine workflow rules and Apex code to accomplish what you're looking for.  I created a custom object called Scheduled Jobs which I use to set off the workflow rule.  The workflow rule is triggered by a checkbox field called "Schedule". One of the fields in Scheduled Jobs is "Next Run Datetime" and there's a workflow rule that is set to run 0 hours after that datetime.  When it runs, all it does is update a "Run Job" field which sets off the Apex code.  Each time a job is run, in addition to whatever code I want run, I have Apex insert a new Scheduled Job record with the Next Run Datetime and Schedule = true, so the process starts over again.  Since I have a few different jobs running this way, the Scheduled Job includes fields to indicate which method to run, the time it should be set off (I store it as military time and convert it to a datetime in the code).  I even have a job which deletes old Scheduled Job records so it doesn't get too cluttered. 

I included the code, minus some irrelevent bits, below.  The intMagicNum is something I had to include because some of the methods I run have to be run in chunks otherwise they hit Apex governor limits.  So, for instance, I have jobs that send out emails but I can only send out 10 emails at a time.  Each time it's run, the method is tracking which users have been emailed and only emails people that haven't received it yet. So when it's done, it returns how many emails it actually sent and if that number = 10, the job is scheduled to run again immediately (there's generally a 15 minute delay) until everyone has been emailed (less than 10 returned).  It's a workaround since Salesforce doesn't yet provide cron job-like functionality, but it works for now!  Maybe more information than you were looking for, but I hope this helps.

Jessie

Code:
trigger InsertUpdateScheduledJobAfter on Scheduled_Job__c (after insert, after update) {
 Integer intReturn;
 Integer intMagicNum;
 List<Scheduled_Job__c> sjList = new List<Scheduled_Job__c>();
 Datetime dte;
 
 for (Scheduled_Job__c sj : Trigger.new){
  if (sj.Run_Job__c == true){
   
   //Create Site Log records for 7 business days in the future, if needed
   if (sj.Method_To_Execute__c == 'CreateSiteLogs'){
    intReturn = clsScheduledJobs.CreateSiteLogs(null);
    //This job is run in batches of 1 site invoice (the max that can be processed
    //before hitting the governor limit, sadly), so less than 1 means it's done
    intMagicNum = 1;
   } 
   
   //Send out Job Loss Report
   if (sj.Method_To_Execute__c == 'JobLossReport'){
    intReturn = clsScheduledJobs.JobLossReport(null);
    //Single email limited to 10 at a time
    intMagicNum = 10;
   }
      
   if (sj.Method_To_Execute__c == 'ScheduledJobCleanup'){
    intReturn = clsScheduledJobs.ScheduledJobCleanup();
    //This job always returns 0, shouldn't need to be re-run anytime soon
    intMagicNum = 1;
   }
   
   Scheduled_Job__c new_sj = new Scheduled_Job__c(
    Method_To_Execute__c = sj.Method_To_Execute__c,
    Name = sj.Name,
    Run_Job__c = false,
    Schedule__c = true,
    Time_to_Start__c = sj.Time_to_Start__c);
   
   //If the number returned by the method is less than the Magic Number, 
   //create a Scheduled Job record to run it again tomorrow night 
   if (intReturn < intMagicNum){
    //Tomorrow, 12am
    dte = Datetime.newInstance(System.today().year(), System.today().month(), System.today().day()).addDays(1);
    
    //Add on hour/minutes from Time To Start field
    dte = dte.addhours(Integer.valueOf(sj.Time_to_Start__c.substring(0,2)));
    dte = dte.addminutes(Integer.valueOf(sj.Time_to_Start__c.substring(2,4)));
    
    //Sometimes if there are multiple scheduled jobs around the same time,
    //SF processes them in bulk and the governor limits are hit, so make sure 
    //they're all spaced out at least 15 minutes
    sjList = [Select Next_Run_Datetime__c from Scheduled_Job__c
       where Next_Run_Datetime__c >=: dte and  
       Next_Run_Datetime__c <: dte.addMinutes(30) and  
       Schedule__c = true and Run_Job__c = false
       order by Next_Run_Datetime__c DESC
       LIMIT 1];
    dte = (sjList.size() > 0 — sjList[0].Next_Run_Datetime__c.addMinutes(15) : dte);
    //Reset Scheduled Job record to midnight of tomorrow
    new_sj.Next_Run_Datetime__c = dte;
    
   //Otherwise, there are more records to process, so create a record 
   //to run it again immediately
   } else {
    //Make sure this is offset from other jobs so they don't run in bulk
    sjList = [Select Next_Run_Datetime__c from Scheduled_Job__c
       where Next_Run_Datetime__c >=: System.now().addMinutes(-15) and 
       Next_Run_Datetime__c <: System.now().addMinutes(30) and  
       Schedule__c = true and Run_Job__c = false
       order by Next_Run_Datetime__c DESC
       LIMIT 1];
    new_sj.Next_Run_Datetime__c = (sjList.size() > 0 – sjList[0].Next_Run_Datetime__c.addMinutes(15) : System.now());
    
   }
   
   insert new_sj;
  }
  
 }
}

 


This was selected as the best answer
David VPDavid VP

Nice !


David

JitendraJitendra
Thank you very much jgrenfell :smileyhappy:

It solved my problem.

Thank you very much
JitendraJitendra
Hi jgrenfell,

I am sending the automaic Email to the users using Apex. I am able to send different emails to different user.
But i am not able to set the From field in the Apex Code.
Email is sent by the Trigger and trigger is activated by Workflow Rule.
It takes the name of last Logged in User by default to set the From field of the email.

Is there any Way to set the Name of default Workflow user or any User?

Please suggest any solution or work around if you know.
jgrenfelljgrenfell
This is how I set it in mine:

Code:
mail.setReplyTo('jgrenfell@ceoworks.org');
// Specify the name used as the display name.
mail.setSenderDisplayName('Jessie Grenfell');

 

JitendraJitendra
Hi  jgrenfell,

Thanks for quick reply.

I am also using the same line but it only displays the Name but it have email address of another user. and the user is not fixed. it is selected by last login.

eg:
mail.setSenderDisplayName('Jessie Grenfell');

Then it will show:
Jessie Grenfell (emailofsomeoneelse@gmail.com)

Can we only set the sender name but not the default email id selected by Salesforc?

Joe2theSonJoe2theSon

With verison 16 of apex code, can you write an apex class and schedule it?