Handling multiple and duplicate emails for Google MailApp - loops

I am trying create a script for Google Sheets that does the following:
1) triggers on a certain date
2) compiles information from columns based on a date criteria
3) sends an email digest of multiple column values to each unique triggered email row
I've looked up several alternate examples, but I cannot grasp the syntax to handle step 3.
The data is generated from Google Forms submissions.
Submissions are added continuously. Script has a time-driven trigger.
I want it to compile all data for entries by the SAME email respondent on the triggered date AND separate out data intended for different email addresses.
I've grabbed a script and altered it to the best of my ability. (see below)
Currently, it produces the following results:
1) script triggers correctly based on date
2) compiles information from triggered columns and reports in one email to all addresses from triggered rows. (incorrect behavior)
Currently the script runs a 'for loop' to grab all triggered data for body of email and sends ALL data to ALL emails. I understand why it does this but I can't wrap my head around how to execute 'step 3' above.'
I think I need to create an object for the email data and run another "for loop" to isolate the reference email.
I'm not sure where to go from here, or how to grab the column value to isolate the required email.
Spreadsheet layout:
Column A: Timestamp
Column B: Recorded Email
Column C - F: Personalized Info
Column G: Requested Followup Date
Column H: Days Remaining until Followup Date
Data set starts on Row 2
Email is triggered when Column H reports value of 0.
Thank you in advance for any and all assistance
Current Script:
function checkReminder() {
// get the spreadsheet object
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
// set the first sheet as active
SpreadsheetApp.setActiveSheet(spreadsheet.getSheets()[0]);
// fetch this sheet
var sheet = spreadsheet.getActiveSheet();
// figure out what the last row is
var lastRow = sheet.getLastRow();
// the rows are indexed starting at 1, and the first row
// is the headers, so start with row 2
var startRow = 2
// grab column 8 (the 'days left' column)
var range = sheet.getRange(2,8,lastRow-startRow+1,1);
var numRows = range.getNumRows();
var reminder_date = range.getDisplayValues();
//grab DOB column
var DOB = sheet.getRange(2,4,lastRow-startRow+1,1)
var DOBrange = DOB.getDisplayValues();
//grab ph# column
var phnum = sheet.getRange(2,5,lastRow-startRow+1,1)
var phnumrange = phnum.getDisplayValues();
//Admin Date of 1st shot
var Admindate = sheet.getRange(2,6,lastRow-startRow+1,1)
var Admin = Admindate.getDisplayValues();
// Now, grab the patient name column
range = sheet.getRange(2,3, lastRow-startRow+1,1);
var patient_info_values = range.getValues();
//grab email
var Emailrows = sheet.getRange(2,2,lastRow-startRow+1,1);
var Emaillist = Emailrows.getValues();
var sendit = 0;
var msg = "";
var greeting = "This email is to remind your team to followup with the following individuals. \n \n";
// Loop over the days left values
for (var i = 0; i <= numRows - 1; i++) {
var days_left = reminder_date[i][0];
if(days_left == 0) {
// if it's equal to 0, do something with the data.
var Adminreport = Admin[i][0];
var patient_name = patient_info_values[i][0];
var DOB_name = DOBrange[i][0];
var phnumgrab = phnumrange[i][0];
msg = msg + " "+patient_name+" DOB: "+DOB_name+" Phone#: "+phnumgrab+" First dose given "+Adminreport+" \n\n";
sendit++;
}
}
if(sendit)
{
MailApp.sendEmail(Emaillist, "Followup Reminder", greeting + msg);
}
};

Related

Vlookup at Google Apps Script with for loop [duplicate]

