Automating Employee Leave Tracking in Google Sheets

I’m working on a project where I need to automatically update an employee calendar based on form submissions. When someone submits a leave request through a Google Form, I want the system to find their name on the calendar sheet and mark specific dates with codes like “V” for vacation, “S” for sick leave, etc.

I’ve got most of the logic working but I’m having trouble with the part where I need to locate the correct cell intersection between an employee’s row and the specific date column. Here’s what I have so far:

function updateCalendar(){
  var spreadsheet = SpreadsheetApp;
  var formResponses = spreadsheet.getActiveSpreadsheet().getSheetByName("Responses");
  var employeeCalendar = spreadsheet.getActiveSpreadsheet().getSheetByName("Schedule");

  var lastEntry = formResponses.getLastRow();
  var entryData = formResponses.getRange(lastEntry,1,1,17).getValues();
  var status = formResponses.getRange(lastEntry,17,1,1).getValue();
  var leaveType = formResponses.getRange(lastEntry,9,1,1).getValue();
  var employeeName = formResponses.getRange(lastEntry, 5,1,1).getValue();
  var startDate = formResponses.getRange(lastEntry, 10,1,1).getValue();
  var endDate = formResponses.getRange(lastEntry, 11,1,1).getValue();

  var dateHeaders = employeeCalendar.getRange("C1:Z1").getValues();
  var employeeList = employeeCalendar.getRange("A3:A100").getValues();

  for(var j = 0; j<employeeList.length; j++){
    if (status === "Approved" && employeeList[j][0] === employeeName){
      // This is where I'm stuck - need to mark the correct date cells
      // with leave codes like V, H, S, M, U, T
    };
  };
};

Any suggestions on how to properly identify and update the date cells would be really helpful.

Your main problem is converting the date range into actual cell coordinates. Once you find the employee row, loop through each day from startDate to endDate. For each date, search your dateHeaders array to find the matching column - dateHeaders[0].indexOf(currentDate) should work, but your date formats need to match exactly between the form and headers. When you’ve got both the employee row and date column index, use employeeCalendar.getRange(employeeRowIndex + 3, dateColumnIndex + 3).setValue(leaveCode) to mark the cell. The +3 adjustments are because your data starts at row 3 and column C. Pro tip I learned the hard way - use Utilities.formatDate() to normalize your date comparisons or you’ll get mismatches from time components in your date objects.

The Problem: You’re trying to automatically update an employee calendar based on Google Form submissions, but you’re struggling to correctly identify and update the cells corresponding to an employee’s leave dates. Your current code finds the employee and the date range, but it’s missing the crucial step of mapping those to the correct cells on the calendar sheet.

:thinking: Understanding the “Why” (The Root Cause):

The core issue lies in correctly translating the date range from the form submission into the corresponding column indices on your calendar sheet. Your dateHeaders array contains the date strings, but you need a way to efficiently find the column index for a given date within that array. Directly comparing dates as strings is prone to errors due to potential formatting inconsistencies and time zone differences. A more robust approach involves iterating through the date range and efficiently searching for the corresponding column for each date.

:gear: Step-by-Step Guide:

Step 1: Efficiently Find the Column Index for Each Date:

The provided solution suggests using a while loop to iterate through the date range and using the indexOf() method to search for the date in the dateHeaders array. This approach requires careful date formatting to ensure accurate matches. Instead of relying on string comparison, converting dates to a consistent numerical representation (milliseconds since epoch) provides a more robust comparison.

Here’s the improved code:

function updateCalendar(){
  // ... (Your existing code to get form data and employee lists remains unchanged) ...

  for(var j = 0; j<employeeList.length; j++){
    if (status === "Approved" && employeeList[j][0] === employeeName){
      let currentDate = new Date(startDate);
      while (currentDate <= endDate) {
        const dateString = Utilities.formatDate(currentDate, Session.getScriptTimeZone(), 'yyyy-MM-dd');
        const columnIndex = dateHeaders[0].indexOf(dateString);

        if (columnIndex !== -1) {
          employeeCalendar.getRange(j + 3, columnIndex + 3).setValue(leaveType);
        } else {
          Logger.log('Date not found in header: ' + dateString); //handle dates not in header
        }
        currentDate.setDate(currentDate.getDate() + 1);
      }
    };
  };
};

