I have a script where I am trying to send emails, and I have almost everything figured out. The last piece is actually selecting the email string from a list of rules.
I want to get the emails (col C) if curSheet.getSheetName() = value in colA AND newVal = value in colB. I am having trouble combining if statements & for loops.
Here is a link to a sample Notification Rules sheet:
[https://docs.google.com/spreadsheets/d/1Cn9R3f85xF_QnOU_BNIvpqRq8FUvRGFaCUnTH3jyCTs/edit#gid=0]
function emailUpdate(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet()
var curSheet = ss.getActiveSheet()
var notifSheets = ["Sheet1", "Sheet2"]
var newVal = e.value
var row = e.range.rowStart
var curDate = Utilities.formatDate(new Date(), "GMT+1", "MM/dd/yyyy")
var hosp = curSheet.getRange(row,4).getValue()
var sheetID = curSheet.getSheetId()
var sheetURL = "linktodoc"+sheetID
var rules = ss.getSheetByName('Notification Rules')
var data = rules.getDataRange().getValues()
//Build Email
var subject = subjecttext
var intro = introtext
var closer = closingtext
if (e.range.columnStart == 2 && notifSheets.indexOf(curSheet.getSheetName()) != -1 && newVal !== undefined) {
console.log("It worked")
curSheet.getRange(row,3).setValue(curDate) //inserts edit date
var email = //some for / if statement to get colC if colA&colB match curSheet & newVal
GmailApp.sendEmail(email,subject,'', {htmlBody: intro+closer})
} else {
console.log("It didn't work")
}
}
Related
The code works when one row is edited at a time. But I need to copy and paste multiple rows as well, and the timestamp only appears on the first row (where I paste it).
function onEdit(e) {
var range = e.range;
var spreadSheet = e.source;
var sheetName = spreadSheet.getActiveSheet().getName();
var row = range.getRow();
var activeSheetName = 'Analysis';
var statusColumnNum = 17;
var updateColumnNum = 15;
if(sheetName == activeSheetName)
{
if(spreadSheet.getActiveSheet().getRange(row,statusColumnNum).getValues() == 'No Further Action' || spreadSheet.getActiveSheet().getRange(row,statusColumnNum).getValues() == 'Invalid Code' || spreadSheet.getActiveSheet().getRange(row,statusColumnNum).getValues() == 'For BAF Filing'){
if(spreadSheet.getActiveSheet().getRange(row,updateColumnNum).getValue() == ''){
var new_date = new Date();
spreadSheet.getActiveSheet().getRange(row,updateColumnNum).setValue(new_date).setNumberFormat("MM/dd/yyyy hh:mm:ss A/P");
}
}
else{
spreadSheet.getActiveSheet().getRange(row,updateColumnNum).setValue('');
}
}
}
In your script, how about the following modification?
Modified script:
function onEdit(e) {
var range = e.range;
var spreadSheet = e.source;
var sheet = spreadSheet.getActiveSheet();
var sheetName = sheet.getName();
var activeSheetName = 'Analysis';
var statusColumnNum = 17;
var updateColumnNum = 15;
var texts = ['No Further Action', 'Invalid Code', 'For BAF Filing'];
if (sheetName == activeSheetName) {
var range = sheet.getRange(range.rowStart, statusColumnNum, range.rowEnd - range.rowStart + 1);
var values = range.getValues();
var new_date = new Date();
range.offset(0, updateColumnNum - statusColumnNum)
.setNumberFormat("MM/dd/yyyy hh:mm:ss A/P")
.setValues(values.map(([q]) => [texts.includes(q) ? new_date : null]));
}
}
In this case, when this script is run by copying and pasting the values, all edited rows are checked. By this, the date is inserted into your expected rows.
Reference:
map()
right now , im having trouble to figuring out a script where when user ticks the checkbox, it will send an email invite 3 weeks in advance based on the payment date
would need your help.
Here is my code which is rather incomplete.
function sendreminder(){
var sheet = SpreadsheetApp.getActiveSheet();
var sheetName = sheet.getName();
var range = e.range;
var approvalEdit = range.getValue().toString(); // Use string to avoid accidentally accepting truthy values.
var column = range.getColumn();
var emailsend = "EMAIL_SENT";
var approvalColumnNo = 12;
var invoice = sheet.getRange(e.range.getRow(),12).getValue();
var calend = CalendarApp.createAllDayEvent();
if( sheetName === "Sheet1" && column === approvalColumnNo && approvalEdit === "true" ){
calend.createAllDayEvent('TEST', new Date('November 20, 2022')
SpreadsheetApp.flush();
Many thanks!
it will send an email invite 3 weeks in advance based on the payment date
You can try the following script:
function calendarEvent(e) {
var sheet = SpreadsheetApp.getActiveSheet();
var sheetName = sheet.getName();
var val = e.value;
var rCol = e.range.getColumn();
var rRow = e.range.getRow();
if(rCol==12 && val=="TRUE" && sheetName=="Sheet1")
{
var val = sheet.getRange(rRow, 10).getValue();
var day = new Date(val).getTime() + 86400000*21; // 21 for the number of days
var nDay = new Date(day);
CalendarApp.createAllDayEvent('This is a test event',nDay);
}
}
Example:
References:
How to add days to date?
createAllDayEvent()
I'm trying to have TIMEVALUE set off a script. I have column P which is a Timer column that is subtracting a time from NOW to get a time value and then I am converting that to a TIMEVALUE in column O. When that value is either below or above certain values I want column N to then have a value so it would trigger my checkboxes script. But for some reason I can't get the TIMEVALUE to trigger. I tried to put it in another column and have the values CopyAndPasted Values Only into a column with that run on a time trigger of every minute, but apparently that doesn't count as an Edit or it's not reading the DisplayValue.
function onEdit(e) {
timeValue(e);
checkboxes(e);
rangerTime(e);
}
function rangerTime(e){
var editRow = e.range.getRow();
var editColumn = e.range.getColumn();
if (editColumn === 13 && e.value == "TRUE") {
const sh = e.range.getSheet();
sh.getRange(editRow, 25).setValue(sh.getRange(editRow, 25).getValue()+1);
sh.getRange(editRow, 13).setValue(" ")
}
}
function timeValue(e) {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getActiveSheet();
var editRow = e.range.getRow();
var editColumn = e.range.getColumn();
if(editRow > 3) {
var rowRange = sheet.getRange("O" + editRow);
var kCell = sheet.getRange("K" + editRow);
var kValue = kCell.getValue();
var nCell = sheet.getRange("N" + editRow);
var kHasValue = kValue != "";
if(editColumn > 10) {
if(rowRange.getDisplayValue()<0.02 && !kHasValue){
nCell.setValue(1);
}
if (rowRange.getDisplayValue()>0.5 && !kHasValue){
nCell.setValue(1);
}
if(kHasValue) {
nCell.setValue(" ");
}
}
}
}
function checkboxes(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
ui = SpreadsheetApp.getUi();
var names = ss.getRange("N4:N");
var namesValues = names.getValues();
var checkboxes = ss.getRange("M4:M");
var cbRows = checkboxes.getHeight();
var cbValues = checkboxes.getValues();
var newCBValues = new Array(cbRows);
for (var row = 0; row < cbRows; row++) {
newCBValues[row] = new Array(0);
if (namesValues[row] == "" || namesValues[row] == " ") {
newCBValues[row][0] = " ";
}else{
if (cbValues[row][0] === true) {
newCBValues[row][0] = true;
}else{
newCBValues[row][0] = false;
}
}
}
checkboxes.setValues(newCBValues);
}
This part is run on the every minute Time Trigger:
function CopyandPaste() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('AA4').activate();
var currentCell = spreadsheet.getCurrentCell();
spreadsheet.getSelection().getNextDataRange(SpreadsheetApp.Direction.DOWN).activate();
currentCell.activateAsCurrentCell();
spreadsheet.getRange('O4').activate();
currentCell = spreadsheet.getCurrentCell();
spreadsheet.getSelection().getNextDataRange(SpreadsheetApp.Direction.DOWN).activate();
currentCell.activateAsCurrentCell();
spreadsheet.getRange('AA4:AA202').copyTo(spreadsheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
};
Column AA = TIMEVALUE(Column P)
From the question
I tried to put it in another column and have the values CopyAndPasted Values Only into a column with that run on a time trigger of every minute, but apparently that doesn't count as an Edit or it's not reading the DisplayValue.
You are right, only actions done by a user directly through the Google Sheets user interface will cause the edit trigger to be triggered.
One option is to make that your time-driven trigger besides changing the checkboxes also call the timeValue function but you have to refactor it or to mock the edit event object to pass it as the timeValue parameter.
Reference
https://developers.google.com/apps-script/guides/triggers
I managed to collect data of user typing in and by using onChange trigger, I did meet my requirements for my task. However, I realized that for example I am filling up Column A1:A4. If my A1:A3 is out of range, it will trigger the email. But if my A4 is within range, the values of A1:A3 will still trigger the email eventhough it has trigger it before. How do I make sure that the values that have been capture do not trigger the email again?
function myFunction() {
var ss = SpreadsheetApp.getActiveSheet();
currentsheet = ss.getSheetName();
//var values = ss.getRange(a1Notation)
//console.log(values);
var lastcol = ss.getLastColumn();
var vibrationtemplate = HtmlService.createTemplateFromFile("vibration");
var temperaturetemplate = HtmlService.createTemplateFromFile("temperature");
//console.log(lastcol);
if((currentsheet == 'A Vibration') || (currentsheet == 'B Vibration')){
console.log(currentsheet);
for(var i =2; i<=lastcol; i++){
var cell = ss.getRange(i,lastcol).getValues();
console.log(""+cell);
if(cell > 8){
console.log("Value is more than 8 and current value is "+cell);
vibrationtemplate.vibrate = cell;
MailApp.sendEmail("someone#gmail.com",
"Parameter Out of Range Notification",
"",{htmlBody: vibrationtemplate.evaluate().getContent()});
}
}
}
if((currentsheet == 'A Temperature') || (currentsheet == 'B Temperature')){
console.log(currentsheet);
for(var i =2; i<=lastcol; i++){
var cell = ss.getRange(i,lastcol).getValues();
console.log(""+cell);
if(cell > 80){
console.log("Value is more than 80 and current value is "+cell);
temperaturetemplate.temp = cell;
MailApp.sendEmail("someone#gmail.com",
"Parameter Out of Range Notification",
"",{htmlBody: temperaturetemplate.evaluate().getContent()});
}
}
}
}
EDIT: Latest code - Using a daily trigger and only checks at the end of the day.
function myFunction() {
const ss = SpreadsheetApp.getActiveSheet();
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var sheetNumber = sheets.length; //Get number of sheets within Spreadsheet
var currentSheet = ss.getIndex()-1; //Get index of current sheet with 0 indexing
var vibrationtemplate = HtmlService.createTemplateFromFile("vibration"); //Create HTML for email by grabbing template from vibration.html
var temperaturetemplate = HtmlService.createTemplateFromFile("temperature"); //Create HTML for email by grabbing template from temperature.html
const vibrationlimit = 8; //Set vibrationlimit as constant equals to 8
const temperaturelimit = 80; //Set temperaturelimit as constant equals to 80
var currentDate = new Date(); //Get system Date and Time
var currentDay = currentDate.getDate(); //Extract Date from Full Date(currentDate)
var currentMonth = currentDate.getMonth() +1; //Extract Month from Full Date(currentDate), add +1 as month index start from 0.
for (var z = currentSheet ; z<sheetNumber ; ++z ){
SpreadsheetApp.setActiveSheet(sheets[z])
var lastcol = sheets[z].getLastColumn();
var lastrow= sheets[z].getLastRow();
var cellDate = sheets[z].getRange(1,lastcol).getValues();
var formattedCellDate = new Date(cellDate);
var cellDay = formattedCellDate.getDate();
var cellMonth = formattedCellDate.getMonth() + 1;
if((z==0) || (z==2)){
if((cellDay == currentDay) && (cellMonth == currentMonth)){
for(var i = 2; i<=lastrow; i++){
var scxvibrationname = sheets[z].getRange(i,1).getValues();
var vibration = sheets[z].getRange(i,lastcol).getValues();
if(vibration > vibrationlimit){
Logger.log("Vibration over 8 - Current Value is "+vibration);
vibrationtemplate.vibrate = vibration;
vibrationtemplate.scxvibration = scxvibrationname;
}
}
}
}
}
You can use PropertiesService to store the last row of the previous script run
The following code sets the start row for the for loop to the (last row+1) of the previous script run, so that only e-mails from newly added rows will be sent:
function myFunction() {
var ss = SpreadsheetApp.getActiveSheet();
currentsheet = ss.getSheetName();
//var values = ss.getRange(a1Notation)
//console.log(values);
var lastcol = ss.getLastColumn();
var lastrow=ss.getLastRow();
var vibrationtemplate = HtmlService.createTemplateFromFile("vibration");
var temperaturetemplate = HtmlService.createTemplateFromFile("temperature");
//console.log(lastcol);
if(PropertiesService.getScriptProperties().getKeys().length==0){
PropertiesService.getScriptProperties().setProperty('startRow', lastrow+1);
}
var startRow=PropertiesService.getScriptProperties().getProperty('startRow');
if((currentsheet == 'A Vibration') || (currentsheet == 'B Vibration')){
console.log(currentsheet);
//I ASSUME THAT YOU WANT TO LOOP THROUGH ALL ROWS AND NOT COLUMNS
for(var i =startRow; i<=lastrow; i++){
var cell = ss.getRange(i,lastcol).getValues();
console.log(""+cell);
if(cell > 8){
console.log("Value is more than 8 and current value is "+cell);
vibrationtemplate.vibrate = cell;
MailApp.sendEmail("someone#gmail.com",
"Parameter Out of Range Notification",
"",{htmlBody: vibrationtemplate.evaluate().getContent()});
}
}
}
...
PropertiesService.getScriptProperties().setProperty('startRow', lastrow+1);
}
function myFunction() {
const ss = SpreadsheetApp.getActiveSheet();
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var sheetNumber = sheets.length; //Get number of sheets within Spreadsheet
var currentSheet = ss.getIndex()-1; //Get index of current sheet with 0 indexing
var vibrationtemplate = HtmlService.createTemplateFromFile("vibration"); //Create HTML for email by grabbing template from vibration.html
var temperaturetemplate = HtmlService.createTemplateFromFile("temperature"); //Create HTML for email by grabbing template from temperature.html
const vibrationlimit = 8; //Set vibrationlimit as constant equals to 8
const temperaturelimit = 80; //Set temperaturelimit as constant equals to 80
var currentDate = new Date(); //Get system Date and Time
var currentDay = currentDate.getDate(); //Extract Date from Full Date(currentDate)
var currentMonth = currentDate.getMonth() +1; //Extract Month from Full Date(currentDate), add +1 as month index start from 0.
for (var z = currentSheet ; z<sheetNumber ; ++z ){
SpreadsheetApp.setActiveSheet(sheets[z])
var lastcol = sheets[z].getLastColumn();
var lastrow= sheets[z].getLastRow();
var cellDate = sheets[z].getRange(1,lastcol).getValues();
var formattedCellDate = new Date(cellDate);
var cellDay = formattedCellDate.getDate();
var cellMonth = formattedCellDate.getMonth() + 1;
if((z==0) || (z==2)){
if((cellDay == currentDay) && (cellMonth == currentMonth)){
for(var i = 2; i<=lastrow; i++){
var scxvibrationname = sheets[z].getRange(i,1).getValues();
var vibration = sheets[z].getRange(i,lastcol).getValues();
if(vibration > vibrationlimit){
Logger.log("Vibration over 8 - Current Value is "+vibration);
vibrationtemplate.vibrate = vibration;
vibrationtemplate.scxvibration = scxvibrationname;
}
}
}
}
}
I manipulated by using daily time-driven trigger.
I've was wondering if what to do is possible and how to go about it. This is For Google Sheets. Here is the script I'm using (Found this script and edited it for my use):
function onEdit(e) {
var s = e.source.getActiveSheet().getName();
var cols = [5];
if (s !== 'Michele' && s !== 'Janet' && s !== 'Stephanie' || cols.indexOf(e.range.columnStart) ==-1 || !e.value) return;
e.range.offset(0,1).setValue(Utilities.formatDate(new Date(), "CST", "MM/dd/yyyy HH:mm:ss"));
}
Column 5 (E) has the option to choose different statuses. So far, I have it putting the TimeStamp of Column 5's change in its adjacent column, 6 (F). What I'm trying to figure out is if the status is changed again (Pending -> Confirmed-> Installed-> ect) , instead of updating Column 6, which would already be TimeStamped, it will instead move on to Column 7, then 8, then 9, ect.
Let me know if more detail is needed.
Thanks a Bunch!
I've tested this code and it works. It finds the number of cells to the right of column E that already have values in them, and then calculates what the next empty column is, and writes the new date to the next empty column.
function onEdit(e) {
var sh = e.source.getActiveSheet();
var s = sh.getName();
var lastCol = sh.getLastColumn();
var cols = [5];
var startCol = SpreadsheetApp.getActiveSpreadsheet().getRangeByName('ColumnE').getColumn();
//In the spreadsheet, name a range that is in the column you want to start at
if (s !== 'Michele' && s !== 'Janet' && s !== 'Stephanie' || cols.indexOf(e.range.columnStart) ==-1 || !e.value) return;
var rowEdited = e.range.getRow(); //Get the row that was edited
//Start in row edited and column 6, get 1 row and X columns
var datesInCells = sh.getRange(rowEdited,startCol,1,lastCol - 5).getValues();
Logger.log('datesInCells: ' + datesInCells);
Logger.log('datesInCells.length: ' + datesInCells[0].length);
var numberOfDates = 0;
for (var i=0;i<datesInCells[0].length;i+=1) {
if (datesInCells[0][i] !== "") {
numberOfDates +=1;
};
};
Logger.log('numberOfDates: ' + numberOfDates);
var colToWriteTo = startCol + numberOfDates;
var newDate = Utilities.formatDate(new Date(), "CST", "MM/dd/yyyy HH:mm:ss");
//var theStatus = sh.getRange(rowEdited, 5).getValue();
var theStatus = e.value;
var bothStatusAndDate = newDate + " : " + theStatus;
sh.getRange(rowEdited,colToWriteTo).setValue(bothStatusAndDate);
};
Version two:
function onEdit(e) {
var sh = e.source.getActiveSheet();
var s = sh.getName();
var lastCol = sh.getLastColumn();
var cols = [5];
if (s !== 'Michele' && s !== 'Janet' && s !== 'Stephanie' || cols.indexOf(e.range.columnStart) ==-1 || !e.value) return;
var rowEdited = e.range.getRow(); //Get the row that was edited
//Start in row edited and column 6, get 1 row and X columns
var datesInCells = sh.getRange(rowEdited,6,1,lastCol - 5).getValues();
Logger.log('datesInCells: ' + datesInCells);
Logger.log('datesInCells.length: ' + datesInCells[0].length);
var numberOfDates = 0;
for (var i=0;i<datesInCells[0].length;i+=1) {
if (datesInCells[0][i] !== "") {
numberOfDates +=1;
};
};
Logger.log('numberOfDates: ' + numberOfDates);
var newDate = Utilities.formatDate(new Date(), "CST", "MM/dd/yyyy HH:mm:ss");
if (numberOfDates > 4) {
var existingValues = sh.getRange(rowEdited,7,1,4).getValues();
sh.getRange(rowEdited,6,1,4).setValues(existingValues);
sh.getRange(rowEdited,10).setValue(newDate);
return;
};
var colToWriteTo = 6 + numberOfDates;
sh.getRange(rowEdited,colToWriteTo).setValue(newDate);
};