Send email from G-Sheets after 4 weeks - google-apps-script

I am using Google Sheets, and I'm trying to send an email to myself, after 4 weeks of a specified date, with information in the body of that email extracted from a certain cell. The cell is in the same row (Col B) that the date is in (Col Q). I have successfully figured out the timing issue, but I just can't get the info from the cell to go into the body of the email.
Here is the code I have.
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getRange(9,17,31).getValues();
var now = Date.now();
for (var i = 0; i < data.length; i++) {
var row = data[i];
var date = new Date(row[0]);
var remind_date = new Date(date.getTime() + 28 * 24 * 60 * 60 * 1000);
var diff = now - remind_date;
var case_name = row[2];
if ((diff >= 0) && (diff < 24 * 60 * 60 * 1000)) {
GmailApp.sendEmail('myemail#me.com', 'Case Inquiry Reminder','Reminder, inquire about status of ', case_name);
}
}
}

First part
On the question it's mentioned that the date is on column Q but the code is getting column A instead.
var date = new Date(row[0]);
Maybe the correct code is
var date = new Date(row[16]);
While some Spreadsheet Service methods use 1 based index, JavaScript arrays use zero based index. Because of this, the index of column Q is 16.
Second part
For the same reason mentioned previously regarding JavaScript indexes, as you said that you need the value of column B, instead of
var case_name = row[2];
the code should be
var case_name = row[1];
Reference
Array
Third part
There is a problem on the following line
GmailApp.sendEmail('myemail#me.com', 'Case Inquiry Reminder','Reminder, inquire about status of ', case_name);
It uses four arguments but the the fourth isn't of the proper type. Maybe you intend to write
GmailApp.sendEmail('myemail#me.com', 'Case Inquiry Reminder','Reminder, inquire about status of ' + case_name);
(replace the last comma , by a plus sign +)
Reference
sendEmail(recipient, subject, body, options)

Related

How to use Google App Script to locate unique names and add up their valued numbers

So I used an importGoogleCalendar code to pull my workers' names and hours from Google Calendar to Google Sheets. However, I pulled EVEYRTHING. The names are duplicated a numerous amount of times in multiple rows for each day with their corresponding hours. How can I get one unique name for each worker along with their added up hours for each time their name appears on the sheet to another sheet so that it looks cleaner and easier to look at?
Example: I would like
Name
Hours
Sam
5
Sam
7
Bob
3
Sam
5
Sam
7
Bob
6
Joe
4
To look like
Name
Hours
Sam
24
Bob
9
Joe
4
Here is the code:
function importGoogleCalendar() {
var sheet = SpreadsheetApp.getActiveSheet();
var calendarId = sheet.getRange('B1').getValue().toString();
var calendar = CalendarApp.getCalendarById(calendarId);
// Set filters
var startDate = sheet.getRange('B2').getValue();
var endDate = sheet.getRange('B3').getValue();
var searchText = '';
// Print header
var header = [["Title", "Description", "Start", "End", "Duration"]];
var range = sheet.getRange("A6:E6");
range.setValues(header);
range.setFontWeight("bold")
// Get events based on filters
var events = (searchText == '') ? calendar.getEvents(startDate, endDate) : calendar.getEvents(startDate, endDate, {search: searchText});
// Display events
for (var i=0; i<events.length; i++) {
var row = i+7;
var details = [[events[i].getTitle(), events[i].getDescription(), events[i].getStartTime(), events[i].getEndTime(), '']];
range = sheet.getRange(row,1,1,5);
range.setValues(details);
// Format the Start and End columns
var cell = sheet.getRange(row, 3);
cell.setNumberFormat('mm/dd/yyyy hh:mm');
cell = sheet.getRange(row, 4);
cell.setNumberFormat('mm/dd/yyyy hh:mm');
// Fill the Duration column
cell = sheet.getRange(row, 5);
cell.setFormula('=(HOUR(D' + row + ')+(MINUTE(D' +row+ ')/60))-(HOUR(C' +row+ ')+(MINUTE(C' +row+ ')/60))');
cell.setNumberFormat('0.00');
}
}
I am willing to make another function if need be
Thank You and Stay Safe
I like the approach of using a pivot table (since they are perfect for handling such data). You get totals, and other features for free.
But if you only want to write summary data to your spreadsheet, you can use the following approach:
My starting point follows on from this line in your existing script:
// Get events based on filters
var events = (searchText == '') ? calendar.getEvents(startDate, endDate) : calendar.getEvents(startDate, endDate, {search: searchText});
From there I pass your events array to a new function:
function summarize(events) {
var totalsByName = new Map();
events.forEach((event) => {
let name = event.getTitle();
// duration in seconds (from milliseconds / 1000):
let duration = Math.abs(event.getEndTime() - event.getStartTime()) / 1000.0;
if (totalsByName.has(name)) {
// increment the existing duration for this person:
totalsByName.set(name, totalsByName.get(name) + duration);
} else {
// add the first entry for this person:
totalsByName.set(name, duration);
}
} );
// iterate over each entry in the map:
for (let [name, duration] of totalsByName) {
console.log(name + ' = ' + (duration / 3600.0));
}
}
The function populates a map of results - one entry per person's name.
In my example, all I do is print the data to the console.
console.log(name + ' = ' + (duration / 3600.0)); // convert seconds to hours
But you can instead adapt all your existing code to write this data to the spreadsheet instead, using my name and duration values.
You can apply additional logic to sort by names, if you wish, and round the numeric data to a preferred number of decimal places.

