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
Scott BradyScott Brady 

Help with Apex Class to be scheduled to just edit/save (touch) all records of a custom object given a set criteria

Hi There,

I am trying to write an APEX Class to be scheduled every year on March 1st and September 1st at 12am that will go out and edit/save all records of a customer object (Personnel_Management__C). The reason for the requirement is that we have semi-annual audit that needs to be performed on our employee information. Nothing needs to be updated, but there is a process/flow behind the scenes that I want to have triggered so that the automated approval processes and other business processes that are included in the process run.  The only way I can see this happening is for an update to be made to each record that would require an audit - scheduled apex.

the customer object in question is Personnel_Management__C - At any given audit, 100+ records could qualify for the select statement for an update (keep in mind, I just want to edit and then save each record, not actually change any values).

The criteria to be met is that within Personnel_Management__C there is a picklist field that is called Employment_Status__C.  This field needs to be "Current Employee" in order for it to qualify for the select statement.

I really appreciate your time and effort to help me get this done - thanks in advance (I am hoping this is quite simple and will serve as a great learning experience for me).
Best Answer chosen by Scott Brady
Mahesh DMahesh D
Hi Scott,

Please find the below class:

Here I considered:

(1) Exception Handling
(2) Sending an email to Admin with number of Batches processed.
(3) Used Database.update, because if there is an error in updating 1 records, all the remaining records should proceed.
global class PMProcessingBatch implements Database.Batchable<sObject>, Schedulable, Database.Stateful {

    //Variable Section
    global FINAL String strQuery;
    global List<String> errorMessages = new List<String>();
    
    global PMProcessingBatch() { 
        this.strQuery = getBatchQuery();
    }
    
    //Returns the Query String to Batch constructor to fetch right records.
    private String getBatchQuery() {
        String strQuery = 'SELECT Id, Name FROM Personnel_Management__c'; 
        return strQuery;
    }
    
    //Batch Start method
    global Database.QueryLocator start(Database.BatchableContext BC) {
        return Database.getQueryLocator(strQuery);
    }

    //Batch Execute method
    global void execute(Database.BatchableContext BC, List<sObject> scopeList) {
        System.debug(LoggingLevel.INFO, '== scopeList size ==' + scopeList.size());
        
        List<Personnel_Management__c> pmList = (List<Personnel_Management__c>) scopeList;
		
		try {                    
			Database.SaveResult[] saveResults = Database.update(pmList, false);
			for (Database.SaveResult sr : SaveResults) {
				if(!sr.isSuccess()) {
					for (Database.Error err : sr.getErrors()) {
						errorMessages.add('Error: ' + err.getStatusCode() + ': ' + err.getMessage());
					}
					system.debug(sr.getErrors()[0].getMessage());
				}
			}
			System.debug('errorMessages: '+errorMessages);
		} catch (System.Exception ex) {
			System.debug('An error occurred while updating PM: ' + ex.getMessage());
			return;
		}
    }  

    //Batch Finish method for after execution of batch work
    global void finish(Database.BatchableContext BC) { 
        AsyncApexJob aaj = [Select Id, Status, NumberOfErrors, JobItemsProcessed, MethodName, TotalJobItems, CreatedBy.Email from AsyncApexJob where Id =:BC.getJobId()];
        
        // Send an email to the Apex job's submitter notifying of job completion.
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        String[] toAddresses = new String[] {aaj.CreatedBy.Email};
        mail.setToAddresses(toAddresses);
        mail.setSubject('JOB Salesforce PM Processing Batch: ' + aaj.Status);
        String bodyText='Total Job Items ' + aaj.TotalJobItems + ' Number of records processed ' + aaj.JobItemsProcessed + ' with '+ aaj.NumberOfErrors + ' failures.\n';
        bodyText += 'Number of Error Messages ' + errorMessages.size() + '\n';
        bodyText += 'Error Message' + String.join(errorMessages, '\n');
        mail.setPlainTextBody(bodyText);
        Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
    }
    
    //Method which schedules the PMProcessingBatch
    global void execute(SchedulableContext sc) {        
        PMProcessingBatch pmBatch = new PMProcessingBatch();
        ID batchprocessid = Database.executeBatch(pmBatch);
    }
}

Here is the Test Class:
 
/**
 * This class contains unit tests for validating the behavior of AccountTeamCreationBatch
 * and AccountRelationshipUtils.
 *
 */
@isTest
private class PMProcessingBatchTest {
    
   
    public static String CRON_EXP = = '0 0 1 * * ?';
    

    static testMethod void pmBatchTest() {
        
        Personnel_Management__c pm = new Personnel_Management__c();
        pm.Name = 'Test PM';
        
        insert pm;
        
        Test.startTest();
        PMProcessingBatch batch = new PMProcessingBatch();
        Database.executeBatch(batch);        
        Test.stopTest();
        
        String jobId = System.schedule('PMProcessingBatch', CRON_EXP, new PMProcessingBatch());
         
        CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, NextFireTime FROM CronTrigger WHERE id = :jobId];

        System.assertEquals(CRON_EXP, ct.CronExpression);
        System.assertEquals(0, ct.TimesTriggered);
    }
}

Please do let me know if helps you.

Regards,
Mahesh

All Answers

