I have this code in Google Apps Script that is linked to a Google Sheet:
function sendReminders() {
var sheet = SpreadsheetApp.openById('1IvXctYLXMKrSYPPSuMg_4_AQopemHPSnLHmQQgBDqH8').getSheetByName('ÉVALUATION');
var startRow = 2;
var numRows = sheet.getLastRow();
var numColumns = sheet.getLastColumn();
var dataRange = sheet.getRange(startRow, 1, numRows, numColumns);
var data = dataRange.getValues();
for (var i in data) {
var row = data[i];
var email = row[4];
var ccEmail = row[5];
var customId = row[1];
var dueDate = Utilities.formatDate(new Date(row[9]), Session.getScriptTimeZone(), "yyyy-MM-dd");
var message = 'Votre E&A est dûe pour la date suivante : ' + dueDate + '. Merci de la compléter en cliquant sur le lien suivant : https://form.jotform.com/221156384847058?customId3=' + customId;
var emailSent = row[12];
var value = Utilities.formatDate(new Date(row[10]), Session.getScriptTimeZone(), "yyyy-MM-dd");
var today = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyy-MM-dd");
if (value == today && emailSent != 1) {
var subject = 'Rappel - Enquête & Analyse';
MailApp.sendEmail(email, subject, message, { cc: ccEmail });
sheet.getRange((startRow + i), 13).setValue(1);
SpreadsheetApp.flush();
}
}
}
The script is supposed to send reminder emails. Everything works well, except this one line:
sheet.getRange((startRow + i), 13).setValue(1);
This line should add the value of 1 to a specific cell in the Google Sheet, in the current row the for loop is at.
However, instead of adding the value to the current row, it skips rows and adds the value to some other row. In some cases, there's a difference of 18 rows, and in others, 198 rows.
Here is the Google Sheet link for reference.
I noticed some issues in your sheet and script.
In your sample sheet, you have values on M22, M27 and M210. The function sheet.getLastRow() will include those values and your last row will be 210.
If you print the value of data, there is an extra empty array.
If you try to print startRow + i, the values are 22, 23, 24...etc. It just appends the value of startRow and i and not doing the arithmetic operation sum.
Solution:
Remove the unnecessary data or clean your sheet so that the value of numRows will become 12.
Since you skip 1 row, you should also deduct 1 row to numRow. Simple add -1 to numRows in your getRange().
Use the parseInt() method to variable i in sheet.getRange((startRow + i), 13).setValue(1);.
Your code should look like this:
function sendReminders() {
var sheet = SpreadsheetApp.openById('1IvXctYLXMKrSYPPSuMg_4_AQopemHPSnLHmQQgBDqH8').getSheetByName('ÉVALUATION');
var startRow = 2;
var numRows = sheet.getLastRow();
var numColumns = sheet.getLastColumn();
var dataRange = sheet.getRange(startRow, 1, numRows-1, numColumns);
var data = dataRange.getValues();
for (var i in data) {
var row = data[i];
var email = row[4];
var ccEmail = row[5];
var customId = row[1];
var dueDate = Utilities.formatDate(new Date(row[9]), Session.getScriptTimeZone(), "yyyy-MM-dd");
var message = 'Votre E&A est dûe pour la date suivante : ' + dueDate + '. Merci de la compléter en cliquant sur le lien suivant : https://form.jotform.com/221156384847058?customId3=' + customId;
var emailSent = row[12];
var value = Utilities.formatDate(new Date(row[10]), Session.getScriptTimeZone(), "yyyy-MM-dd");
var today = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyy-MM-dd");
if (value == today && emailSent != 1) {
var subject = 'Rappel - Enquête & Analyse';
MailApp.sendEmail(email, subject, message, { cc: ccEmail });
sheet.getRange((startRow + parseInt(i)), 13).setValue(1);
SpreadsheetApp.flush();
}
}
}
Sample:
Note: I adjusted the Reminder Date values to my current date so that the script will work.
Output:
Reference:
parseInt()
Try it this way:
function sendReminders() {
const ss = SpreadsheetApp.openById('1IvXctYLXMKrSYPPSuMg_4_AQopemHPSnLHmQQgBDqH8');
var sh = ss.getSheetByName('ÉVALUATION');
var startRow = 2;
var dataRange = sh.getRange(startRow, 1, sh.getLastRow() - 1, sh.getLastColumn());
var vs = dataRange.getValues();
vs.forEach((r, i) => {
var email = r[4];
var ccEmail = r[5];
var customId = r[1];
var dueDate = Utilities.formatDate(new Date(r[9]), Session.getScriptTimeZone(), "yyyy-MM-dd");
var message = 'Votre E&A est dûe pour la date suivante : ' + dueDate + '. Merci de la compléter en cliquant sur le lien suivant : https://form.jotform.com/221156384847058?customId3=' + customId;
var emailSent = r[12];
var value = Utilities.formatDate(new Date(r[10]), Session.getScriptTimeZone(), "yyyy-MM-dd");
var today = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyy-MM-dd");
if (value == today && emailSent != 1) {
var subject = 'Rappel - Enquête & Analyse';
MailApp.sendEmail(email, subject, message, { cc: ccEmail });
sh.getRange(startRow + i, 13).setValue(1);
}
});
}
Related
I am trying to have this script loop per column. If a date in the column is the same as today's date, the script will send an email using the persons name and email address detailed in the same row.
Table of Expiry Dates
function myFunction()
{
var spreadSheet = SpreadsheetApp.getActiveSheet();
var dataRange = spreadSheet.getDataRange();
var data = dataRange.getValues();
var date = Utilities.formatDate(new Date(), "GMT", "dd/MM/yyyy")
var engcheck1 = dataRange.getColumn[2];
for (var i = 1; i < data.length; i++)
if (engcheck1 == date)
{
var row = data[i];
var emailAddress = row[7]; //position of email header — 1
var name = row[1]; // position of name header — 1
var subject = "Your Currency for Engineering is now expired";
var text = "Please note that your currency has expired today (" + date +"). You are now unauthorised to carry out any engineering tasks until you have been signed off by a member of the engineering team.";
var message = "Dear " + name + "," + "\n\n" + text + "\n\n" + "Please get back in currency at your earliest convenience." + "\n\n" + "Many thanks," + "\n" + "Dominic Paul"
MailApp.sendEmail(emailAddress, subject, message);
}(i);
}
Without the 'If' statement, the script will loop through each row and sending out an email address. When I include the 'If' statement, nothing is sent to the email addresses. Either I am using the if statement incorrectly or I am not targeting the column accurately. I tried creating a variable engcheck1 for column 2 ONLY but no email is sent despite today being one of the dates in column 2 (C).
I did not understand, what is var engcheck1 = dataRange.getColumn[2];.
If you need to send and email if date is the same as the value in C column, provided column C has type Plain text, this is the answer:
function myFunction() {
var spreadSheet = SpreadsheetApp.getActiveSheet();
var dataRange = spreadSheet.getDataRange();
var lastRow = SpreadsheetApp.getActiveSpreadsheet().getDataRange().getNumRows();
var dateData = spreadSheet.getRange("C1:C" + lastRow).getDisplayValues().flat();
var data = dataRange.getValues();
var date = Utilities.formatDate(new Date(), "GMT", "dd/MM/yyyy")
for (var i = 1; i < data.length; i++) {
var rowDate = dateData[i];
var row = data[i];
// if the date in row is the same as date variable
if (rowDate === date) {
var emailAddress = row[7]; //position of email header — 1
var name = row[1]; // position of name header — 1
var subject = "Your Currency for Engineering is now expired";
var text = "Please ...";
var message = "Dear ...";
MailApp.sendEmail(emailAddress, subject, message);
};
}
}
Try this:
function myFunction() {
const ss = SpreadsheetApp.getActive();
var sh = ss.getActiveSheet();
var rg = sh.getDataRange();
var vs = rg.getValues();
const dt = new Date();
var dtv = new Date(dt.getFullYear(),dt.getMonth(),dt.getDate()).valueOf();
vs.forEach((r,i) => {
let d = new Date(r[2]);
let dv = new Date(d.getFullYear(),d.getMonth(),d.getDate()).valueOf();
if(dv == dtv ) {
//send you email
}
})
}
I have a sendEmail function which works almost perfectly with my Google Sheet.
The getLastRow function return all my blank rows because they have an hidden formula and I'd like to grab only filled rows.
Here is my code :
var EMAIL_SENT = 'EMAIL SENT';
function sendEmails() {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 2; // First row of data to process
var numRows = sheet.getLastRow();
// Fetch the range of cells
var dataRange = sheet.getRange(2, 1,numRows-1,20);
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var emailAddress = row[7] + "," + row[9] + "," + row[13];
var message = row[18];
var emailSent = row[19];
if (emailSent != EMAIL_SENT) { // Prevents sending duplicates
var subject = 'Un nouvel adhérent vous a été affecté';
MailApp.sendEmail(emailAddress, subject, message);
sheet.getRange(startRow + i, 20).setValue(EMAIL_SENT);
// Make sure the cell is updated right away in case the script is interrupted
SpreadsheetApp.flush();
}
}
}
Already tried var data = dataRange.getDisplayValues(); instead of var data = dataRange.getValues();without significant result.
Do you have any idea ?
Here is a sample of my sheet :
Solution:
Replace this:
var numRows = sheet.getLastRow();
with:
var numRows = sheet.getRange("F1:F").getDisplayValues().filter(String).length;
The latter will filter out the cells of your sheet that are empty even if they contain a formula.
References:
getDisplayValues()
filter()
Why don't you just add an if statement inside a for loop and check if one of the cells is empty, and break the loop if so.
Your code would look something like this:
var EMAIL_SENT = 'EMAIL SENT';
function sendEmails() {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 2; // First row of data to process
var numRows = sheet.getLastRow();
// Fetch the range of cells
var dataRange = sheet.getRange(2, 1,numRows-1,20);
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (var i = 0; i < data.length; ++i) {
var row = data[i];
if(row[6] === "") break;
var emailAddress = row[7] + "," + row[9] + "," + row[13];
var message = row[18];
var emailSent = row[19];
if (emailSent != EMAIL_SENT) { // Prevents sending duplicates
var subject = 'Un nouvel adhérent vous a été affecté';
MailApp.sendEmail(emailAddress, subject, message);
sheet.getRange(startRow + i, 20).setValue(EMAIL_SENT);
// Make sure the cell is updated right away in case the script is interrupted
SpreadsheetApp.flush();
}
}
}
I'd like the date and time to populate in two different cells, when the cell in the column next to it is populated with 'Yes'. The idea is to create a checkout time and date.
The top portion of the script is check in, and the lower half is checkout, however currently it is just for check out time, but the check out time doesn't work. Any advise?
function onEdit(event) {
var timezone = "GMT+1";
var timestamp_format = "hh:mm:ss"; // Timestamp Format.
var timestamp_formatT = "dd-MM-yyyy";
var updateColName = "Student Name";
var timeStampColName = "Time Out";
var sheet = event.source.getSheetByName('2019/20');
var actRng = event.source.getActiveRange();
var editColumn = actRng.getColumn();
var index = actRng.getRowIndex();
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues();
var dateCol = headers[0].indexOf(timeStampColName);
var updateCol = headers[0].indexOf(updateColName);
updateCol = updateCol + 1;
if (dateCol > -1 && index > 1 && editColumn == updateCol) {
var cell = sheet.getRange(index, dateCol + 1);
var cellT = sheet.getRange(index, dateCol + 2);
var date = Utilities.formatDate(new Date(), timezone, timestamp_format);
var dateT = Utilities.formatDate(new Date(), timezone, timestamp_formatT);
cell.setValue(date);
cellT.setValue(dateT);
}
var updateColName2 = "Check In";
var timeStampColName2 = "Time In";
var actRng2 = event.source.getActiveRange();
var editColumn2 = actRng2.getColumn();
var index2 = actRng2.getRowIndex();
var headers2 = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues();
var dateCol2 = headers2[0].indexOf(timeStampColName2);
var updateCol2 = headers2[0].indexOf(updateColName2);
updateCol2 = updateCol2 + 1;
if (dateCol2 > -1 && index2 > 1 && editColumn2 == updateCol2) {
var cell2 = sheet.getRange(index2, dateCol2 + 1);
var date2 = Utilities.formatDate(new Date(), timezone, timestamp_format);
cell2.setValue(date2);
}
}
Screen shot of the sheet below if it's any help.
It's not working because of a very small detail. The column header is called Check IN and in the script you are looking for one called Check In. So you just need to change this:
var updateColName2 = "Check In";
To this:
var updateColName2 = "Check IN";
Also, if you want the Time In and Date In columns to be populated only when the value Check IN column is Yes, you should add a new condition, so you could change this:
if (dateCol2 > -1 && index2 > 1 && editColumn2 == updateCol2) {
To:
if (dateCol2 > -1 && index2 > 1 && editColumn2 == updateCol2 && actRng2.getValue() == "Yes") {
Finally, you don't need to define a headers2 variable, it's the same as headers.
EDIT
Also, you should add this line inside the last if block:
var date2T = Utilities.formatDate(new Date(), timezone, timestamp_formatT);
cell2T.setValue(date2T);
So, full code could be like:
function onEdit(event) {
var timezone = "GMT+1";
var timestamp_format = "hh:mm:ss"; // Timestamp Format.
var timestamp_formatT = "dd-MM-yyyy";
var updateColName = "Student Name";
var timeStampColName = "Time Out";
var sheet = event.source.getSheetByName('2019/20');
var actRng = event.source.getActiveRange();
var editColumn = actRng.getColumn();
var index = actRng.getRowIndex();
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues();
var dateCol = headers[0].indexOf(timeStampColName);
var updateCol = headers[0].indexOf(updateColName);
updateCol = updateCol + 1;
if (dateCol > -1 && index > 1 && editColumn == updateCol) {
var cell = sheet.getRange(index, dateCol + 1);
var cellT = sheet.getRange(index, dateCol + 2);
var date = Utilities.formatDate(new Date(), timezone, timestamp_format);
var dateT = Utilities.formatDate(new Date(), timezone, timestamp_formatT);
cell.setValue(date);
cellT.setValue(dateT);
}
var updateColName2 = "Check IN";
var timeStampColName2 = "Time In";
var actRng2 = event.source.getActiveRange();
var editColumn2 = actRng2.getColumn();
var index2 = actRng2.getRowIndex();
var dateCol2 = headers[0].indexOf(timeStampColName2);
var updateCol2 = headers[0].indexOf(updateColName2);
updateCol2 = updateCol2 + 1;
var editedValue = actRng2.getValue();
if (dateCol2 > -1 && index2 > 1 && editColumn2 == updateCol2 && editedValue == "Yes") {
var cell2 = sheet.getRange(index2, dateCol2 + 1);
var cell2T = sheet.getRange(index2, dateCol2 + 2);
var date2 = Utilities.formatDate(new Date(), timezone, timestamp_format);
var date2T = Utilities.formatDate(new Date(), timezone, timestamp_formatT);
cell2.setValue(date2);
cell2T.setValue(date2T);
}
}
I hope this is of any help to you.
I have this piece of script.
It filter a range by a criteria,
then It copy values that respect criteria in a specific sheet
then It deletes all the row in the original sheet that respect the criteria.
So that If my range contains more than 1000 rows, It's said to me error: Google app script timeout.
I put my code here, can You help me to get a better performance about execution time of this script?
function trasferisciDati() {
var ui = SpreadsheetApp.getUi();
var response = ui.prompt('Inserisci il mese dei dati da esportare', '(Esempio: agosto (tutto minuscolo))', ui.ButtonSet.OK_CANCEL);
var inizioTRASFERISCIVALORI = Utilities.formatDate(new Date(), "CET", "HH:mm:ss.SSS");
if (response.getSelectedButton() == ui.Button.OK) {
//get filtered range and set values to the new range
var description = ui.prompt('Inserisci una descrizione per questa esportazione', 'apparirà come tag dell\'esportrazione', ui.ButtonSet.OK_CANCEL);
var sourceData = SpreadsheetApp.openById("1XkYhjdQfgU7mVCR7E8mfZsf292I-cJ16PnpCimnd1v8").getSheetByName("Prova");
var destinationData = SpreadsheetApp.openById("1cdXMqqBwgWK5nCQUtAP_TyIIDOHksS7wWvSG4jRu658").getSheetByName("Prova");
var lastRow = sourceData.getLastRow();
var data = sourceData.getRange(1, 1, lastRow, 1).getValues();
var chiave = response.getResponseText();
for(var i=0;i<data.length;i++)
{
if (data[i][0] == chiave) {
var filteredRow = sourceData.getRange(i+1,1,1,5).getValues();
destinationData.appendRow(filteredRow[0]);
}
}
//number of records of the filtered range
var lastRow = destinationData.getLastRow();
var data = destinationData.getRange(1, 6, lastRow, 1).getValues();
var loop = 0
for(var i=0;i<data.length;i++)
{
if(!data[i][0])
{
var loop = loop + 1
}
}
Logger.log(Utilities.formatString('%1.0f', Math.floor(loop)))
//appendi timestamp al rigo ed eventuale descrizione aggiuntiva inserita dall'utente
var lastRow = destinationData.getLastRow();
var data = destinationData.getRange(1, 6, lastRow, 1).getValues();
var timestamp = Utilities.formatDate(new Date(), "CET", "dd/MM/YYYY HH.mm.ss")
for(var i=0;i<data.length;i++)
{
if(!data[i][0])
{
destinationData.getRange(i+1,6).setValue(timestamp)
destinationData.getRange(i+1,7).setValue(description.getResponseText())
}
}
//cancella l'intervallo originale
var maxRows = sourceData.getMaxRows();
var data = sourceData.getRange(1, 1, maxRows, 1).getValues();
for(var i=data.length; i>=0;i--)
{
if (data[i] == chiave) {
sourceData.deleteRow(i+1)
}
}
var fineTRASFERISCIVALORI = Utilities.formatDate(new Date(), "CET", "HH:mm:ss.SSS");
var inTime=inizioTRASFERISCIVALORI.split(":");
var outTime= fineTRASFERISCIVALORI.split(":");
var hr = outTime[0] - inTime[0];
var min = ((outTime[1] - inTime[1])+hr*60)%60;
var sec = ((outTime[2] - inTime[2])+min*60)%60;
var duration = Utilities.formatString('%2.0f', Math.floor(hr)) + 'h ' + Utilities.formatString('%2.0f', Math.floor(min)) + 'm ' + Utilities.formatString('%2.0f', sec) + 's';
ui.alert('Trasferite '+ Utilities.formatString('%1.0f', Math.floor(loop)) +' righe in '+ duration, ui.ButtonSet.OK)
} else if (response.getSelectedButton() == ui.Button.CANCEL) {
SpreadsheetApp.getUi().alert('L\'utente ha annullato l\'operazione');
} else {
SpreadsheetApp.getUi().alert('L\'utente ha chiuso la finestra di dialogo');
}
}
You might try this:
var data = sourceData.getRange(1, 1, lastRow, 5).getValues();
var chiave = response.getResponseText();
for(var i=0;i<data.length;i++)
{
if (data[i][0] == chiave)
{
//var filteredRow = sourceData.getRange(i+1,1,1,5).getValues();
destinationData.appendRow(data[i]);
}
}
And you might consider the same thing on your destination data.
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var calendarName = "CALENDAR";
var calTimeZone = CalendarApp.openByName(calendarName).getTimeZone();
var menuEntries = [ {name: "Update Calendar", functionName: "cal1"} ];
ss.addMenu("Calendar", menuEntries);
ss.setSpreadsheetTimeZone(calTimeZone);
}
function cal1() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var dataSheet = ss.getSheetByName("Sheet1");
var calendarName = "CALENDAR";
var calTimeZone = CalendarApp.openByName(calendarName).getTimeZone();
var startRow = 3;
var numRows = 100;
var dataRange = dataSheet.getRange(startRow, 1, numRows, 26);
var data = dataRange.getValues();
var cal = CalendarApp.getCalendarsByName("CALENDAR")[0];
for (i in data) {
var row = data[i];
if (row[3] == "Add") {
var title = row[24]; // Column with Title
var desc = row[21]; // Column with Description
var startdate = row[12]; //Column with Date
var enddate = row[14]; //Column with Location
cal.createEvent(title, startdate, enddate);
}
}
}
This is a simple script that's supposed to look through my spreadsheet and add it to my calendar if column 3 is "Add". The code runs without any error but when I check my calendar there is nothing there. Am I doing something wrong here?
After checking the logger I realised that the indexing starts from 0 (column C = row[2], not row[3]). It's a silly mistake, and as much as I'd like to delete this question away I thought it would be good to put this out there in case someone does the same thing.