Why does my spreadsheet script throw a TypeError when I call getTime()?

I am writing a small function using the Google Apps Script editor that checks that an event date in a spreadsheet is within 12 hours of the current date to send me a morning email notification. The for loop below loops through the dates column in my spreadsheet to find the current date (entered in the spreadsheet formatted as a date expressed as MM/DD/YYYY). I can't figure out why schedule[i][0].getTime() is giving me the following error:
TypeError: Cannot find function getTime in object .
Here's the function:
function sendEmail() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Schedule"); // get sheet
var lastRow = spreadsheet.getLastRow(); // find length of sheet
var schedule = spreadsheet.getRange(6, 1, lastRow, 5).getValues(); // this an array with dates and info on events (first column has dates)
var now = new Date().getTime(); // get today's date in ms
var MILLIS = 1000 * 60 * 60 * 12 // get number of milliseconds in 12 hours
for (var i=0; i <= lastRow; i++) {
var eventDate = schedule[i][0].getTime()
if (now - eventDate < MILLIS && now > eventDate) {
...send email...
};
};
};
I've checked to make sure that the schedule[i][0] object is a valid date (e.g. myvar instanceof Date, debugger, format in google sheet, etc.), and everything indicates that it is. Yet, any method I've tried that nests the if clause in a function or calls getTime() more than once in the if statement causes a TypeError.
What am I doing wrong that is causing this error? How can I edit my code so that the condition in my if statement only runs if the difference between now and the date in schedule[i][0] is less than 12 hours?
Here's a link to a spreadsheet showing how the data is formatted.
Thanks for your help!
Edit: I edited the question to match the "minimal reproducible example" format. The problem is with the for loop, which doesn't account for the header. Thanks to tehhowch for the fix.
This works on your data.
Using your data and a few minor tweaks to the code. I also used valueOf() instead of getTime() ...it's some thing I prefer using.
function sendEmail() {
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName("Sheet177");
var schedule = sh.getRange(6, 1, sh.getLastRow(), 1).getValues(); // this an array with dates and info on events (first column has dates)
var halfday = 1000 * 60 * 60 * 12;
var idxA=[];
for (var i=0;i<schedule.length;i++) {
var now=new Date().valueOf();
var sked=new Date(schedule[i][0]).valueOf();
var diff=now-sked;
if ((diff<halfday) && (now>sked)) {
idxA.push(i);//I am just collecting the indexes of the var schedule that are within 12 hours of now.
//send email
}
}
Logger.log(idxA);
}
Per tehhowch, the problem is that my for loop did not account for the header rows in my spreadsheet or that an array is 0-indexed.
Removing the empty rows in the array and giving the right limit in the for loop fixed the problem:
...
function sendEmail() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Schedule"); // get active sheet
var lastRow = spreadsheet.getLastRow(); // find length of sheet
var schedule = spreadsheet.getRange(6, 1, lastRow - 5, 5).getValues(); // get values from first column of dates
var now = new Date().valueOf(); // get today's date
var MILLIS = 1000 * 60 * 60 * 12 // get number of ms in 12 hours
for (var i=0; i <= schedule.length - 1; i++) {
var eventDate = schedule[i][0].getTime()
if (now - eventDate < MILLIS && now > eventDate) {
...send email...
};
};
};

