You need to sign in to do that
Don't have an account?
Ben Meyers
Scheduled apex randomly stops rescheduling
I use the Schedulable class to schedule apex code to run every minute. At random times the apex is not rescheduled and the process stops. Below is the code used. Any thoughts?
public class ScheduleSalesInvoiceStatus implements ScheduleDispatcherSalesInvoiceStatus.IScheduleDispatchedSalesInvoiceStatus
{
public static string query = 'select id, quote__r.status, c2g__PaymentStatus__c, quote__c, c2g__Opportunity__c from c2g__codaInvoice__c where Quote__c != null AND ((c2g__PaymentStatus__c = \'Part Paid\' and quote__r.status != \'Invoice Part Paid\') OR (quote__r.status != \'Credited\' AND quote__r.status != \'Credit Requested\' AND c2g__PaymentStatus__c = \'Paid\' AND quote__r.status != \'Invoice Paid\'))';
public void execute(SchedulableContext sc)
{
system.abortJob(sc.getTriggerID()); // Always abort the job on completion
//Run key batch
SaleInvoiceStatusBatch batch = new SaleInvoiceStatusBatch(query);
Database.executeBatch(batch, 1);
}
public static String GetSchedulerExpression(Datetime dt)
{
return ('' + dt.second() + ' ' + dt.minute() + ' ' + dt.hour() + ' ' + dt.day() + ' ' + dt.month() + ' ? ' + dt.year());
}
private static Boolean ScheduledInContext = false;
public static void StartScheduler()
{
if(ScheduledInContext) return;
ScheduledInContext = true;
List<CronTrigger> jobs = [SELECT Id, CronJobDetail.Name, State, NextFireTime
FROM CronTrigger where CronJobDetail.Name='Sales Invoice Status Schedule Job'];
if(jobs.size() > 0 && jobs[0].state != 'COMPLETED' && jobs[0].state != 'ERROR' && jobs[0].state != 'DELETED')
{
return; // Already running
}
Set<String> activejobstates = new Set<String>{'Queued','Processing','Preparing'};
List<AsyncApexJob> apexjobs = [Select ID, ApexClass.Name from AsyncApexJob
where ApexClass.Name = 'SaleInvoiceStatusBatch' And Status in :activejobstates];
if(apexjobs.size() > 0)
{
return; // The batch is running
}
System.schedule('Sales Invoice Status Schedule Job',
GetSchedulerExpression(DateTime.Now().addMinutes(2)),
new ScheduleDispatcherSalesInvoiceStatus());
}
}
global class ScheduleDispatcherSalesInvoiceStatus Implements Schedulable
{
public Interface IScheduleDispatchedSalesInvoiceStatus
{
void execute(SchedulableContext sc);
}
global void execute(SchedulableContext sc)
{
Type targettype = Type.forName('ScheduleSalesInvoiceStatus');
if(targettype != null)
{
IScheduleDispatchedSalesInvoiceStatus obj = (IScheduleDispatchedSalesInvoiceStatus)targettype.NewInstance();
obj.execute(sc);
}
}
}
global class SaleInvoiceStatusBatch implements Database.Batchable<sObject>, Database.AllowsCallouts
{
private string query = '';
public SaleInvoiceStatusBatch(string query)
{
this.query = query;
}
global Database.QueryLocator start(Database.BatchableContext BC)
{
return Database.getQueryLocator(query);
}
/*
* Updates quote status to invoice paid, part-paid
* */
global void execute(Database.BatchableContext BC, List<c2g__codaInvoice__c> invoices)
{
Set<ID> oppIds = new Set<ID>();
Set<Id> invoiceIds = new Set<Id>();
List<Quote> quotes = new List<Quote>();
for (c2g__codaInvoice__c i : invoices)
{
try
{
Quote q = new Quote();
q.Id = i.quote__c;
if (i.c2g__PaymentStatus__c == 'Paid')
{
q.Status = 'Invoice Paid';
oppIds.add(i.c2g__Opportunity__c);
invoiceIds.add(i.Id);
}
else if (i.c2g__PaymentStatus__c == 'Part Paid')
{
q.Status = 'Invoice Part Paid';
}
quotes.add(q);
}
catch(Exception e)
{
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
String[] toAddresses = new String[] {'ben.meyers@pkware.com'};
mail.setToAddresses(toAddresses);
mail.setSenderDisplayName('Quote status update error');
mail.setSubject('The following exception has occurred: ' + e.getMessage());
mail.setPlainTextBody(e.getMessage());
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}
}
//List of tasks to Complete
Map<Id, Task> tToUpdateList = new Map<Id, Task>();
//List of Opportunities to update Paid Date
List<Opportunity> oToUpdateList = new List<Opportunity>();
// If there are any opportunities to update, then proceed.
if (!oppIds.isEmpty())
{
// Get tasks on Opportunity being closed, update and close if still open
for (Task t : [select isclosed, subject, status from task t where whatid in :oppIds and t.isclosed = false
and (t.type = 'AR Collection' or t.type = 'Maintenance Courtesy' or t.type = 'Maintenance Collection') for update])
{
// Otherwise, mark the task as completed.
t.status = 'Completed';
if (!tToUpdateList.containsKey(t.Id))
{
tToUpdateList.put(t.id, t);
}
}
//Add oppids to update list
for (Id oppId : oppIds)
{
Opportunity o = new Opportunity();
o.Id = oppId;
o.Paid_Date__c = System.today();
oToUpdateList.add(o);
}
}
// If there are any invoices to update, then proceed.
if (!invoiceIds.isEmpty())
{
// Get tasks on Opportunity being closed, update and close if still open
for (Task t : [SELECT isclosed, subject, status FROM task t WHERE whatid in: invoiceIds AND t.isclosed = false
AND (t.type = 'AR Collection' OR t.type = 'Maintenance Courtesy' OR t.type = 'Maintenance Collection') FOR UPDATE])
{
// Otherwise, mark the task as completed.
t.status = 'Completed';
//update t;
if (!tToUpdateList.containsKey(t.Id))
{
tToUpdateList.put(t.id, t);
}
}
//Open AR opportunity
Set<ID> openAROppIds = new Set<ID>();
for (Opportunity o : [SELECT fforce_invoice__c FROM opportunity WHERE fforce_invoice__c IN: invoiceIds])
{
openAROppIds.add(o.Id);
}
for (Task t : [SELECT isclosed, subject, status FROM task t WHERE whatid IN: openAROppIds AND t.isclosed = false
AND (t.type = 'AR Collection' OR t.type = 'Maintenance Courtesy' OR t.type = 'Maintenance Collection') FOR UPDATE])
{
// Otherwise, mark the task as completed.
t.status = 'Completed';
//update t;
if (!tToUpdateList.containsKey(t.Id))
{
tToUpdateList.put(t.id, t);
}
}
}
//Update opportunities
if (!oToUpdateList.isEmpty())
{
update oToUpdateList;
}
//Update tasks
if (!tToUpdateList.isEmpty())
{
update tToUpdateList.values();
}
//Update list of quotes
if (!quotes.isEmpty())
{
//Set recursion flag and skip validation flag
//Recursion.flag = true;
//QuoteStatusActions.SetSkipValidation(true);
update quotes;
//Set recursion flag and skip validation flag
//Recursion.flag = false;
//QuoteStatusActions.SetSkipValidation(false);
}
}
global void finish(Database.BatchableContext BC)
{
if (!Test.isRunningTest())
ScheduleSalesInvoiceStatus.StartScheduler();
}
}
public class ScheduleSalesInvoiceStatus implements ScheduleDispatcherSalesInvoiceStatus.IScheduleDispatchedSalesInvoiceStatus
{
public static string query = 'select id, quote__r.status, c2g__PaymentStatus__c, quote__c, c2g__Opportunity__c from c2g__codaInvoice__c where Quote__c != null AND ((c2g__PaymentStatus__c = \'Part Paid\' and quote__r.status != \'Invoice Part Paid\') OR (quote__r.status != \'Credited\' AND quote__r.status != \'Credit Requested\' AND c2g__PaymentStatus__c = \'Paid\' AND quote__r.status != \'Invoice Paid\'))';
public void execute(SchedulableContext sc)
{
system.abortJob(sc.getTriggerID()); // Always abort the job on completion
//Run key batch
SaleInvoiceStatusBatch batch = new SaleInvoiceStatusBatch(query);
Database.executeBatch(batch, 1);
}
public static String GetSchedulerExpression(Datetime dt)
{
return ('' + dt.second() + ' ' + dt.minute() + ' ' + dt.hour() + ' ' + dt.day() + ' ' + dt.month() + ' ? ' + dt.year());
}
private static Boolean ScheduledInContext = false;
public static void StartScheduler()
{
if(ScheduledInContext) return;
ScheduledInContext = true;
List<CronTrigger> jobs = [SELECT Id, CronJobDetail.Name, State, NextFireTime
FROM CronTrigger where CronJobDetail.Name='Sales Invoice Status Schedule Job'];
if(jobs.size() > 0 && jobs[0].state != 'COMPLETED' && jobs[0].state != 'ERROR' && jobs[0].state != 'DELETED')
{
return; // Already running
}
Set<String> activejobstates = new Set<String>{'Queued','Processing','Preparing'};
List<AsyncApexJob> apexjobs = [Select ID, ApexClass.Name from AsyncApexJob
where ApexClass.Name = 'SaleInvoiceStatusBatch' And Status in :activejobstates];
if(apexjobs.size() > 0)
{
return; // The batch is running
}
System.schedule('Sales Invoice Status Schedule Job',
GetSchedulerExpression(DateTime.Now().addMinutes(2)),
new ScheduleDispatcherSalesInvoiceStatus());
}
}
global class ScheduleDispatcherSalesInvoiceStatus Implements Schedulable
{
public Interface IScheduleDispatchedSalesInvoiceStatus
{
void execute(SchedulableContext sc);
}
global void execute(SchedulableContext sc)
{
Type targettype = Type.forName('ScheduleSalesInvoiceStatus');
if(targettype != null)
{
IScheduleDispatchedSalesInvoiceStatus obj = (IScheduleDispatchedSalesInvoiceStatus)targettype.NewInstance();
obj.execute(sc);
}
}
}
global class SaleInvoiceStatusBatch implements Database.Batchable<sObject>, Database.AllowsCallouts
{
private string query = '';
public SaleInvoiceStatusBatch(string query)
{
this.query = query;
}
global Database.QueryLocator start(Database.BatchableContext BC)
{
return Database.getQueryLocator(query);
}
/*
* Updates quote status to invoice paid, part-paid
* */
global void execute(Database.BatchableContext BC, List<c2g__codaInvoice__c> invoices)
{
Set<ID> oppIds = new Set<ID>();
Set<Id> invoiceIds = new Set<Id>();
List<Quote> quotes = new List<Quote>();
for (c2g__codaInvoice__c i : invoices)
{
try
{
Quote q = new Quote();
q.Id = i.quote__c;
if (i.c2g__PaymentStatus__c == 'Paid')
{
q.Status = 'Invoice Paid';
oppIds.add(i.c2g__Opportunity__c);
invoiceIds.add(i.Id);
}
else if (i.c2g__PaymentStatus__c == 'Part Paid')
{
q.Status = 'Invoice Part Paid';
}
quotes.add(q);
}
catch(Exception e)
{
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
String[] toAddresses = new String[] {'ben.meyers@pkware.com'};
mail.setToAddresses(toAddresses);
mail.setSenderDisplayName('Quote status update error');
mail.setSubject('The following exception has occurred: ' + e.getMessage());
mail.setPlainTextBody(e.getMessage());
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}
}
//List of tasks to Complete
Map<Id, Task> tToUpdateList = new Map<Id, Task>();
//List of Opportunities to update Paid Date
List<Opportunity> oToUpdateList = new List<Opportunity>();
// If there are any opportunities to update, then proceed.
if (!oppIds.isEmpty())
{
// Get tasks on Opportunity being closed, update and close if still open
for (Task t : [select isclosed, subject, status from task t where whatid in :oppIds and t.isclosed = false
and (t.type = 'AR Collection' or t.type = 'Maintenance Courtesy' or t.type = 'Maintenance Collection') for update])
{
// Otherwise, mark the task as completed.
t.status = 'Completed';
if (!tToUpdateList.containsKey(t.Id))
{
tToUpdateList.put(t.id, t);
}
}
//Add oppids to update list
for (Id oppId : oppIds)
{
Opportunity o = new Opportunity();
o.Id = oppId;
o.Paid_Date__c = System.today();
oToUpdateList.add(o);
}
}
// If there are any invoices to update, then proceed.
if (!invoiceIds.isEmpty())
{
// Get tasks on Opportunity being closed, update and close if still open
for (Task t : [SELECT isclosed, subject, status FROM task t WHERE whatid in: invoiceIds AND t.isclosed = false
AND (t.type = 'AR Collection' OR t.type = 'Maintenance Courtesy' OR t.type = 'Maintenance Collection') FOR UPDATE])
{
// Otherwise, mark the task as completed.
t.status = 'Completed';
//update t;
if (!tToUpdateList.containsKey(t.Id))
{
tToUpdateList.put(t.id, t);
}
}
//Open AR opportunity
Set<ID> openAROppIds = new Set<ID>();
for (Opportunity o : [SELECT fforce_invoice__c FROM opportunity WHERE fforce_invoice__c IN: invoiceIds])
{
openAROppIds.add(o.Id);
}
for (Task t : [SELECT isclosed, subject, status FROM task t WHERE whatid IN: openAROppIds AND t.isclosed = false
AND (t.type = 'AR Collection' OR t.type = 'Maintenance Courtesy' OR t.type = 'Maintenance Collection') FOR UPDATE])
{
// Otherwise, mark the task as completed.
t.status = 'Completed';
//update t;
if (!tToUpdateList.containsKey(t.Id))
{
tToUpdateList.put(t.id, t);
}
}
}
//Update opportunities
if (!oToUpdateList.isEmpty())
{
update oToUpdateList;
}
//Update tasks
if (!tToUpdateList.isEmpty())
{
update tToUpdateList.values();
}
//Update list of quotes
if (!quotes.isEmpty())
{
//Set recursion flag and skip validation flag
//Recursion.flag = true;
//QuoteStatusActions.SetSkipValidation(true);
update quotes;
//Set recursion flag and skip validation flag
//Recursion.flag = false;
//QuoteStatusActions.SetSkipValidation(false);
}
}
global void finish(Database.BatchableContext BC)
{
if (!Test.isRunningTest())
ScheduleSalesInvoiceStatus.StartScheduler();
}
}
Shashank (Salesforce Developers)
There is an interesting discussion around it here: http://salesforce.stackexchange.com/questions/15003/scheduled-job-does-not-execute-though-shows-in-gui