This revised code iterates through the date range, converts each date to a consistent yyyy-MM-dd format using Utilities.formatDate(), and then uses indexOf() to find the corresponding column index in the dateHeaders array. The if statement ensures that only dates present in the header are updated, preventing errors. The Logger.log statement provides debugging information if a date is missing from the header.

Step 2: Handle Leave Type Mapping:

Before updating the calendar, you might want to map the leaveType values from the form to the single-character codes you’re using (“V”, “H”, “S”, etc.). You can achieve this using a simple object:

const leaveTypeCodes = {
  "Vacation": "V",
  "Sick Leave": "S",
  "Holiday": "H",
  // Add other leave types here
};

// ... inside the loop ...
let leaveCode = leaveTypeCodes[leaveType] || "U"; //Default to "U" if leaveType is unknown.
employeeCalendar.getRange(j + 3, columnIndex + 3).setValue(leaveCode);
// ... rest of the code

This code ensures that the correct single-character code is used for each leave type.

:mag: Common Pitfalls & What to Check Next:

  • Date Formatting: Double-check that the date format in your dateHeaders array exactly matches the format created by Utilities.formatDate(currentDate, Session.getScriptTimeZone(), 'yyyy-MM-dd'). Any inconsistencies will lead to incorrect column indexing.
  • Time Zone: Ensure that the time zone used in Utilities.formatDate (Session.getScriptTimeZone()) is consistent with the time zone used in your Google Sheet and form.
  • Error Handling: The added if (columnIndex !== -1) check and Logger.log statement provides basic error handling. Consider more robust error handling for scenarios where dates are missing or unexpected data is encountered in the form submission.
  • Data Validation: Add data validation to your Google Form to ensure that users enter dates and leave types correctly.
  • Leave Type Codes: Expand the leaveTypeCodes object to include all possible leave types from your form.

:speech_balloon: Still running into issues? Share your (sanitized) config files, the exact command you ran, and any other relevant details. The community is here to help!

Had to solve this exact same problem last month. The tricky part you’re missing is handling multi-day ranges properly. After finding your employee row, loop from startDate to endDate using new Date() and increment by one day each time. For each date in the loop, convert it to match your header format before searching the dateHeaders array. I found getTime() comparison way more reliable than string matching since date objects can be weird. Once you get the column index from your date search, updating the cell is easy: getRange(employeeRowIndex + 3, dateColumnIndex + 3).setValue(leaveCode). Don’t forget to map your leaveType values first - just create something like {“Vacation”:“V”, “Sick”:“S”} before the loop. Also add a check that your date actually exists in the headers before updating, or you’ll get runtime errors on weekends or dates outside your calendar.

I hit this exact problem when building something similar for our department. You need to map your date range to column positions in the header row. Once you find the employee row, loop through each date from startDate to endDate, find which column matches that date in your dateHeaders array, then update that cell. Here’s what worked for me: loop day by day from start to end, then use indexOf on your flattened dateHeaders to get the column position. With the row index from your employee search and column index from the date search, use getRange(row, col) to target the exact cell. Add 3 to your row index since employee data starts at row 3, and add 3 to column index since dates start at column C. Watch out for date formatting - make sure you’re formatting both form dates and header dates the same way before comparing.

you’re close! after you locate the employee row index (j), you gotta loop thru each date from startDate to endDate. for each date, get its column pos using dateHeaders.indexOf, then set the value using employeeCalendar.getRange(j+3, columnIndex+3). also, convert leave type to the right code first, using a switch or an obj for mapping is helpful!

Been there with these calendar sync headaches. Manual fixes work but get messy when you scale up.

Skip the Google Scripts and cell indexing nightmare - automate the whole thing instead. Set up a flow that triggers on new form submissions, handles approval logic, then updates all your sheets at once.

You’ll avoid hardcoded row/column offsets and get proper error handling, logging, plus easy add-ons like email notifications or Slack alerts when leave gets approved.

Built something similar for our PTO tracking. Handles overlapping requests, different leave types, even syncs with HR systems. Way better than Google Scripts that break whenever someone moves a column.

The visual workflow builder makes debugging dead simple when date formatting inevitably screws up.

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.