This question already has answers here:
Google Script version of VLookup (More Efficient Method?)
(2 answers)
Closed 2 years ago.
I need your help please. I would like to do a for loop or something else that works like a Formula =Vlookup
I have two Sheets. in Sheet1 (Overview) there are ID's like 1000, 1002, 1003,...,100X in Column A;
Sheet2 is a Form Response Sheet (Response), where you need to enter your ID and an Action with 'Ok' and 'Nok'. The ID I enter appears in Sheet2 Column B and the Action (Ok/Nok) apperas in Sheet2 Column C.
Now I would like to Copy the Ok/Nok to the Row with the same ID in the Overview sheet with a onFormSubmit function.
for Example. Person with ID 1005 makes a form response with the Action 'Ok'. Now should the 'Ok' copied to the Overview sheet in Column B and in the exact row (in this case: row with the ID 1005).
Here is my function. but I don't want to have formulars in the sheet. so I aked for another solution.
function vlookup() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var cell = sheet.getRange(1,5);
cell.setFormula('=ARRAYFORMULA(IFS(ROW(Response!B:B)=1,"Action from
User",Response!B:B="","",TRUE,IFERROR(VLOOKUP(A:A,Response!B:C,2,0),"Waiting for Response")))');
}
Hope someone can help me.
Thank you in advance for your help!
Jonas
Thank you for all these answers. I tryed the code from stackoverflow.com/questions/60255775 – TheMaster and that workes fine!
but it seams very complicated for a programming beginner. exspecially the part with the "Hash".
I also added a second compare and copy to get the data from a Reason if the Nok is used in the Form.
const ss = SpreadsheetApp.getActive();
/**
* #param {GoogleAppsScript.Spreadsheet.Sheet} fromSht -Sheet to import from
* #param {GoogleAppsScript.Spreadsheet.Sheet} toSht -Sheet to import to
* #param {Number} fromCompCol -Column number of fromSht to compare
* #param {Number} toCompCol -Column number of toSht to compare
* #param {Number} fromCol -Column number of fromSht to get result
* #param {Number} toCol -Column number of toSht to get result
*/
function copyToOverview(e,response,
fromSht = ss.getSheetByName('Response'),
toSht = ss.getSheetByName('Overview'),
fromCompCol = 2,
toCompCol = 1,
fromCol = 3,
toCol = 2,
fromColRej = 4,
toColRej = 3
) {
const toShtLr = toSht.getLastRow();
const toCompArr = toSht.getRange(2, toCompCol, toShtLr - 1, 1).getValues();
const fromArr = fromSht.getDataRange().getValues();
fromCompCol--;
fromCol--;
fromColRej--;
/*Create a hash object of fromSheet*/
const obj1 = fromArr.reduce((obj, row) => {
let el = row[fromCompCol];
el in obj ? null : (obj[el] = row[fromCol]);
return obj;
}, {});
/*Create a second hash object of fromSheet to copy the Reason why it is Nok (also from filling out the Form) */
const obj3 = fromArr.reduce((obj2, row) => {
let el1 = row[fromCompCol];
el1 in obj2 ? null : (obj2[el1] = row[fromColRej]);
return obj2;
}, {});
//Paste to column first toSht copy the "ok/nok" second toSht for the Reason why Nok
toSht
.getRange(2, toCol, toShtLr - 1, 1)
.setValues(toCompArr.map(row => (row[0] in obj1 ? [obj1[row[0]]] : [null])));
toSht
.getRange(2, toColRej, toShtLr - 1, 1)
.setValues(toCompArr.map(row => (row[0] in obj3 ? [obj3[row[0]]] : [null])));
}
I also tried the Code from "Michiel the Temp" and it seams, that it also works.
The code from "Mateo Randwolf" looks very simple and I tried it too. Works also very good!
I have modified it a bit and it works like I wish! I think I will use this code.
function onFormSubmit(e) {
// Get the sheet where the form responses are submitted and the one where we want to check the IDs
var formSheet = SpreadsheetApp.getActive().getSheetByName('Response');
var destinationSheet = SpreadsheetApp.getActive().getSheetByName('Overview');
// Get the new incoming data (ID and Ok/Nok) with each form submit by accessing
// the trigger object e which is the submited and new form response row
var submittedId = formSheet.getRange(e.range.getRow(), 2).getValue();
var submittedValue = formSheet.getRange(e.range.getRow(), 3).getValue();
var submittedValueReason = formSheet.getRange(e.range.getRow(), 4).getValue();
// get all the ID values we have in the sheet we want to check them. flat will convert all the returning
// 2D array of values in a 1D array with all the IDs
var idRange = destinationSheet.getRange(1, 1, destinationSheet.getLastRow(),1).getValues().flat();
// iterate over all your IDs
for(i=0;i<idRange.length;i++){
// if one ID is the same as the incoming one from the form response
if(idRange[i] == submittedId){
// set its value to the one submitted by the form
destinationSheet.getRange(i+1, 2).setValue(submittedValue);
}
if(idRange[i] == submittedId){
destinationSheet.getRange(i+1, 3).setValue(submittedValueReason);
destinationSheet.getRange(i+1, 2).getValue() == "Nok" ? destinationSheet.getRange(i+1, 4).setValue("Closed") : destinationSheet.getRange(i+1, 4).setValue("Open");
}
}
}
Thank you all for the Help you are amazing!
So I can do my next step in the Project with updating checkboxes in the Form.
I didn't test this with a trigger but this should work
function vlookup() {
var ssOverview = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Overview");
var ssOverviewLr = ssOverview.getLastRow();
var ssOverviewData = ssOverview.getRange(2, 1, ssOverviewLr, 1).getValues(); //assuming you have a header in the first row
var ssResponse = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Response");
var ssResponseLr = ssResponse.getLastRow();
var newResponse = ssResponse.getRange(ssResponseLr, 2, 1, 2).getValues();
var Ids = ssOverviewData.map(function (r){return r[0];});
for(var i = 0; i < newResponse.length; i++)
{
var row = newResponse[i];
var id = row[0];
var action = row[1];
var index = Ids.indexOf(id);
if(index == -1)
{
SpreadsheetApp.getActiveSpreadsheet().toast("No matches", "Be aware")
}
else
{
ssOverview.getRange(index + 2, 2).setValue(action); //this puts the action in column B
}
}
}
In order to check the IDs every time there is a new form submission and change the data in the ID sheet accordingly you will need to use installable triggers. Specifically you should use a FormSubmit trigger which triggers the function every time there is a form submission. Along with this trigger you will use its event object.
To add an installable trigger, in your Apps Script editor go to Edit -> Current project's triggers and create a new trigger by clicking Add trigger. Make sure that you select On form submit as the event type and that you select the function presented below (so please first copy/paste the function below before creating your trigger).
The following function takes use of this trigger event to compare the incoming data to your Column A of IDs and check for matches and if so it adds the relevant Ok/Nok information. It has self explanatory comments:
function onFormSubmit(e) {
// Get the sheet where the form responses are submitted and the one where we want to check the IDs
var formSheet = SpreadsheetApp.getActive().getSheetByName('Form Responses 1');
var destinationSheet = SpreadsheetApp.getActive().getSheetByName('Check');
// Get the new incoming data (ID and Ok/Nok) with each form submit by accessing
// the trigger object e which is the submited and new form response row
var submittedId = formSheet.getRange(e.range.getRow(), 2).getValue();
var submittedValue = formSheet.getRange(e.range.getRow(), 3).getValue();
// get all the ID values we have in the sheet we want to check them. flat will convert all the returning
// 2D array of values in a 1D array with all the IDs
var idRange = destinationSheet.getRange(1, 1, destinationSheet.getLastRow(),1).getValues().flat();
// iterate over all your IDs
for(i=0;i<idRange.length;i++){
// if one ID is the same as the incoming one from the form response
if(idRange[i] == submittedId){
// set its value to the one submitted by the form
destinationSheet.getRange(i+1, 2).setValue(submittedValue);
}
}
}