Delete Row If Two Corresponding Cells Match

In my google sheet file, sheet2 is being used as a blacklist wherein colA = a name, colB = date they were blacklisted, colC = date they will be removed from the blacklist (3 months after value in colB). Sheet1 records the responses of a signup form.
I would like a script that will automatically remove the names after their colC date. Perhaps a script that can delete an entire row if two cells match. An example would be if B2 and C2 are equal then delete row 2. This would have to apply to every row.
Any help would be greatly appreciated. Thank you very much.
It wasn't immediately clear which sheet you wanted to remove the name from. My answer assumes that you are removing the name from the blacklist sheet, sheet2.
var ss = SpreadsheetApp.getActiveSpreadsheet ();
var sheet2 = ss.getSheetByName ("sheet2");
var values = sheet2.getDataRange ().getValues ();
var day = 24*3600*1000;
var today = parseInt ((new Date ().setHours (0,0,0,0))/day);
var ssdate; // Date to check, spreadsheet date
// Start at the bottom and move up the file, since all rows will shit up when deleted
for (var i = values.length; i >= 0; i--)
{
try
{
ssdate = values[i][2].getTime () / day; // 2 is for the C column (0 is for the A column)
}
catch (e) {ssdate = null;}
// Testing that the date is today or later to remove from file
if (ssdate && Math.floor (ssdate) >= today)
{
sheet2.deleteRow (i + 1); // Removes name from blacklist.
// i + 1, because it is the row number, which starts at 1
// where arrays start index at 0.
}
}

Replicate formula with integer and referencing cell above current row

