Developing the Batch Apex Class

A good design approach for Batch Apex is to consider the input schema, output schema, and the most direct algorithm to transform input to output. You’ve already designed the output schema based on what the users want to see: the Missing Timecard object. That leaves the input and the algorithm to be designed.

Consider the algorithm first, which drives the input. The algorithm loops through assignments that are not in Tentative or Closed status. It builds a list of Week Ending dates of valid timecards (in Submitted or Approved status) in the same project as the assignment. It then cycles through the weeks between the start and end dates of the assignment, up to the current day. If a week ending date is not found in the list of timecard Week Ending dates, it is considered missing and its assignment and date are added to the Missing Timecards object.

With the algorithm nailed down, move on to the input. The key to a concise, maintainable Batch Apex class is formulating the right SOQL query to provide the input records. Most of the effort is in finding the optimal SObject to base the query on. If you pick the wrong SObject, you could be forced to augment the input in your execute method, resulting in more queries, this time subject to SOQL governor limits.

It is clear from the algorithm that the batch input must include Assignment records and corresponding Timecard records. But Assignment and Timecard are two separate many-to-many relationships with no direct relationship with each other.

Although basing the query on the Assignment or Timecard objects might be tempting, this leads to a weak design. For example, if you query the assignments in the start method and then augment this with Timecard records in the execute method, you need to build dynamic SOQL to optimize the second query given the input Assignment records. This is a sure sign that you should continue to iterate on the design.

When you switch tracks and design the batch around the Project object, life becomes easier. From Project, you have access to Timecard and Assignment records at the same time. The code in Listing 9.10 implements the missing timecard feature with a query on Project as the input.

Listing 9.10 MissingTimecardBatch


global class MissingTimecardBatch
  implements Database.Batchable<SObject> {
  global Database.QueryLocator start(Database.BatchableContext context) {
    return Database.getQueryLocator([ SELECT Name, Type__c,
      (SELECT Name, Start_Date__c, End_Date__c
        FROM Assignments__r WHERE Status__c NOT IN ('Tentative', 'Closed')),
      (SELECT Status__c, Week_Ending__c
        FROM Timecards__r
        WHERE Status__c IN ('Submitted', 'Approved'))
      FROM Project__c
    ]);
  }
  global void execute(Database.BatchableContext context,
    List<SObject> scope) {
    List<Missing_Timecard__c> missing = new List<Missing_Timecard__c>();
    for (SObject rec : scope) {
      Project__c proj = (Project__c)rec;
      Set<Date> timecards = new Set<Date>();
      if (proj.Assignments__r != null) {
        for (Assignment__c assign : proj.Assignments__r) {
          if (proj.Timecards__r != null) {
            for (Timecard__c timecard : proj.Timecards__r) {
              timecards.add(timecard.Week_Ending__c);
            }
          }
/** Timecards are logged weekly, so the Week_Ending__c field is always
  * a Saturday. We need to convert an assignment, which can contain an
  * arbitrary start and end date, into a start and end period expressed
  * only in terms of Saturdays. To do this, we use the toStartOfWeek
  * method on the Date object, and then add 6 days to reach a Saturday.
  */
          Date startWeekEnding =
            assign.Start_Date__c.toStartOfWeek().addDays(6);
          Date endWeekEnding =
            assign.End_Date__c.toStartOfWeek().addDays(6);
          Integer weeks = 0;
          while (startWeekEnding.addDays(weeks * 7) < endWeekEnding) {
            Date d = startWeekEnding.addDays(weeks * 7);
            if (d >= Date.today()) {
              break;
            }
            if (!timecards.contains(d)) {
              missing.add(new Missing_Timecard__c(
                Assignment__c = assign.Id,
                Week_Ending__c = d));
            }
            weeks++;
          }
        }
      }
    }
    insert missing;
  }
  global void finish(Database.BatchableContext context) {
  }
}


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.124.194