Pulling scattered data from changing spreadsheet

I'm trying to write a script which has multiple parts. The current part (function copyOver) supposed to open a spreadsheet (by ID), extract specific data from it and insert it into a Master spreadsheet.
The part I'm stuck with is that the spreadsheet changes daily. Some days there are cells containing data of "Sea Water", other days there aren't any. Ideally, I was trying to write a script which loops through the sheet looking for the specific tab names in cells (for example: Sea water, chlorine content...) and extract the data from the row below, up until the second tab and so on. I would only need very specific data, like data from "C6", "E6", "F10", but these cells are always changing so I have to look for them by using the tab names in cells above them. There would be multiple arrays for each tab and the data coming with it.
Would that be possible to extract data this way and put them into an array containing the tab value as the header or title and the data connected to that specific tab.
var sourceID = "source sheet ID";
var main = SpreadsheetApp.openById("master sheet ID"); // MASTER
var source = SpreadsheetApp.openById(sourceID); //the workbook you're copying from
var mainsheet = main.getSheetByName("Lab Data"); // the sheet you want to copy the stuff into
var sourcesheet = source.getSheetByName("ANALYSIS REPORT"); // the sheet you're copying from
var dataRange = sourcesheet.getDataRange().getValues(); // gets sheet as data range
// finds SEA WATER tab in cell and gets the range below until the end of the document
// this is the range I need to find the rest of the tabs and the data under them.
var cont = "SEA WATER";
var i = [];
for (var y = 0; y < dataValues.length; y++){
if(dataValues[y] == cont){
i.push(y);
} // end if
} // end for
Logger.log(i);
var Row = Number(i)+Number(dataRange.getRow()); // gets the row number of SEA WATER tab
Logger.log("row number: " + Row);
var swLast = sourcesheet.getLastRow();
var swRange = sourcesheet.getRange(Row,2,swLast,11);
var swValues = swRange.getValues(); // range of needed information
Logger.log("sw: " + swValues);
var con2 = "SW outlet SW from Coarse filtration"; // looking for the secondary tab, within the
range
I got from the first loop
var res2 = [];
for(var i2 = 0; i2 < swValues.length; i2++) {
if(swValues[i2][4] === con2) res2.push(data[i2])
var row2 = Number(i2)+Number(swRange.getRow());
Logger.log("row2 " + row2);
} // for end
var look1 = "SW outlet SW from Coarse filtration ";
for(var i1 = 0; i1<dataRange.length;i1++){
if(dataRange[i1][1] == look1){
return i1+1;
}
}
Logger.log((i1+1));
} // end of function
EDIT: Here's a link to a sheet. Required data starts from row 237, until row 268 - but this can change every day. (Deleted the information for privacy reasons.)
Basically, I need all the cells with "x" in them, preferably together with the title cells above them, so I'll know what data it is.
The following code loops through your headers in column B and finds the row in which your header of interest is located
Subsequently, it copies the data starting from this row to the last data row into the sheet "Lab Data":
function myFunction() {
var sourceID = "source sheet ID";
var main = SpreadsheetApp.openById("sourceID"); // MASTER
var source = SpreadsheetApp.openById(sourceID); //the workbook you're copying from
var mainsheet = main.getSheetByName("Lab Data"); // the sheet you want to copy the stuff into
var sourcesheet = source.getSheetByName("ANALYSIS REPORT"); // the sheet you're copying from
var dataRange = sourcesheet.getDataRange()
var dataValues=dataRange.getValues(); // gets sheet as data range
for (var a = 0; a < dataValues.length; a++){
if(dataValues[a][1]=="SEA WATER"){
var Row=a+1;
break;
}
}
if(Row){
var swLast = sourcesheet.getLastRow();
var swRange = sourcesheet.getRange(Row,2,swLast,11);
swRange.copyTo(mainsheet.getRange(mainsheet.getLastRow()+1, 2));
}
} // end of function
I hope this helps to solve your issue.