I am trying to replicate the following formula in an if function in google apps script for a spreadsheet.
=IF(AND((G2/(1/96)/(INT(G2/(1/96))))=1,B2<>B1),F2, "")
Populating the cell in the D column. I am trying to get it to work so that it goes through each row and works applies this formula.
I think where I am struggling to replicate the formula is how to make it an integer and how to get the script to read the cell in the row above.
Any help would be much appreciated! See below for my rudimentary attempt at this.
function releaseTimes() {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 2;
var numRows = 249;
var dataRange = sheet.getRange(startRow, 1, numRows, 16)
var data = dataRange.getValues();
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var startTime = row[1];
var quarterHour = (1/96);
var committedSeats = (startTime-halfHour);
var startTime1 = sheet.getRange(startRow + i, 2);
var startTime2 = sheet.getRange(startRow + i-1, 2);
var G2 = (startTime2-quarterHour);
var G2divided = (G2/quarterHour);
var int = parseInt(G2divided);
var firstCondition = G2divided/int;
if ( firstCondition = 1 && startTime2 != startTime1) {
sheet.getRange(startRow + i, 4).setValue(committedSeats);
} else {
sheet.getRange(startRow + i, 4).setValue(blank);
}
}
}
The spreadsheet function you've provided:
=IF(AND((G2/(1/96)/(INT(G2/(1/96))))=1,B2<>B1),F2, "")
Can be expressed like this:
IF (G2 is a time ending on the quarter hour) AND (B2 is not equal to B1)
THEN
DISPLAY F2
ELSE
DISPLAY nothing
ENDIF
The magic number 96 appears because there are 96 quarter hours in a 24 hour period. The code would be much clearer if it just dealt with time, like this:
var firstCondition = (startTime.getMinutes() % 15 == 0); /// BOOLEAN!
There's some confusion in your question, though. The spreadsheet formula refers to column "G", Release all Holds, for a 15-minute check. Your script, though, uses column "B", Start time, for that. Looking at your example data, there are rows like "Show 9" where the start time is a "15" while release-all is not, so you'll get different results depending on which of those two calculations is "correct". I'll assume that your script is correct, and that all times are relative to the start time.
One last point... in your script, you mix two methods of manipulating range data. You start into the (more efficient) javascript method, with var data = dataRange.getValues();, but then return to touching individual cells with statements like sheet.getRange(). That's confusing, since the first uses 0-based indexes while spreadsheets use 1-based. If possible, pick one way, and stick with it.
Script
Here's an updated version of your script. The spreadsheet data is accessed just twice - it's read and written in bulk, saving time vs cell-by-cell updates. All times are calculated relative to startTime, something you can change if you wish. Finally, javascript Date object methods are used for all time calculations, avoiding the use of magic numbers.
function releaseTimes() {
var sheet = SpreadsheetApp.getActiveSheet();
var headRows = 1; // # header rows to skip
var dataRange = sheet.getDataRange();
var data = dataRange.getValues();
// Handy time definitions
var minute = 60 * 1000; // 60 * 1000 ms
var quarterHour = 15 * minute;
var halfHour = 30 * minute;
var hour = 60 * minute;
// Process data, skipping headRows
for (var i = headRows; i < data.length; ++i) {
var row = data[i];
var startTime = row[1]; // Assume all times are relative to startTime
var prevStartTime = data[i-1][1];
// Test whether startTime is on a quarter-hour
var firstCondition = (startTime.getMinutes() % 15 == 0);
// Committed blank unless unique quarter hour start time
// NOTE: You can't compare javascript time objects using ==
// See: http://stackoverflow.com/a/7961381
if ( firstCondition && (prevStartTime - startTime) != 0) {
row[3] = new Date(startTime.getTime() - halfHour); // Monitor Committed Seats. Return to sale orders that
} else { //have been in the basket for over 1 hour.
row[3] = '';
}
row[4] = new Date(startTime.getTime() - hour); // Release Press Holds bar 2
row[5] = new Date(startTime.getTime() - halfHour); // Release all Company Holds. Release Venue Holds bar 2
row[6] = new Date(startTime.getTime() - quarterHour); // Release all remaining holds
}
// Write out updated information
dataRange.setValues(data);
}
Result
As you can see, the result is a bit different from your initial example, because of the decision to base decisions on startTime.

"Row Last Modified" Script Produces Wrong Month

This code has been working well for me, but today (when I modified a cell) I noticed that the script is producing a date that is one month behind the actual date (i.e., today is 6/5/2013, but the script produced 5/5/2013). Can anyone see what might be causing this problem?
// * based on the script "insert last modified date" by blisterpeanuts#gmail.com *
// Update cell with the the last modified time of any (single) cell in that row, excluding row 1 and column 1
function onEdit() {
var d = new Date();
// format date nicely
var month_str = d.getMonth();
var day_str = d.getUTCDate();
var year_str = d.getYear().toString().substring(2);
// create the formatted time and date strings
var date_str = month_str + '/' + day_str + '/' + year_str;
// create the message (change this to whatever wording you prefer)
// note also that rather than all the above, you could just use d.toString()
// I didn't because I didn't want it printing the timezone.
var s = date_str;
var active_range = SpreadsheetApp.getActiveRange();
if (active_range.getHeight() == 1 && active_range.getWidth() == 1 && active_range.getRow != 1 && active_range.getColumn() != 1) {
var update_row = active_range.getRow().toString();
var update_cell = "AF" + update_row;
SpreadsheetApp.getActiveSheet().getRange(update_cell).setValue(s);
}
}
I don't think this has ever worked correctly for you. The documentation for Date.getMonth() says:
The value returned by getMonth is an integer between 0 and 11. 0
corresponds to January, 1 to February, and so on.
You need to increment the month by one.
var month_str = d.getMonth() + 1;
(Also the variable name month_str is misleading, it isn't a string, getMonth() returns an integer)