Am trying to add a Guest to the calendar through google apps script that takes a calendar request from the user through Google forms---> it then updates a Google online excel sheet if no conflicts and makes the reservation if no conflict all this works fine but if the user tries to edit the time for the reservation they can't as they aren't the owner of the calendar to make edits so the only workaround I could think of is adding there email from the submitted request and add them as guest to that specific event but it ended unsuccessfully.
user submits a google form request with his email name time etc.
google forms update google excel sheet and checks that there are no conflicts.
updates the calendar successfully and sends a confirmation to the user's email.
The user needs to edit the time of his reservation on the calendar but can't as he is not a guest and can't edit the calendar (trying to solve this by automatically adding him as a guest to the requested event.
read so solutions here on adding location but was unsuccessful in adding the user as a guest.
tried adding
var body = {
'sendNotification': true,
'attendees': attendees
};
var ret = Calendar.Events.patch(body, calendarId, eventId);
but was unsuccessful
Code looks like:
// Room Reservation
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var lastRow = sheet.getLastRow();
var lastColumn = sheet.getLastColumn();
// Calendar to output reservations to
var cal145 = CalendarApp.getCalendarById('xxxxxx#resource.calendar.google.com');
// Create use data from user submission
// This function submits data from the google form and inputs it into the "Room Reservation" sheet by the user
function Submission(){
var row = lastRow;
this.timestamp = sheet.getRange(row, 1).getValue();
this.NameReason = sheet.getRange(row, 2).getValue();
this.email = sheet.getRange(row, 3).getValue();
this.date = sheet.getRange(row, 5).getValue();
this.time = sheet.getRange(row, 6).getValue();
this.duration = sheet.getRange(row, 7).getValue();
this.room = sheet.getRange(row, 8).getValue();
// Info not from spreadsheet
this.roomInt = this.room.replace(/\D+/g, '');
this.status;
this.dateString = (this.date.getMonth() + 1) + '/' + this.date.getDate() + '/' + this.date.getYear();
this.timeString = this.time.toLocaleTimeString();
this.date.setHours(this.time.getHours());
this.date.setMinutes(this.time.getMinutes());
this.calendar = eval('cal' + String(this.roomInt));
return this;
}
// Use duration to create endTime variable
function getEndTime(request){
request.endTime = new Date(request.date);
switch (request.duration){
case "1 hour":
request.endTime.setMinutes(request.date.getMinutes() + 60);
request.endTimeString = request.endTime.toLocaleTimeString();
break;
case "2 hours":
request.endTime.setMinutes(request.date.getMinutes() + 120);
request.endTimeString = request.endTime.toLocaleTimeString();
break;
case "3 hours":
request.endTime.setMinutes(request.date.getMinutes() + 180);
request.endTimeString = request.endTime.toLocaleTimeString();
break;
}
}
// Check for appointment conflicts
function getConflicts(request){
var conflicts = request.calendar.getEvents(request.date, request.endTime);
if (conflicts.length < 1) {
request.status = "Approve";
} else {
request.status = "Conflict";
}
}
function draftEmail(request){
request.buttonLink = "xxxxxxx ";
request.buttonText = "New Request";
switch (request.status) {
case "Approve":
request.subject = "Confirmation: " + request.room + " Reservation for " + request.dateString;
request.header = "Confirmation";
request.message = "Your room reservation has been scheduled.";
break;
case "Conflict":
request.subject = "Conflict with " + request.room + "Reservation for " + request.dateString;
request.header = "Conflict";
request.message = "There is a scheduling conflict. Please pick another time."
request.buttonText = "Reschedule";
break;
}
}
function updateCalendar(request){
var event = request.calendar.createEvent(
request.NameReason,
request.date,
//request.reason,
request.endTime
)
}
function sendEmail(request){
MailApp.sendEmail({
to: request.email,
subject: request.header,
htmlBody: makeEmail(request)
})
sheet.getRange(lastRow, lastColumn).setValue("Sent: " + request.status);
}
function sendinvite(request)
{
request.addGuest(email);
}
// --------------- main --------------------
function main(){
var request = new Submission();
getEndTime(request);
getConflicts(request);
draftEmail(request);
Logger.log(request);
sendEmail(request);
if (request.status == "Approve") updateCalendar(request);
if (request.status == "Approve")
{
sendinvite(request);
}
}
The final result should let the user edit the time of the event created although he is not the owner of the calendar. the function should add the user as a guest to that specific event he requested so he could edit the time.
this script works fine currently as is without the addition of guest sendinvite function.
thanks
Related
I have a google sheet with a dropdown menu. When the cell the drop down is in is equal to a certain string a pop up shows up on screen. You answer yes or no.
When you answer yes it sends the email. If you answer no it does not send the email and resets the cell to its previous state.
The issue I am having is that if you select no it does reset to the previous data, but it still sends the email regardless. Please help. I am fairly new and still learning.
UPDATE:
Please see the update below. The only var I had to fix was for rData, other than that the script works as intended now. Thank you so much for your time and input. This was a headache for some time.
function sendMailEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ui = SpreadsheetApp.getUi();
if (ss.getSheetName() == 'Project List' &
ss.getActiveCell().getColumn() == 3 &
ss.getActiveCell().getValue() == "_Complete") {
var alertPromptText = 'Are you sure you want to select Complete? ' +
'This action will notify all parties in the next process that this job is ready for fabrication.';
var promptResponse = ui.alert(alertPromptText, ui.ButtonSet.YES_NO);
if (promptResponse == ui.Button.YES) {
// CHECK START
// variable email needs to be fixed. It gets the column of values.
// it needs to be converted to a comma separated list of recepients
var email = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Email").getRange(1, 1, 100).getValues();
// CHECK END
var rData = e.source.getActiveSheet().getRange(e.range.rowStart,1,1,12).getValues();
sendEmail(email,rData);
} else { // For both 'No' and cancel response to pop-up
fix(e);
}
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
function sendEmail(email,rData) {
var first = 0;
var email = 1;
var emailTemp = HtmlService.createTemplateFromFile("send");
var ws = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Email");
var data = ws.getRange("A1:B" + ws.getLastRow()).getValues();
var lj = rData[0][1];
var j = rData[0][5];
var d = rData[0][3];
var p = rData[0][4];
var m = rData[0][7];
var desc = rData[0][11];
var now = new Date().toLocaleString("en-US");
var msg1 = "Laser Job Number: " + lj + " (" + now + ")" +
"\nProject Job Number: " + j +
"\nDesigner: " + d +
"\nDate Project was Submitted to Programming: " + p +
"\nMaterial used: " + m +
"\nDescription: " + desc;
var subject = "Project Ready for Fab";
Logger.log(msg1);
data.forEach(function(row){
emailTemp.fn = row[first];
emailTemp.msg = msg1;
emailTemp.j = j;
emailTemp.d = d;
emailTemp.lj = lj;
emailTemp.p = p;
emailTemp.m = m;
emailTemp.desc = desc;
//emailTemp.cart = cart;
const htmlMessage = emailTemp.evaluate().getContent();
GmailApp.sendEmail(row[email], subject, "Please open with an email client that supports HTML",
{htmlBody: htmlMessage});
return;
});
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
function fix(e) {
e.range.setNote(e.oldValue);
e.range.setValue(e.range.getNote());
e.range.clearNote();
}
You need to set a variable to capture the response to the pop-up.
And then compare the value of the variable for the go-no-go part of the script.
For example:
var response = ui.prompt('Alert', 'Are you sure you want to select Complete?', ui.ButtonSet.YES_NO);
And then
if (response.getSelectedButton() == ui.Button.YES) { ... }
More here in the documentation.
Update
Try the following script. You'll need to set the onEdit trigger to run the function SendMailEdit
Also, check the code for getting the list of email recepients. Not sure if it will work.
function sendMailEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ui = SpreadsheetApp.getUi();
if (ss.getSheetName() == 'Project List' &
ss.getActiveCell().getColumn() == 3 &
ss.getActiveCell().getValue() == "_Compvare") {
var alertPromptText = 'Are you sure you want to select Compvare? ' +
'This action will notify all parties in the next process that this job is ready for fabrication.';
var promptResponse = ui.alert(alertPromptText, ui.ButtonSet.YES_NO);
if (promptResponse.getSelectedButton() == ui.Button.YES) {
// CHECK START
// variable email needs to be fixed. It gets the column of values.
// it needs to be converted to a comma separated list of recepients
var email = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Email").getRange(1, 1, 100).getValues();
// CHECK END
var rData = ss.getRange(ss.getActiveCell().getRow(), 1, 1, 12).getValues();
sendEmail(email, rData);
} else { // For both 'No' and cancel response to pop-up
fix(e);
}
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
function sendEmail(email, rData) {
var lj = rData[0][1];
var j = rData[0][5];
var d = rData[0][3];
var p = rData[0][4];
var m = rData[0][7];
var desc = rData[0][11];
var now = new Date().toLocaleString("en-US");
var msg = "Laser Job Number: " + lj + " (" + now + ")" +
"\nProject Job Number: " + j +
"\nDesigner: " + d +
"\nDate Project was Submitted to Programming: " + p +
"\nMaterial used: " + m +
"\nDescription: " + desc;
Logger.log(msg);
GmailApp.sendEmail(email, "Project Ready for Fab", msg);
return;
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
function fix(e) {
e.range.setNote(e.oldValue);
e.range.setValue(e.range.getNote());
e.range.clearNote();
}
I recently came across a video from Kurt Kaiser where he goes through the process of creating an Appointment Booking System.
I'm new to coding and I followed his steps along with having the code that he has shared and I cannot seem to get this to work. For some reason after I submit the form and approve the request it never puts the event in my calendar.
I'm calling the calendar in the script I made by the ID. Is there something else I need to do after I create the calendar in order for this to work?
Do I need to make my calendar public in google?
Again I followed his instruction video to the T and still don't get the events in the calendar.
I also get an error for name under the :// Creates a calendar event using the submitted data in the code below, I believe it said it wasn't defined. But maybe that's because I was just running the script?
I have tried to reach out to the author of the code but he has not responded.
Again he has freely shared this code from his website.
Any help would greatly be appreciated as I have been pulling my hair out on this project and trying to come up with a booking system that uses google forms for my team.
`// Calendar Reservation Approval System
// Kurt Kaiser, 2018
// All Rights Reserved
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var lastRow = sheet.getLastRow();
var lastColumn = sheet.getLastColumn();
**// Calendar to output requests
var calendar = CalendarApp.getCalendarById('bfpkrilvj3avj0srbelt3n89rc#group.calendar.google.com');**
// Creates an object from the last form submission
function Submission(row){
this.timestamp = sheet.getRange(row, 1).getValue();
this.name = sheet.getRange(row, 2).getValue();
this.reason = sheet.getRange(row, 3).getValue();
this.date = new Date(sheet.getRange(row, 4).getValue());
this.dateString = (this.date.getMonth() + 1) + '/' +
this.date.getDate() + '/' + this.date.getYear();
this.time = sheet.getRange(row,5).getValue();
this.timeString = this.time.toLocaleTimeString();
this.email = sheet.getRange(row, 6).getValue();
// Adjust time and make end time
this.date.setHours(this.time.getHours());
this.date.setMinutes(this.time.getMinutes());
this.endTime = new Date(this.date);
this.endTime.setHours(this.time.getHours() + 2);
}
// Check for conflicting events
function getConflicts(request) {
var conflicts = calendar.getEvents(request.date, request.endTime);
if (conflicts.length < 1) {
request.status = "New";
} else {
request.status = "Conflict";
sheet.getRange(lastRow, lastColumn - 1).setValue("Reject");
sheet.getRange(lastRow, lastColumn).setValue("Sent: Conflict");
}
}
// Convert data of status and notified columns into array
function StatusObject(){
this.statusArray = sheet.getRange(1, lastColumn -1, lastRow, 1).getValues();
this.notifiedArray = sheet.getRange(1, lastColumn, lastRow, 1).getValues();
this.statusArray = [].concat.apply([], this.statusArray);
this.notifiedArray = [].concat.apply([], this.notifiedArray);
}
// Get the index of the row that has had a status change
function getChangeIndex(statusChange){
statusChange.index = statusChange.notifiedArray.indexOf("");
statusChange.row = statusChange.index + 1;
if (statusChange.index == -1){
return;
} else if (statusChange.statusArray[statusChange.index] != "") {
statusChange.status = statusChange.statusArray[statusChange.index];
sheet.getRange(statusChange.row, lastColumn).setValue("Sent: " + statusChange.status);
statusChange.notifiedArray[statusChange.index] = "update";
} else {
statusChange.status = statusChange.statusArray[statusChange.index];
statusChange.notifiedArray[statusChange.index] = "no update";
}
}
// Draft contents for emails depending on needed message
function draftEmail(request){
request.buttonLink = "button URL" // I would have the URL in place here for the button
request.buttonText = "New Request";
switch (request.status) {
case "New":
request.subject = "Request for " + request.dateString + " Appointment Received";
request.header = "Request Received";
request.message = "Once the request has been reviewed you will receive an email updating you on it.";
break;
case "New2":
request.email = "myemail#address.com";
request.subject = "New Request for " + request.dateString;
request.header = "Request Received";
request.message = "A new request needs to be reviewed.";
request.buttonLink = "https://link to the spreadsheet";// I would have the URL to my spreadsheet here
request.buttonText = "View Request";
break;
case "Approve":
request.subject = "Confirmation: Appointment for " + request.dateString + " has been scheduled";
request.header = "Confirmation";
request.message = "Your appointment has been scheduled.";
break;
case "Conflict":
request.subject = "Conflict with " + request.dateString + " Appointment Request";
request.header = "Conflict";
request.message = "There was a scheduling conflict. Please reschedule.";
request.buttonText = "Reschedule";
break;
case "Reject":
request.subject = "Update on Appointment Requested for " + request.dateString;
request.header = "Reschedule";
request.message = "Unfortunately the request times does not work. Could "+
"we reschedule?";
request.buttonText = "Reschedule";
break;
}
}
// Creates a calendar event using the submitted data
function updateCalendar(request){
var event = calendar.createEvent(
request.name,
request.date,
request.endTime
)
}
// Send Email
function sendEmail(request){
MailApp.sendEmail({
to: request.email,
subject: request.subject,
htmlBody: makeEmail(request)
})
}
// -------------------- Main Functions ---------------------
function onFormSubmission(){
var request = new Submission(lastRow);
getConflicts(request);
draftEmail(request);
sendEmail(request);
if (request.status == "New"){
request.status = "New2";
draftEmail(request);
sendEmail(request);
}
}
// Triggered function to check if any status has changed
function onEdit(){
var statusChange = new StatusObject();
while (true){
getChangeIndex(statusChange);
if (statusChange.index == -1){
return;
} else {
var request = new Submission(statusChange.row);
if (statusChange.status){
request.status = statusChange.status;
if (statusChange.status == "Approve"){
updateCalendar(request);
}
draftEmail(request);
sendEmail(request);
}
}
}
}`
Personally I would do this a little differently:
function onFormSubmission(e){
var request = new Submission(e.range.rowStart);//this determines which row the form just modified
getConflicts(request);
draftEmail(request);
sendEmail(request);
if (request.status == "New"){
request.status = "New2";
draftEmail(request);
sendEmail(request);
}
}
also the below onEdit function cannot just be a simple trigger because some of the functions require permission. This will have to be an installable trigger and don't forget to change the name when you create the installable trigger otherwise the function will get two triggers a simple trigger and an installable trigger
function onEdit(){
var statusChange = new StatusObject();
while (true){
getChangeIndex(statusChange);
if (statusChange.index == -1){
return;
} else {
var request = new Submission(statusChange.row);
if (statusChange.status){
request.status = statusChange.status;
if (statusChange.status == "Approve"){
updateCalendar(request);
}
draftEmail(request);
sendEmail(request);
}
}
}
}
The above function must complete in 30 seconds. Actually I should say the above function will stop after 30 seconds whether it's complete or not
There have been a lot of changes to Google Apps Script since 2018 so some of this code may not work. Especially, with the new ES6.
I'm thinking that your missing an updateCalendar
function onFormSubmission(){
var request = new Submission(lastRow);
getConflicts(request);
draftEmail(request);
sendEmail(request);
if (request.status == "New"){
updateCalendar(request);//added this line
request.status = "New2";
draftEmail(request);
sendEmail(request);
}
}
I am working on a "vehicle scheduler" for my workplace that will display the dates and times a truck will be unavailable. Here's the link to the Google Sheets where the script runs. I have set up two example "busy times" to show what I am doing
One adjustment that was requested from me is to do 30 minute increments, but then the amount of columns gets absolutely bonkers.
I am open to any recommendations to improve this tool, even if it means something better (and free) already exists.
I appreciate your input!
See code below... (I am RIDICULOUSLY novice at coding, so be kind :)
function processForm(formObject){
var day = formObject.dow;
var vehicle = formObject.vehicle;
var jobName = formObject.job;
var driverName = formObject.driver;
var duration = formObject.duration;
var startTime = formObject.startTime;
var ampm = formObject.ampm;
var location = formObject.location;
var returnArray = [day, vehicle, jobName, driverName, duration, startTime, ampm, location]
var startTimeColumn = 0;
var startDayColumn = 2;
var sduration = startTime;
var ss = SpreadsheetApp.getActive().getSheetByName("Week View");
if (day=="Monday"){
startDayColumn = 26;
}
else if (day=="Tuesday"){
startDayColumn = 50;
}
else if (day=="Wednesday"){
startDayColumn = 74;
}
else if (day=="Thursday"){
startDayColumn = 98;
}
else if (day=="Friday"){
startDayColumn = 122;
}
else if (day=="Saturday"){
startDayColumn = 146;
}
if (ampm=="PM"){
startDayColumn+=12;
}
if (startTime==12){
startTime = 0;
}
startTimeColumn = parseInt(startTimeColumn) + parseInt(startDayColumn) + parseInt(startTime);
var columnA = ss.getRange(1,1,ss.getLastRow(),1).getValues();
for (var rr=0;rr<ss.getLastRow();rr++){
if (columnA[rr]==vehicle){
break;
}
}
rr +=1;
for (var cc=0; cc<duration; cc++){
var checkColumn = parseInt(cc) + parseInt(startTimeColumn);
if (ss.getRange(rr, checkColumn).isBlank()){}
else return "It looks like that time is booked :(";
}
var displayString = "Job: " + jobName + String.fromCharCode(10) + "Driver: " + driverName + String.fromCharCode(10) + sduration + ":00 " + ampm + String.fromCharCode(10) + location;
var merging = ss.getRange(rr,startTimeColumn,1,duration).mergeAcross();
var enterInfo = ss.getRange(rr, startTimeColumn).setValue(displayString);
if (rr % 2 == 0){
enterInfo.setBackgroundRGB(45, 114, 157);
}
else {enterInfo.setBackgroundRGB(26, 153, 136);}
enterInfo.setFontColor('white')
return "Success! Load has been booked to the scheduler.";
}
Instead of making Sheets jump thru hoops and create a interface for inserting data and a Calendar to show it, why not use Forms and Calendar to do it all?
What I have in mind
Sheets as a long-time database for Truck Information and schedules
Calendar as a way to visualize the availability of Trucks
Forms as a interface for data-input
Set-up
A Spreadsheet file with a "List of Trucks" Sheet.
A Form attached to it that allows users to insert information about the truck booking.
To make this work, I have inputted some data on out List of Trucks:
Then I associated a form with that sheet and setup some basic questions:
I also modified the forms settings to allow people to edit it on the future, this will allow us to add information to the calendar to tie an event with a response form the form.
I added a script to the Forms that populates the first question with the data from our sheets:
function populateForm() {
var form = FormApp.getActiveForm();
var TruckIdQ = form.getItems()[0];
var truckList = SpreadsheetApp.openByUrl("YOUR SHEET URL").getSheetByName("List of Trucks").getDataRange().getValues().map(function (row) { return row[0]});
truckList.shift(); //Remove header
var choices = [];
for (var i=0; i< truckList.length; i++) {
choices.push(TruckIdQ.asListItem().createChoice(truckList[i]));
}
TruckIdQ.asListItem().setChoices(choices);
}
And set it to be run when the form is opened:
After that I went back to my sheets and modified the automatic Responses sheet to have a more understandable name: "Bookings".
Now we need to create a shared calendar to put all this information on it.
I created one named "Truck Booking Calendar" in the following way:
I then created a new Script on the Forms responsible for populating the calendar with information.
function populateCalendar(e) {
var bookingCalendar = CalendarApp.getCalendarById("CALENDAR ID");
var questions = FormApp.getActiveForm().getItems();
var response = e.response.getItemResponses();
var eventDescription = "";
var truckname;
var fromDate, toDate;
var fromTime, toTime;
for (var i=0; i< questions.length; i++) {
eventDescription = eventDescription + questions[i].getTitle() + " : " + response[i].getResponse() + "\n";
switch (i) {
case 0:
//Truck Id
truckname = response[i].getResponse();
break;
case 1:
//From Date
fromDate = response[i].getResponse();
break;
case 2:
//To Date
toDate = response[i].getResponse();
break;
case 3:
//From Time
fromTime = response[i].getResponse();
break;
case 4:
//To Time
toTime = response[i].getResponse();
break;
}
}
var startDateTime = new Date(fromDate + " " + fromTime);
var endDateTime = new Date(toDate + " " + toTime);
var newEvent = bookingCalendar.createEvent("Booking "+truckname, startDateTime, endDateTime, {description:eventDescription});
newEvent.setTag("truck", truckname);
newEvent.setTag("responseId", e.response.getEditResponseUrl());
newEvent.setTag("responseUrl", e.response.getId());
}
And created a trigger for it like this:
Now, when I submit this into the form:
I get this entry on my calendar:
Hope this helps you!
Google App script not firing all JavaScript pop up boxes using on edit trigger. Does not fire when a change is made to the sheet for multiple users. Also only works when it feels like it. I have set the trigger to onEdit(). My code:
function Error() {
var sheet = SpreadsheetApp.getActiveSheet();
if (sheet.getName() == "Protocal Check List 2"){
var ui = SpreadsheetApp.getUi(); // Same variations.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var active = ss.getActiveCell();
var getactive = ss.getActiveCell().getDisplayValue();
var column = ss.getActiveRange().getColumn();
var row = ss.getActiveRange().getRow();
var msg = '';
var section = sheet.getRange('C'+ row);
switch (column) {
case 9:
var msg = "error 1";
break;
case 10:
var msg = "Error 2";
break;
default:
msg = "Error";
break;
}
if(getactive == "TRUE"){
if(column >= 9 && column <= 60
){
var result = ui.alert(
"pika Says",
msg,
ui.ButtonSet.YES_NO);
// Process the user's response.
if (result == ui.Button.YES) {
// User clicked "Yes".
window.alert('Task Complete: \n\n' + msg);
active.setValue('True');
} else {
var i = 0;
while (i < 1) {
var result = ui.prompt(
'Please detail cause of the problem:',
ui.ButtonSet.OK);
var text = result.getResponseText();
var textcount = text.length;
if(textcount > 0) {
i++;
}}
var cell = "H" + row;
var emailAddress = "email#gmail.com";
var d = new Date();
var n = d.toDateString();
var t = d.toTimeString();
var staffname = ss.getRange(cell).getValues();
var message = "Date: " + n +"\n\nTime: " + t +"\n\nStaff: " + staffname + "\n\nError: " + msg + "\n\nProblem: " + text;
var subject = msg;
var thedate = n + " / " + t;
ss.getRange('A1').setValue(thedate);
ss.getRange('B1').setValue(staffname);
ss.getRange('C1').setValue(msg);
ss.getRange('D1').setValue(text);
var s1 = ss.getRange('A1:D1'); //assign the range you want to copy
var s1v = s1.getValues();
var tss = SpreadsheetApp.openById('1mOzdRgKxiP5iB9j7PqUWKKo5oymWuAeQZ1jJ1s6qL9E'); //replace with destination ID
var ts = tss.getSheetByName('AB Protocal'); //replace with destination Sheet tab name
ts.getRange(ts.getLastRow()+1, 1, 1,4).setValues(s1v); //you will need to define the size of the copied data see getRange()
MailApp.sendEmail(emailAddress, subject, message);
// User clicked "No" or X in the title bar.
//ui.alert("The following note has been sent to the Duty Manager: \n\n" + text);
active.setValue('FALSE');
}}}}
}
Any help would be appreciated. I need it to run every signal time for users who have access to the sheet.
You'll have to
assume that onEdit triggers are best-effort, but may not catch all
edits done to the spreadsheet.
Bug thread is here.
To get around the bug as mentioned by Edward implemente a dumb router.
// create a new file -> router.gs
function routerOnEdit(e) {
myOnEditHook1(e);
myOnEditHook2(e);
myOnEditHook3(e);
}
I have submitted an issue, which is the updated version of a long-running 7 year issue. They closed it in 2022, saying the feature is intended. They have now added a feature where onEdit can queue up to 2 trigger events. The issue I'm having is that the second edit still does not trigger. Despite what the documentation says.
Note: The onEdit() trigger only queues up to 2 trigger events.
View the new issue here: https://issuetracker.google.com/issues/221703754
I am still learning the ropes here. Based on code suggested by other contributors, I put together a script to send reminder emails to consultants who record their time entries using a Google Form. The spreadsheet first imports calendar entries with all the Job information for each consultant. After the calendar entries are imported, if the consultant has not yet recorded their time entry, the following script will send them an email with a reminder to do so:
function sendReminder() {
var rmndrFrom = "XYZ, Inc.";
var myemail = "support#xyz.com";
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Pending");
var numRows = sheet.getLastRow();
var lastCol = sheet.getLastColumn();
var dataRange = sheet.getRange(2, 1, numRows, lastCol); // row 1 is the header row
var sheetData = dataRange.getValues();
for (var i = 0; i < sheetData.length; ++i) {
var row = sheetData[i];
if (row[0]){
var jobNumb = row[0]; // Job Number
var conName = row[2]; // Consultant Name
var conMail = row[3]; // Consultant Email
var jobDate = row[4]; // Date
// format email string
var subject = "Time Entry Reminder: " + conName + " / Job " + jobNumb;
try {
var conMsgH = 'This is a reminder for you to record your time entry for Job #<strong>' + jobNum + '/' + jobDate + '</strong>';
// strip HTML for plain text message
var conMsgP = conMsgH.replace(/\<br\/\>/gi, '\n').replace(/(<([^>]+)>)/ig, "");
// send reminder to consultants
MailApp.sendEmail(conMail, subject, conMsgP, { htmlBody:conMsgH, name:rmndrFrom });
} catch (e) { // error handler
MailApp.sendEmail(myemail, "Error in sending reminder email.", e.message);
}
}
}
}
So basically, this script parses the Pending sheet, if column A has a Job Number, it will send a reminder email to the consultant with that Job Number. However, a single consultant may have several job numbers to their name. I would like the script to send a single email to each consultant with a list of the Job Numbers for which they have to record their time entries.
Thanks in advance for your kind help. Any suggestions on how to optimize the code will also be very much appreciated.
There are a number of ways that you can approach this. One way is to keep a sheet with the consultants emails, names and a list of their job numbers. Load this data into your script, a list of all job ids and the job info. Then filter the job ids based on the consultants list and build your email, or you could just send that list of numbers for a very short script.
Another way is to do all of that sorting per consultant in the code and send out the emails that way. This is the approach I've taken, and I've also made use of the iterative JS functions map, filter and reduce more details at MDN.
The code is posted below, but if you would like to take a look at it attached to a spreadsheet and commented (as well as the functions to build that extra sheet with just the consultants info on it) take a look here.
Below is my iteration of your function. I hope it is helpful for your situation:
var rmndrFrom = "XYZ, Inc.";
var myemail = "me#emailisawesome.com";
var subject = "Time Entry Reminder";
function sendReminder() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Pending");
var numRows = sheet.getLastRow();
var lastCol = sheet.getLastColumn();
var sheetData = sheet.getRange(2, 1, numRows-1, lastCol).getValues();
var cons = sheet.getRange(2,3,numRows-1,1).getValues().reduce(flatten_).filter(getUniqueConsultants_);
cons.forEach(sendEmail_, sheetData);
}
function sendEmail_(consultant) {
var consultantsJobs = this.filter(getJobsForConsultant_, consultant);
var jobList = consultantsJobs.map(buildJobLine_).join("<br>");
try {
var conMsgH = "Hi " + consultant + ",<br>";
conMsgH += "This is a reminder for you to record your time entry for the following jobs:<br><br>";
conMsgH += jobList;
conMsgH += "<br><br>Thank you for your cooperation.";
var conMsgP = conMsgH.replace(/\<br\/\>/gi, '\n').replace(/(<([^>]+)>)/ig, "");
MailApp.sendEmail(consultantsJobs[0][3], subject, conMsgP, {htmlBody:conMsgH, name:rmndrFrom});
} catch (e) {
MailApp.sendEmail(myemail, "Error in sending reminder email.", e.message);
}
}
function buildJobLine_(job) {
return "Job #" + job[0] + " on " + Utilities.formatDate(job[4], Session.getTimeZone(), "MMM dd yyyy");
}
function getJobsForConsultant_(row) {
return row[2] == this;
}
function getUniqueConsultants_(v,i,a) {
return a.indexOf(v) == i;
}
function flatten_(a,b) {
return a.concat(b);
}
I must say that fooby's answer is far beyond my JS skills, I'm sure it will work nicely but I still feel like proposing something different (and simpler from my pov), just for the fun of it ;-)
The main difference with your original script is the sorting of the array that allowed me to detect duplicate names and threat it accordingly. The html composition could be far better for sure but that was not your main request.
Here is the code
function sendReminder() {
var rmndrFrom = "XYZ, Inc.";
var myemail = "support#xyz.com";
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Pending");
var initial = "Hi conName,<BR>This is a reminder for you to record your time entry for the following jobs : <BR><BR><table border = 1 cellpadding = 3 bgcolor='#FFFFBB'><TR><TD>";
var sheetData = sheet.getDataRange().getValues();// simplified code to get the data array
sheetData.shift();//skips the headers
sheetData.sort(function(x,y){
var xp = x[2];// sort on column 3 but you can change here...
var yp = y[2];// sort on column 3 but you can change here...
return xp == yp ? 0 : xp < yp ? -1 : 1;// sort ascending, eventually change here...
});
// Logger.log(sheetData);// sorted
var last = sheetData.length
var i = 1;//index 0 is handled outside the loop
var row = sheetData[0];
var subject = "Time Entry Reminder: " + row[2] + " / Job "
var conMsgH = initial
var msgComponent = makeline_(row)
subject += msgComponent[0]
conMsgH += msgComponent[1]
while (i<last){
if(sheetData[i][2]!=sheetData[i-1][2]||i==last-1){
sendData_(sheetData[i-1][3],sheetData[i-1][2],subject,conMsgH)
var subject = "Time Entry Reminder: " + sheetData[i][2] + " / Job "
var conMsgH = initial;
}
msgComponent = makeline_(sheetData[i])
subject += msgComponent[0]
conMsgH += msgComponent[1]
++i
}
}
function sendData_(conMail,conName,subject,conMsgH){
conMsgH = conMsgH.substr(0,conMsgH.length-8)+'</TABLE>'
conMsgH = conMsgH.replace('conName',conName)
var conMsgP = conMsgH.replace(/<\/tr>/ig, '\n').replace(/<br>/ig, '\n').replace(/(<([^>]+)>)/ig, "")
subject = subject.substr(0,subject.length-2);// remove the last '+ '
// Logger.log(subject)
// Logger.log(conMsgH)
Logger.log(conMsgP)
// Logger.log(conMail)
try{
// send reminder to consultants
MailApp.sendEmail(conMail, subject, conMsgP, { htmlBody:conMsgH, name:rmndrFrom });
} catch (e) { // error handler
// MailApp.sendEmail(myemail, "Error in sending reminder email.", e.message);
}
}
function makeline_(row){
var jobNumb = row[0]; // Job Number
var conName = row[2]; // Consultant Name
var conMail = row[3]; // Consultant Email
var descr = row[1]; // description
var FUS1=new Date(row[4]).toString().substr(25,6)+':00';// get timezone of this event, taking care of daylight savings
var jobDate = Utilities.formatDate(row[4], FUS1, "MMM dd yyyy # hh:mm aaa"); // Date
var subject = jobNumb+' + ';
var conMsgH = 'Job #'+jobNumb + '</TD><TD>' + jobDate + '</TD><TD>' + descr + '</TD></TR><TR><TD>';
return [subject,conMsgH];
}
EDIT : made some improvement in the mail format, used a table to show jobs & dates + removed some bugs ;-) (to be honest, I made this also for my personal use as I am having almost the same use case )