Search for Row based on Column Value, Change Values of Row Found from Values in Input sheet- Google Apps Script (Sheets)

I'm new to JavaScript... I have 2 spreadsheets created. 1 that has input values, and the 2nd is a target sheet where I want to update information.
I am searching through the target sheet with values from the input sheet with a column labeled "OpportunityID". Once the associated row is found, I want to update the target row with the values from the input sheet.
I've been able to search the target sheet with OpportunityID value from the input sheet and pull the values of columns in that specific row, but I am having trouble changing the values on the target sheet to the corresponding values on the input sheet.
Here is the code I have tried so far that pulls the appropriate information, but I need help resetting the values of that row:
function updateOpportunity() {
// Get active spreadsheets and sheets
var inputSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Search & Create New Records');
var OppsAndContracts = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Opportunities & Contracts');
var opportunityUpdateCopy = inputSheet.getRange('A8:Q8').getValues();
Logger.log(opportunityUpdateCopy);
//Search for Opportunities using OpportunityID
var last=OppsAndContracts.getLastRow();
var data=OppsAndContracts.getRange(1,1,last,17).getValues();// create an array of data from columns A through Q
var opportunityID = updateSheet.getRange("A8").getValue();
Logger.log(opportunityID);
for(nn=0;nn<data.length;++nn){
if (data[nn][0]==opportunityID){
var OppID = (((data[nn][0])));
var OppName = ((data[nn][1]));
var AccountID = ((data[nn][2]));
var AccountName = ((data[nn][3]));
var OppOwner = ((data[nn][4]));
var CloseDate = ((data[nn][5]));
var Amount = ((data[nn][6]));
var ProposalOwner = ((data[nn][7]));
var Stage = ((data[nn][8]));
var AeroServicesProducts = ((data[nn][9]));
var MechServicesProducts = ((data[nn][10]));
var ProjectStatus = ((data[nn][11]));
var PaymentIssues = ((data[nn][12]));
var UniquePaymentTerms = ((data[nn][13]));
var PaymentTerms = ((data[nn][14]));
var ProposalNumber = ((data[nn][15]));
var ContractNumber = ((data[nn][16]));} ;
OppsAndContracts.getRange([data]).setValues(opportunityUpdateCopy);
}
I've also tried getting the cell reference of the cells in the row with the corresponding OpportunityID and setting them with the values from the input sheet, but that hasn't worked either.
Any help or advice is much appreciated!
You need to use the nn value to select the range when there's a match, since nn is effectively your row index.
function updateOpportunity() {
// Get active spreadsheets and sheets
var ss = SpreadsheetApp.getActiveSpreadsheet();
var updateSheet = ss.getSheetByName("Update Sheet"); // You didn't have this defined, so I added
var inputSheet = ss.getSheetByName('Search & Create New Records');
var OppsAndContracts = ss.getSheetByName('Opportunities & Contracts');
var opportunityUpdateCopy = inputSheet.getRange('A8:Q8').getValues();
//Search for Opportunities using OpportunityID
var last = OppsAndContracts.getLastRow();
var data = OppsAndContracts.getRange(1,1,last,17).getValues(); // create an array of data from columns A through Q
var opportunityID = updateSheet.getRange("A8").getValue();
for (var nn = 0; nn < data.length; ++nn) {
if (data[nn][0] == opportunityID) {
OppsAndContracts.getRange(nn + 1, 1, 1, 17).setValues(opportunityUpdateCopy);
}
}
}
(I removed a bunch of the code that was irrelevant to your question.)

Send email using google apps script with attachment and mark checkbox as done

I am trying to build a script which will be sending emails based on the data in a spreadsheet in google sheets.
Column 1 - emailAddress
Column 2 - subject
Column 3 - body
Column 4 - signature
Column 5 - fileName (name of the file I'd like to attach, will be different in each case)
Column 6 - checkbox (new functionality in G Sheets, marked= TRUE, unmarked=FALSE).
The aim is to send email only to those which are not marked as sent. After the script is done checkbox should change to TRUE to avoid duplications.
I wrote the below script however there are two issues:
How to make a fileName variable value which will be taken to code from column 5?
How to force the program to change the status of the checkbox to TRUE and to omit those which are already TRUE?
The function:
function Email() {
var rng = SpreadsheetApp.getActiveSheet().getActiveRange()
var sheet = SpreadsheetApp.getActiveSheet();
var data = rng.getValues();
for (i in data)
{
var rowData = data[i];
var checkbox = data[5]
var file = DriveApp.getFilesByName(data[4])
var emailAddress = rowData[0];
var body = rowData[2];
var subject = rowData[1];
var signature = rowData[3];
var message = body + '\n\n'+ signature;
if(checkbox is != 'TRUE')
{
GmailApp.sendEmail(emailAddress,subject,message,
{
attachments: [file.next().getAs(MimeType.PDF)]
}
);
cell.setValue("TRUE");
}
}
}
How to make a fileName variable value which will be taken to code from column 5?
Create a variable var fileName. For uniqueness purposes just add a counter at the end 1,2, etc so it ends looking like fileName1, fileName2, etc
How to force the program to change the status of the checkbox to TRUE and to omit those which are already TRUE?
Use a double for loops because working with sheets is like working with 2D arrays. Inside that create an if statement:
if( getValue() is != 'TRUE'){
cell.setValue("TRUE")
}
Hopefully you got the idea from the pseudocode.

Google Apps Script: How to copy and automatically edit the data from a spreadsheet to others?

I'm trying to solve this question but despite the effort I have not advanced!
Every day I get a spreadsheet with two tabs of data. Each one has about 9 thousand rows. I need to capture the data from this worksheet and generate two new spreadsheets with one for "daily reporting" (with only the data of this day) and another for create a "database" (with all data previous results). How can I do this automatically with GAS?
I working in this code below so far, but when I will set the values in the new spreadsheet, I got error about range. :( I don't know how to declare that I want copy and set all the rows between 10 and the last with data from
function getData() {
var day = SpreadsheetApp.openById('XXXX');
var tab1 = day.getSheets()[0];
var tab2 = day.getSheets()[1];
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sss = ss.getSheets()[0];
var weeks = tab1.getRange("G8").getValues();
var dates = tab1.getRange("G9").getValues();
var regions = tab1.getRange('A10:A').getValues();
var locnames = tab1.getRange('B10:B').getValues();
var locnums = tab1.getRange('C10:C').getValues();
var divisions = tab1.getRange('D10:D').getValues();
var depnums = tab1.getRange('E10:E').getValues();
var depnames = tab1.getRange('F10:F').getValues();
var saless = tab1.getRange('G10:G').getValues();
var qtys = tab2.getRange('G10:G').getValues();
var datestart = sss.getRange('A2').setValues(dates);
var dateend = sss.getRange('B2').setValues(dates);
var week = sss.getRange('C2').setValues(weeks);
var region = sss.getRange('D2:D').setValues(regions);
var locname = sss.getRange('E2:E').setValues(locnames);
var locnum = sss.getRange('F2:F').setValues(locnums);
var division = sss.getRange('G2:G').setValues(divisions);
var depnum = sss.getRange('H10:H').setValues(depnums);
var depname = sss.getRange('I2:I').setValues(depnames);
var sales = sss.getRange('J2:J').setValues(saless);
var qty = sss.getRange('K2:K').setValues(qtys);
}
If anyone can help me, thank you immensely.
I put a spreadsheet in the link to illustrate the format of sheets and what I need to do. The first two tabs are the same as the ones I get. And in the next two tabs(daily_report, database) are the worksheets that I want to get separately with the code.
https://docs.google.com/spreadsheets/d/1_iz0oansAfINosV2qNoDG0QMNpGS1bp1AL5kJQztRtc/edit?usp=sharing
The target range must be matched to the source range, or there will be an error. The length and width of the target range must be the same as the source data. You can use the data to set the length (number of rows) and width (number of columns)
Parameters:
range(Start Row, Start Column, Number of Rows, Number of Columns)
Code:
var regions = tab1.getRange('A10:A').getValues();
var region = sss.getRange(2,4,regions.length,regions[0].length).setValues(regions);

Resources