Chris Gary CloudPerformerChris Gary CloudPerformer
The following code snippet implements both interfaces that you would need, the Database.Batchable and the Schedulable interface:
global class ObjectsUpdater implements Database.Batchable<sObject>,Schedulable {
    global Database.QueryLocator start(Database.BatchableContext bc) {
        String query = 'SELECT Id, Name FROM Personnel_Management__c'; // create the query to run
        return Database.getQueryLocator(query); //get all of the data in the start
    }
    global void execute(Database.BatchableContext bc,List<sObject> objects){
        List<Personnel_Management__c> personnelList = (List<Personnel_Management__c>)objects; //because a generic list is passed in, you have to 'box' it (type set) it to the Right Object.
        update personnelList;
    }
    global void finish(Database.BatchableContext bc){
        //you can do something after the job is done....
    }
    global void execute(SchedulableContext sc){
        Database.executeBatch(new ObjectsUpdater());
    }
}

Hope this helps!
Scott BradyScott Brady
Hey Chris - first off - thank you very much for your response and the comments in your code.  Would you also assist me in creating a test class for the code above as well?  Seeing the structure of the code above has helped me immensely, and I believe that all I want to do in a test class is specify the record that I want this code to grab and update - right? 
Mahesh DMahesh D
Hi Scott,

Please find the below class:

Here I considered:

(1) Exception Handling
(2) Sending an email to Admin with number of Batches processed.
(3) Used Database.update, because if there is an error in updating 1 records, all the remaining records should proceed.
global class PMProcessingBatch implements Database.Batchable<sObject>, Schedulable, Database.Stateful {

    //Variable Section
    global FINAL String strQuery;
    global List<String> errorMessages = new List<String>();
    
    global PMProcessingBatch() { 
        this.strQuery = getBatchQuery();
    }
    
    //Returns the Query String to Batch constructor to fetch right records.
    private String getBatchQuery() {
        String strQuery = 'SELECT Id, Name FROM Personnel_Management__c'; 
        return strQuery;
    }
    
    //Batch Start method
    global Database.QueryLocator start(Database.BatchableContext BC) {
        return Database.getQueryLocator(strQuery);
    }

    //Batch Execute method
    global void execute(Database.BatchableContext BC, List<sObject> scopeList) {
        System.debug(LoggingLevel.INFO, '== scopeList size ==' + scopeList.size());
        
        List<Personnel_Management__c> pmList = (List<Personnel_Management__c>) scopeList;
		
		try {                    
			Database.SaveResult[] saveResults = Database.update(pmList, false);
			for (Database.SaveResult sr : SaveResults) {
				if(!sr.isSuccess()) {
					for (Database.Error err : sr.getErrors()) {
						errorMessages.add('Error: ' + err.getStatusCode() + ': ' + err.getMessage());
					}
					system.debug(sr.getErrors()[0].getMessage());
				}
			}
			System.debug('errorMessages: '+errorMessages);
		} catch (System.Exception ex) {
			System.debug('An error occurred while updating PM: ' + ex.getMessage());
			return;
		}
    }  

    //Batch Finish method for after execution of batch work
    global void finish(Database.BatchableContext BC) { 
        AsyncApexJob aaj = [Select Id, Status, NumberOfErrors, JobItemsProcessed, MethodName, TotalJobItems, CreatedBy.Email from AsyncApexJob where Id =:BC.getJobId()];
        
        // Send an email to the Apex job's submitter notifying of job completion.
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        String[] toAddresses = new String[] {aaj.CreatedBy.Email};
        mail.setToAddresses(toAddresses);
        mail.setSubject('JOB Salesforce PM Processing Batch: ' + aaj.Status);
        String bodyText='Total Job Items ' + aaj.TotalJobItems + ' Number of records processed ' + aaj.JobItemsProcessed + ' with '+ aaj.NumberOfErrors + ' failures.\n';
        bodyText += 'Number of Error Messages ' + errorMessages.size() + '\n';
        bodyText += 'Error Message' + String.join(errorMessages, '\n');
        mail.setPlainTextBody(bodyText);
        Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
    }
    
    //Method which schedules the PMProcessingBatch
    global void execute(SchedulableContext sc) {        
        PMProcessingBatch pmBatch = new PMProcessingBatch();
        ID batchprocessid = Database.executeBatch(pmBatch);
    }
}

Here is the Test Class:
 
/**
 * This class contains unit tests for validating the behavior of AccountTeamCreationBatch
 * and AccountRelationshipUtils.
 *
 */
@isTest
private class PMProcessingBatchTest {
    
   
    public static String CRON_EXP = = '0 0 1 * * ?';
    

    static testMethod void pmBatchTest() {
        
        Personnel_Management__c pm = new Personnel_Management__c();
        pm.Name = 'Test PM';
        
        insert pm;
        
        Test.startTest();
        PMProcessingBatch batch = new PMProcessingBatch();
        Database.executeBatch(batch);        
        Test.stopTest();
        
        String jobId = System.schedule('PMProcessingBatch', CRON_EXP, new PMProcessingBatch());
         
        CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, NextFireTime FROM CronTrigger WHERE id = :jobId];

        System.assertEquals(CRON_EXP, ct.CronExpression);
        System.assertEquals(0, ct.TimesTriggered);
    }
}

Please do let me know if helps you.

Regards,
Mahesh
This was selected as the best answer
Scott BradyScott Brady

Thank you very much - Mahesh - this was a great write up for code and testing with great comments.  I learned a lot from this exercise and your code was able to accomplish exactly what I needed.  I was even able to add a few more actions and remove them from other workflows.

Thanks a ton!

Cheers,

Scott