-----
First off, credit where credit is due. My initial inspiration for this was work done by Steve Anderson that he shared on his blog gokubi.com, which is a great resource. What this allows you to do is both schedule code to run at particular times, and what I added on to his work is the ability to break processes into chunks that won't hit the governor limits so that large processes can be scheduled as well. I believe SF is working on providing this functionality, but until then, feel free to make use of the code below.
The anecdotal explanation: I created a custom object called Scheduled Jobs which I use to set off a 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.
The code, minus some irrelevent bits, is 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 that day. 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).
Code:
trigger InsertUpdateScheduledJobAfter on Scheduled_Job__c (after insert, after update) { Integer intReturn; Integer intMagicNum; ListsjList = new List (); 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; } } }
No comments:
Post a Comment