Here is an example of the sheet I am trying to make this work for: https://docs.google.com/spreadsheets/d/1M79ki9QVRkfkwy1uWyNAVddeyPpcvqDppi3Et-b-cLw/edit?usp=sharing
The goal is that an email is sent when column I is greater than 2 and column K is null. Column I is a count formula based on columns C-H, which are manually filled in.
Here is the script I have, but it doesn't seem to be working:
function sendEmail() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName("Workable");
var listing_id = sheet.getRange("B:B").getValues();
var flag_count = sheet.getRange("I:I").getValues();
var action_type = sheet.getRange("K:K").getValues();
var subject = 'New Listing Flagged';
var message = 'Listing' + listing_id + 'has been flagged. Please resolve ASAP in the QA Google Sheet. Thank you!';
var email_address = 'allyson#hipcamp.com';
if (flag_count > 2 && action_type == ""){MailApp.sendEmail(email_address, subject, message)}
}
What do I need to change to make it work?
I got your latest code in the sample sheet. Since you've decided to use onEdit trigger, you need to use the installable onEdit trigger because you are using MailApp.sendEmail() service which requires authorization.
Pre-requisite (installable trigger):
Sample Code:
function sendNotification1(e) {
Logger.log(JSON.stringify(e));
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var row = e.range.getRow();
var col = e.range.getColumn();
var startRow = 2;
var startCol = 3; //Column C
var endCol = 8; //Column H
if(sheet.getName() == "Workable"
&& col >= startCol
&& col <= endCol
&& row >= startRow
&& sheet.getRange(row,9).getValue() > 2 //flag_count column > 2
&& sheet.getRange(row,11).getValue() == "" //action column null
)
{
var campground_id = sheet.getRange(row,2).getValue();
var email = 'your#email.com';
var subject = "New Listing Flagged for Fraud";
var body = 'Campground ' + campground_id + ' has been flagged for fraud. Please resolve ASAP in the QA Google Sheet. Thank you!';
MailApp.sendEmail(email, subject, body);
}
}
What it does?
Your column I value depends on the value provided in column C-H. Hence your trigger should be the change in values done in column C-H (column index 3-8). I include an additional condition to check the current active sheet to verify that the modified cells are in sheet Workable. Check the value in column I if greater than 2. Lastly, check if column K is null/empty
Send the email
Output:
(Update)
Checkbox column included that will act as a checker button to trigger the email notification.
Sample Code:
function sendNotification(e) {
Logger.log(JSON.stringify(e));
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var row = e.range.getRow();
var col = e.range.getColumn();
var startRow = 2;
if(sheet.getName() == "Workable"
&& col == 9
&& row >= startRow
&& sheet.getRange(row,9).isChecked()
&& sheet.getRange(row,10).getValue() > 2 //flag_count column > 2
&& sheet.getRange(row,12).getValue() == "" //action column null
)
{
var campground_id = sheet.getRange(row,2).getValue();
var email = 'your#email.com';
var subject = "New Listing Flagged for Fraud";
var body = 'Campground ' + campground_id + ' has been flagged for fraud. Please resolve ASAP in the QA Google Sheet. Thank you!';
MailApp.sendEmail(email, subject, body);
}
}
Sample Sheet:
Related
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()
Absolute noob here !
Background:
am trying to create a Google Sheet which I can update for a series of events and
create Google Calendar events based on those entries
so far, am successful in creating calendar events and also updating back the last column of the sheet with the EventID (iCalUID) - thanks to other stackoverflow posts
am also successful in not creating Duplicates by checking if the EventID (iCalUID) is already present in the last column - thanks again to other stackoverflow posts
But... have another requirement, where am failing:
need to mark an existing event as 'Cancelled' in one of the columns in the sheet and
if this is 'true' then look-up the EventID (iCalUID) from the corresponding last cell (of that row which has a 'Cancelled' entry) and
delete that particular event from the calendar
also, calendar events should NOT be created again as long as that cell remains/retains the word 'Cancelled'.
the "var check1 = row[23]; //Booked/Blocked/Cancelled" in below script was just added to bring in this logic that I wanted, but am kind of unable to proceed
Relevant screen-shot of the sheet
Code that I used so far as below:
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Sync to Calendar')
.addItem('Sync Now', 'sync')
.addToUi();
}
function sync() {
var sheet = SpreadsheetApp.getActive().getSheetByName('Sheet1');
var calendar = CalendarApp.getCalendarById('myemailid#gmail.com');
var startRow = 2; // First row from which data should process > 2 exempts my header row
var numRows = sheet.getLastRow(); // Number of rows to process
var numColumns = sheet.getLastColumn();
var dataRange = sheet.getRange(startRow, 1, numRows-1, numColumns);
var data = dataRange.getValues();
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var name = row[1]; //Name of Guest
var place = row[4]; //Add2
var room = row[9]; //Room Number
var inDate = new Date(row[10]); //Check-In Date
var outDate = new Date(row[11]); //Check-Out Date
var check1 = row[23]; //Booked/Blocked/Cancelled
var check2 = row[24]; //Event created and EventID (iCalUID) populated
if (check2 == "") {
var currentCell = sheet.getRange(startRow + i, numColumns);
var event = calendar.createEvent(room, inDate, outDate, {
description: 'Booked by: ' + name + ' / ' + place + '\nFrom: ' + inDate + '\nTo: ' + outDate
});
var eventId = event.getId();
currentCell.setValue(eventId);
}
}
}
I believe your goal is as follows.
You want to check the columns "X" and "Y".
When the column "X" is not Cancelled and the column "Y" is empty, you want to create a new event.
When the column "X" is Cancelled and the column "Y" is not empty, you want to delete the existing event.
When the column "X" is Cancelled, you don't want to create a new event.
In this case, how about the following modification?
Modified script:
In this script, in order to check whether the event has already been deleted, Calendar API is used. So please enable Calendar API at Advanced Google services.
function sync() {
var calendarId = 'myemailid#gmail.com'; // Please set your calendar ID.
var sheet = SpreadsheetApp.getActive().getSheetByName('Sheet1');
var calendar = CalendarApp.getCalendarById(calendarId);
var startRow = 2; // First row from which data should process > 2 exempts my header row
var numRows = sheet.getLastRow(); // Number of rows to process
var numColumns = sheet.getLastColumn();
var dataRange = sheet.getRange(startRow, 1, numRows - 1, numColumns);
var data = dataRange.getValues();
var done = "Done"; // It seems that this is not used.
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var name = row[1]; //Name of Guest
var place = row[4]; //Add2
var room = row[9]; //Room Number
var inDate = new Date(row[10]); //Check-In Date
var outDate = new Date(row[11]); //Check-Out Date
var check1 = row[23]; //Booked/Blocked/Cancelled
var check2 = row[24]; //Event created and EventID (iCalUID) populated
// I modified below script.
if (check1 != "Cancelled" && check2 == "") {
var currentCell = sheet.getRange(startRow + i, numColumns);
var event = calendar.createEvent(room, inDate, outDate, {
description: 'Booked by: ' + name + ' / ' + place + '\nFrom: ' + inDate + '\nTo: ' + outDate
});
var eventId = event.getId();
currentCell.setValue(eventId);
} else if (check1 == "Cancelled" && check2 != "") {
var status = Calendar.Events.get(calendarId, check2.split("#")[0]).status;
if (status != "cancelled") {
calendar.getEventById(check2).deleteEvent();
}
}
}
}
Reference:
Events: get
I tried inserting timestamp when a row is being copied and data inserts or edits in Column C same row cell, but it works only on manual entry, not on copy-paste.
Please suggest to me what I am missing or doing wrong.
function onChange() {
var s = SpreadsheetApp.getActiveSheet();
var sName = s.getName();
var r = s.getActiveCell();
var row = r.getRow();
var ar = s.getActiveRange();
var arRows = ar.getNumRows()
// Logger.log("DEBUG: the active range = "+ar.getA1Notation()+", the number of rows = "+ar.getNumRows());
if( r.getColumn() == 3 && sName == 'Sheet1') { //which column to watch on which sheet
// loop through the number of rows
for (var i = 0;i<arRows;i++){
var rowstamp = row+i;
SpreadsheetApp.getActiveSheet().getRange('F' + rowstamp.toString()).setValue(new Date()).setNumberFormat("MM/dd/yyyy hh:mm"); //which column to put timestamp in
}
}
}//setValue(new Date()).setNumberFormat("MM/dd/yyyy hh:mm:ss");
Use getLastColumn() to check whether column C is included in the pasted range.
Use getNumRows() to get the number of rows your copied range has, and so add the timestamp to all these rows.
No need to used an installed onChange() for this, a simple onEdit() is enough.
I'd also suggest to use event object in order to get information on which range was edited (even though this way you won't be able to fire this successfully from the script editor).
Edit: if you want to remove the timestamp when the range is cleared, you can just check that's the case, using every, or some, and clearContent if that's the case.
Code snippet:
function onEdit(e) {
var s = SpreadsheetApp.getActiveSheet();
var r = e.range;
var firstRow = r.getRow();
var numRows = r.getNumRows();
var firstCol = r.getColumn();
var lastCol = r.getLastColumn();
if((firstCol <= 3 || lastCol >= 3) && s.getName() == 'Sheet1') {
var emptyRange = r.getValues().every(row => row.every(value => value === ""));
var destRange = s.getRange(firstRow, 6, numRows);
if (emptyRange) destRange.clearContent();
else {
var dates = new Array(numRows).fill([new Date()]);
destRange.setValues(dates).setNumberFormat("MM/dd/yyyy hh:mm");
}
}
}
The following script will create timestamps starting from column F until the last column when you copy the row.
I think you are looking for this:
function onEdit(e) {
const startCol = 6; // column F
const s = e.source.getActiveSheet();
const sName = s.getName();
const ar = e.range;
const row = ar.getRow();
const arColumns = ar.getNumColumns();
const arRows = ar.getNumRows();;
if( sName == 'Sheet1') {
const rng = s.getRange(row,1,arRows,s.getMaxColumns());
check = rng.getValues().flat().every(v=>v=='');
if(check){
rng.clearContent();
}
else{
s.getRange(row,startCol,arRows,s.getMaxColumns()-startCol+1).setValue(new Date()).setNumberFormat("MM/dd/yyyy hh:mm");
}
}
}
Note:
Again, onEdit is a trigger function. You are not supposed to execute it manually and if you do so you will actually get errors (because of the use of the event object). All you have to do is to save this code snippet to the script editor and then it will be triggered automatically upon edits.
This question is similar to "Forms Data Manipulation In Google Sheets" (https://webapps.stackexchange.com/questions/88736/forms-data-manipulation-in-google-sheets) but requires a bit more automation:
Background: Users fill out a google form for a request and have the option of repeating those same questions to fill out a second, third, fourth, and fifth request. I have created a sheet that will manipulate these rows so that rows with identical columns will be transferred to one column.
Here is my example sheet:
https://docs.google.com/spreadsheets/d/11DM7z_vwuR1S6lgMN7Wu7a0GoouVc2_5xj6nZ1Ozj5I/edit#gid=1967901028
Form Responses: sheet that returns the responses from users filling out form
Manipulated Rows: sheet that returns manipulated rows using:
=OFFSET('Form Responses'!$A$2,ceiling((row()-row($B$1))/5,1)-1,column()-column($B$1),1,COUNTA($B$1:$D$1)) in cell B2,
and
=OFFSET('Form Responses'!$A$2,ceiling((row()-row($B$1))/5,1)-1,mod(row()-(row($B$1)+1),5)*COUNTA($E$1:$N$1)+COUNTA($B$1:$D$1),1,COUNTA($E$1:$N$1))
in cell E2
Paste Values: this sheet returns a paste values of Manipulated Rows, excluding the Offset formula and then deleting any rows that have blank cells E-N. Here is the apps script reflected in the 'Paste Values' tab:
var ss = SpreadsheetApp.getActive();
var sheet = SpreadsheetApp.getActiveSheet();
//Duplicate sheet 'Manipulated Rows' as paste values
function moveValuesOnly() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Paste Values');
var source = ss.getRange('Manipulated Rows!A1:T100000');
source.copyTo(ss.getRange('Paste Values!A1'), {contentsOnly: true});
deleteRows(sheet);
}
//Function to Delete empty rows:
function deleteRows(sheet) {
var rows = sheet.getDataRange();
var range_manipulated_rows = ss.getSheetByName('Manipulated Rows!A1:T100000');
var range_paste_values = ss.getSheetByName('Paste Values!A1:T100000');
var numRows = rows.getNumRows();
var values = rows.getValues();
var rowsDeleted = 0;
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
if (range_manipulated_rows == range_paste_values && row[4] == '' && row[5] == '' && row[6] == '' && row[7] == '' && row[8] == '' && row[9] == ''
&& row[10] == '' && row[11] == '' && row[12] == '' && row[13] == '') { // if paste values tab is equal to manipulated rows tab and cell E-N are blank
sheet.deleteRow((parseInt(i)+1) - rowsDeleted);
rowsDeleted++;
}
}
};
I want to make this more automated by creating an apps script that will directly convert the sheet of 'Form Responses' to the sheet of 'Paste Values' without using Manipulated Rows. As in the 'Paste Values' sheet, it needs to remove any rows where all of cells E-N are blank.
You want to directly convert the values of "Form Response" to "Paste Values" using Google Apps Script.
There are 5 cycles of "Address" to "Do you have another printer request?" of the columns of "D" to "AZ". The data might be 1 cycle and 3 cycles. But The maximum 5 cycles are constant.
From your question and comments, I could understand above. How about this sample script?
Sample script:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var src = ss.getSheetByName("Form Responses");
var dst = ss.getSheetByName("Paste Values");
var values = src.getDataRange().getValues();
var header = values.splice(0, 1)[0].splice(0, 13);
var res = values.reduce(function(ar, e) {
var h = e.splice(0, 3);
h.unshift("");
for (var i = 0; i < 5; i++) {
var temp = e.splice(0, 10);
if (temp.filter(String).length == 0) continue;
if (temp.length < 10) temp.splice(temp.length, 10 - temp.length, "");
ar.push(h.concat(temp));
}
return ar;
}, []);
if (dst.getRange("A1").getValue() != "Status") res.unshift(["Status"].concat(header));
dst.getRange(dst.getLastRow() + 1, 1, res.length, res[0].length).setValues(res);
}
Note:
In this sample script, the sheet names of Form Responses and Paste Values are used. If you want to change the sheet name, please modify the script.
In this sample script, the header row of the sheet of Paste Values is automatically set. If you don't want to set this, please modify the script.
References:
splice()
reduce()
concat()
I'm a little stuck on this one. I'm trying to find the corresponding row and update the last column in another google spreadsheet after updating the first column of another spreadsheet.
When the user selects "Restocked" in ColA of spreadsheet X , I need to lookup the ID value in ColB on another sheet (Y). Then I need to access spreadsheet Y, find the row that contains that same ID. Access the last column or columnAZ (52) and change the cell value to "Restocked".
Here is what I have so far...
function restockComplete(){
var s = SpreadsheetApp.getActiveSpreadsheet();
if( s.getName() == "Restock Queue"){
var r = s.getActiveCell();
if ( r.getColumn() == 1 && r.getValue() == "Restocked"){
var nextCell = r.offset(0, 1);
var buybackId = nextCell.getValue();
// Opens SS by its ID
var ss = SpreadsheetApp.openById("xxxxxxxxxxxxxxxSheetIDHerexxxxxxxxxxx");
var sheet = ss.getSheetByName('NameOfSheetHere'); // Name of sheet
// var range = sheet.getRange(1,1); // Gets Column 1 Cell 1 value
//var data = range.getValue();
var data = sheet.getDataRange().getValues();
//var buyback = sheet.getRange(buybackId).getValue();
for(var i = 0; i<data.length;i++){
if(data[i][1] == buybackId){ //[1] because column B
Logger.log((i+1))
i.offset(0, 52).setValue('Restocked');
return i+1;
}
}
}
}
};
You're close, save for one error. Right now, to test for the sheet name, you have to actually get the sheet. I would do the following to fix the immediate problem:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getActiveSheet() // call this for the active sheet
...
You also had an error in your loop setting the value:
for(var i = 0; i<data.length;i++){
if(data[i][1] == buybackId){ //[1] because column B
// Get the range of the cell, not the index.
sheet.getRange((i+1), 1).setValue('Restocked');
}
}
...
Rename your secondary sheet from ss to something else to avoid a conflict.
Beyond this fix, there are some changes I'd recommend making to make the script more efficient for your users. Rather than look for the active cell, you can use the onEdit simple trigger with an e event object. This will allow your script to modify cells regardless of where the active cell is.
function onEdit(e){
var ss = SpreadsheetApp.getActiveSpreadsheet()
var s = ss.getActiveSheet();
if( s.getName() == "Restock Queue"){
if ( (e.range.getColumn() == 1.0) && (e.range.getValue() == "Restocked") ){
var nextCell = e.range.offset(0, 1);
var buybackId = nextCell.getValue();
var ss2 = SpreadsheetApp.openById('xxx');
var sheet = ss2.getSheetByName('NameOfSheetHere'); // Name of sheet
var data = sheet.getDataRange().getValues();
for(var i = 0; i<data.length;i++){
if(data[i][1] == buybackId){ //[1] because column B
// Get the range of the cell, not the index.
sheet.getRange((i+1), 52).setValue('Restocked');
}
}
}
}
}