Here is the sample sheet containing the apps script:
https://docs.google.com/spreadsheets/d/1AOzYMVqOqww4bUfSigDWz6gL6tmESfQxuKw4auNS4kU/edit?usp=sharing
I have a master list that tracks if a transaction letter is sent. 3 letters are supposed to be sent after the transaction date - 1st, 3rd, and 6th months after the initial transaction date so there are 3 checkboxes for each ID. If a letter is sent, right now I manually check one checkbox off depending on what letter month it was for. I created a second sheet that will show all the earliest dates for each ID that is not checked and that is before the date on cell B2. My goal is when I run the script, it will check off all the corresponding checkboxes where the Customer ID and Date is matching.
Here is my current apps script code:
function myFunction() {
var sh1 = Sheets.Spreadsheets.Values.get('1AOzYMVqOqww4bUfSigDWz6gL6tmESfQxuKw4auNS4kU','Payment Due by Date!A4:D');
var sh2 = Sheets.Spreadsheets.Values.get('1AOzYMVqOqww4bUfSigDWz6gL6tmESfQxuKw4auNS4kU','Master List!A2:K');
for(var i = 0; i < sh1.values.length; i++){
var sh1Id = sh1.values[i][0]; //sheet1 ID
var sh1DD = sh1.values[i][2]; //sheet1 date
for(var j = 0; j < sh2.values.length; j++){
var sh2Id = sh2.values[j][0]; //sheet2 ID
var sh2m1d = sh2.values[j][3]; //sheet2 month1 date
var sh2m1c = sh2.values[j][4]; //sheet2 month1 checkbox
var sh2m3d = sh2.values[j][5]; //sheet2 month3 date
var sh2m3c = sh2.values[j][6]; //sheet2 month3 checkbox
var sh2m6d = sh2.values[j][7]; //sheet2 month6 date
var sh2m6c = sh2.values[j][8]; //sheet2 month6 checkbox
//Logger.log(sh2m1c);
//compare the ID & month and check the corresponding checkbox
if(sh1Id == sh2Id && sh1DD == sh2m1d)
sh2m1c = "TRUE";
else if(sh1Id == sh2Id && sh1DD == sh2m3d)
sh2m3c = "TRUE";
else if(sh1Id == sh2Id && sh1DD == sh2m6d)
sh2m6c = "TRUE";
}
}
}
When I checked the logger, the loop is I think getting the values(correct me if I'm wrong). I dont know if my loop is incorrectly ran or if how im checking the checkbox is wrong.
EDIT: A specific example in my sheet of what im trying to do:
If in the Payment Due date this row is present
Customer ID
Customer Name
Payment Due Date
Payment Mo Due
1042
Tom, C
6/5/2020
6th Mo Due
When the script runs I want the checkbox under the 6th Mo Due in Master List for ID 1042 to be checked.
Customer ID
Customer Name
Transaction Date
1st Mo Due
1st Mo Sent?
3rd Mo Due
3rd Mo Sent?
6th Mo Due
6th Mo Sent?
Last Unchecked Date (CALCULATED)
Last Date Due (CALCULATED)
1042
Tom, C
12/5/2019
1/5/2020
TRUE
3/5/2020
TRUE
6/5/2020
FALSE
6/5/2020
6th Mo Due
So under the 6th Mo Sent? column, the checkbox will be checked which is now FALSE or unchecked
Try this:
function myFunction() {
var sheet1 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Master List');
var sheet2 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Payment Due By Date');
var spreadsheetId = SpreadsheetApp.getActiveSpreadsheet().getId();
//Use getDisplayValues so the complier won't automatically convert the date based on timezone.
var sheet1_val = sheet1.getRange(2, 1, 12, 11).getDisplayValues();
//get Ids and merge into 1 dimensional array
var sheet1_ids = [].concat(...sheet1.getRange(2, 1, 12, 1).getDisplayValues());
var sheet2_val = sheet2.getRange(4,1,sheet2.getLastRow()-3, 4).getDisplayValues();
for(var i = 0; i < sheet2_val.length; i++){
var customer_id = sheet2_val[i][0];
var pay_due_date = sheet2_val[i][2];
//check if customer id exists in the id list (sheet1_ids)
if (sheet1_ids.indexOf(customer_id) !== -1) {
//get the position/index of the id
var index = sheet1_ids.indexOf(customer_id);
//I used 3 and 8 since we only need the column D-H
for(var j = 3; j < 8; j++){
//Iterate and check each column if match to the pay_due_date
if(pay_due_date == sheet1_val[index][j]){
//assign new value
sheet1_val[index][j+1] = "TRUE";
}
}
}
}
var lastRow = sheet1.getLastRow();
//merge subarrays with same index
var sheet1Transpose = transpose(sheet1_val);
var request = {
'valueInputOption': 'USER_ENTERED',
'data': [
{
'range': 'Master List!E2:E'+lastRow,
'majorDimension': 'COLUMNS',
'values': [sheet1Transpose[4]]
},
{
'range': 'Master List!G2:G'+lastRow,
'majorDimension': 'COLUMNS',
'values': [sheet1Transpose[6]]
},
{
'range': 'Master List!I2:I'+lastRow,
'majorDimension': 'COLUMNS',
'values': [sheet1Transpose[8]]
}
]
};
var response = Sheets.Spreadsheets.Values.batchUpdate(request, spreadsheetId);
}
function transpose(matrix) {
return matrix[0].map((col, i) => matrix.map(row => row[i]));
}
Note: Make sure to add Google Sheets in Services.
Example:
Payment Due Date:
Before:
After:
Reference:
getDisplayValues
Writing to multiple ranges
Related
I am trying to do hideColumns() in google sheets. The starting column is always fixed (column B) but the number of columns to hide is variable depending on a date function. Each cell in the header row has a date, and I want to hide all columns with a date before today's date. So I did a lookup and found the index number of the column with today's date and I want to simply reference that index minus 1 to come up with the number of columns to hide.
The below will work if I hard code a value for number of columns (e.g. 3) but it won't work if I reference i or even try to redefine i as a variable
function onOpen(e) {
// Get column index of yesterday's date
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getSheetByName('Daily Tracker');
var lastColumn = sheet.getLastColumn();
var data = sheet.getRange(1,2,1,lastColumn).getDisplayValues().flat(); //create an array of data from row 1
for (var i = 0; i <= data.length; i++) {
var dateToday = Utilities.formatDate(new Date(), "EST", "MM/dd/yyyy")
if (data[i] == dateToday)
{
return i;
break;
}
var hiddenColumnCount = i - 1;
// Number of columns to hide = index - 1 (e.g. 10/14/2020 = index of 8. I need to hide columns 10/6/2020 through 10/13/2020, which = 7 columns so i - 1)
// Hide all columns from column B through column with yesterday's date
sheet.hideColumns(2,hiddenColumnCount);
}
}
I don't know if i is unable to be referenced because maybe it's a string and not a number since I flattened the array from above. But I tried converting to a number using parseInt() but not sure if it worked or if it's the right thing. I've also tried defining var hiddenColumnCount = 0 above the for statement and then instead of return i I tried typing hiddenColumnCount = i - 1 but this didn't work either
I'm new to programming and try to look everything up on my own but I've hit a road block
I believe your goal as follows.
From Each cell in the header row has a date, and I want to hide all columns with a date before today's date. and I want to simply reference that index minus 1 to come up with the number of columns to hide., in your situation, when the date of today can be found, you want to hide the columns "B" to the left side of the column with the date.
Modifcation points:
In your case, it seems that the column number of the date of today can be retrieved. I think that this can be used for achieving your goal.
When the array of data is looped, please modify the condition of for loop from for (var i = 0; i <= data.length; i++) { to for (var i = 0; i < data.length; i++) {. When i <= data.length is used, the last loop is the index of out of array.
In your script, when the date of today is found, the script is finished.
When hideColumns is used, I think that in your case, this can be put to the out of for loop.
When above points are reflected to your script, it becomes as follows.
Modified script:
function onOpen(e) {
// Get column index of yesterday's date
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getSheetByName('Daily Tracker');
var lastColumn = sheet.getLastColumn();
var data = sheet.getRange(1,2,1,lastColumn).getDisplayValues().flat(); //create an array of data from row 1
// I modified below script.
var columnNumber = 0;
for (var i = 0; i < data.length; i++) {
var dateToday = Utilities.formatDate(new Date(), "EST", "MM/dd/yyyy");
if (data[i] == dateToday) {
columnNumber = i + 2;
}
}
sheet.hideColumns(2, columnNumber - 2);
// Here, columnNumber is the column number of the found date.
}
Note:
In this modification, it supposes that the date is the ascending order from the column "B". Please be careful this. When above modified script is not the result you expect, I think that it might be required to confirm the situation of Spreadsheet.
Reference:
hideColumns(columnIndex, numColumns)
Try this:
function onOpen(e) {
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Daily Tracker');
var data = sheet.getRange(1,2,1,sh.getLastColumn()).getDisplayValues().flat();
for(var i = 0; i <= data.length; i++) {
if (data[i]==Utilities.formatDate(new Date(), "EST", "MM/dd/yyyy")){break;}else{sh.hideColumn(i+2);}
}
}
I have a rota (a fixed order of rotation (as of persons or duties)) that I've already had help with this week. It's up & running as is, but for simpler reading I'd like to transpose it.
You can see the transposed sheet as I'd like it here
The current script is for the pre-transposed table.
It would search Column 0 for the date. If it was was 7 days away it would retrieve the name from Column 1 & match it with e-mail address in separate sheet etc.
What I'd like to do is instead have the Date in Row 0 & then subsequent names in Row 1 etc etc
I've tried various things. I've stepped through the code & can see what it's doing, & I've done some reading through about 2 dimensional arrays, but I can't seem to find a way of getting the code to work down through columns, instead of across the rows.
Here's the code:
function sendEmails() {
var ss1 = SpreadsheetApp.getActiveSpreadsheet();
var sh1 = ss1.getSheetByName("Rota")
ss1.setActiveSheet(sh1);
var rotalink = "https://docs.google.com/spreadsheets/d/1LgzUWSAGA2kbpar8r5nosU1bSHF7nrtvtUiHS3nB_e8";
var sheet = SpreadsheetApp.getActiveSheet();
// Fetch the range
var dataRange = sheet.getRange("B3:G50")
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (i in data) {
var row = data[i];
var today=new Date();
var timecell = new Date(row[0]);
var timediff = new Date();
var one_day=1000*60*60*24;
var daystogo = Math.ceil((timecell.getTime()-today.getTime())/(one_day));
if (daystogo==7) {//only e-mail people with one week to go. To change that alter the "7" to the number of days you want
var subject = "Rota reminder!";
var emailAddress = [];
var message;
message = "Hello \n\n"+
"You are down to help at Youth Café this week. \n\n" +
"Please see the below rota for your role \n\n" +
"If you have any questions or problems let us know at thameyouthcafe#gmail.com \n\n" +
"Remember, you can check the rota anytime by clicking on the link below: \n\n"+
rotalink
for (var x = 1; x < 5; x++) { // 5 because emails are till col4
// var emailAddress = []; // Start by collecting the non-blank emails in an array
if (getEmailFromName(row[x]) != "") {
emailAddress.push(getEmailFromName(row[x]))
}
}
emailAddress = emailAddress.join(); // Join the array to get a comma separated string
MailApp.sendEmail(emailAddress, subject, message);
}
}
}
and here's the getEmailFromName function that matches with SKey (which I presume comes from the "i" variable in the first function?
function getEmailFromName(sKey) {
// to use this function, don’t put anything in the first column (A) or row (1).
// Put the name (i.e. the key, or what we’re looking for) in column B.
// Put what we want to return in column C.
var columnToSearch = 1; //column B
// Set the active sheet to our email lookup
var ss1 = SpreadsheetApp.getActiveSpreadsheet();
var sh1 = ss1.getSheetByName("EmailContactList")
ss1.setActiveSheet(sh1);
var data = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
var line = -1;
for( var i = 0; i < data.length; i++ ) {
if( data[i][columnToSearch] == sKey ) {
line = i;
break;
}
}
if( line != -1 ) {
//do what you want with the data on "line"
return data[line][2]; //value on column C of the matched line
}
else {
return "";
// if criteria is not found
}
}
Try it this way:
function sendEmails() {
var ss1 = SpreadsheetApp.getActive();
var sh1 = ss1.getSheetByName("Rota")
ss1.setActiveSheet(sh1);
var rotalink = "https://docs.google.com/spreadsheets/d/1LgzUWSAGA2kbpar8r5nosU1bSHF7nrtvtUiHS3nB_e8";
var sheet = SpreadsheetApp.getActiveSheet();
var dataRange = sheet.getRange("B3:G50")
var data = dataRange.getValues();
for (var i=0;i<dataRange.length;i++) {
var row = data[i];
var today=new Date();
var timecell = new Date(row[0]);
var timediff = new Date();
var one_day=1000*60*60*24;
var daystogo = Math.ceil((timecell.getTime()-today.getTime())/(one_day));
if (daystogo==7) {
var subject = "Rota reminder!";
var emailAddress = [];
var message = Utilities.formatString('Hello\n\nYou are down to help at Youth Café this week.\n\n Please see the below rota for your role \n\nIf you have any questions or problems let us know at thameyouthcafe#gmail.com \n\nRemember, you can check the rota anytime by clicking on the link below: \n\n%s',rotalink);
for (var x=1;x<5;x++) {
if(data[i][x]) {
emailAddress.push(data[i][x]);
}
}
MailApp.sendEmail(emailAddress.join(), subject, message);
}
}
}
Managed to solve it - thank you for your contributions. Turned out it was incredibly simple.
Just had to change this line:
var timecell = new Date(data[0])
to this:
var timecell = new Date(data[0][i])
so it iterates through the first row of each column.
I attempted to build a script but there are some issues. The table format are 2 columns which are date and values. These are the needs:
IDEAL STATE
Grab the last filled row (today's date) in the Google Sheets called "test".
Check in that row if the value in column F is greater than 0.5.
If it greater than 0.5, then trigger an email.
In email body, it should state "Results found on [date]."
This was my starting point but it does not produce what I want. These are the issues:
CURRENT STATE
1.The script grabs every row in which column F was greater than 0.5 in the past. I only want to check for today (which would be the last row). It should not look through everything in the past.
2.The email body states: Result found on [row number]". This makes no sense. I want the date to show, not the row number.
This is the current code. Please help.
function readCell() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("test");
var values = sheet.getRange("F3:F").getValues(); //this has the values
var date = sheet.getRange("D3:D").getValues(); // this has the date
var results = [];
for(var i=0;i<values.length;i++){
if(values[i]>=0.5)
{
results.push("Result found on:" +(i+3));
}
}
MailApp.sendEmail('blabla#gmail.com', 'Alert', results.join("\n"));
};
Last Row in this context is Row 217, not 218, assuming sheet.getLastRow() would ignore #DIV/o! values. See screenshot for this.
LATEST UPDATE
The current Error is related "toDateString". I think it may be related that my Google Sheet is one day behind. So, it today is Jan 10, the last row in my Google Sheet is Jan 9th. I think that is why the error happens. Can you confirm? In that case, how do I change it to today-1 day?
See below.
Here's how you can check the last row:
function readCell() {
var sheet = SpreadsheetApp.getActive().getSheetByName('test');
var lastRow = sheet.getLastRow();
var value = sheet.getRange('F' + lastRow).getValue();
var date = sheet.getRange('D' + lastRow).getValue();
if (value >= 0.5) {
var result = 'Result found on: ' + date;
MailApp.sendEmail('blabla#gmail.com', 'Alert', result);
}
};
After seeing your data, I think the code below would suit you better.
function readCell() {
var sheet = SpreadsheetApp.getActive().getSheetByName('test');
var dates = sheet.getRange('D1:D').getValues();
var date = null;
var dateRow = 0;
var dateCount = dates.length;
var yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
var yesterdayString = yesterday.toDateString();
for (dateRow; dateRow < dateCount; ++dateCount) {
date = dates[dateRow];
if (date instanceof Date) {
date = date.toDateString();
if (date === yesterdayString) {
++dateRow;
// To account for zero-based array
break;
}
}
}
var value = sheet.getRange('F' + dateRow).getValue();
if (value >= 0.5) {
var result = 'Result found on: ' + date;
MailApp.sendEmail('blabla#gmail.com', 'Alert', result);
}
};
The aim of the script below is to send an email from a Google sheet, BUT I only want it to send if a condition is met.
In my spreadsheet in column M (row 12), the value is either TRUE or FALSE. The cell shows TRUE or FALSE based on an AND script that runs inside the cell. For example, one condition for TRUE is that column P is empty (rather than showing EMAIL_SENT). The AND statement works fine.
But when I run the script, it just sends emails regardless of this condition. So something is wrong with the condition...
I based it on this tutorial here... but I just ran it like that and the original doesn't seem to work for me either, which doesn't help I guess!
// Create a date object for the current date and time.
var now = new Date();
// This constant is written in column C for rows for which an email
// has been sent successfully.
var EMAIL_SENT = "EMAIL_SENT";
var SENDYESNO = "FALSE";
function sendEmails2() {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 2; // First row of data to process
var numRows = 2; // Number of rows to process
// Fetch the range of cells A2:B3
var dataRange = sheet.getRange(startRow, 1, numRows, 16)
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var title = row[0];
var name = row[2];
var link = row[9];
var emailAddress = row[10]; // First column
var message = row[14]; // Second column
var emailSent = row[16]; // Third column
var date = row[17];
var yesNo = row[12];
if (yesNo != SENDYESNO) { // Prevents sending duplicates
var subject = "Sending emails from a Spreadsheet";
GmailApp.sendEmail(emailAddress, subject, "Dear " + title + " " + name + ", \n\nThis is an email report of your link. \n\nYour link is " + link + " \n\nKind regards,\nName ", {
from: "my#email.com",
name: "My name"});
sheet.getRange(startRow + i, 16).setValue(EMAIL_SENT);
sheet.getRange(startRow + i, 17).setValue(now);
// Make sure the cell is updated right away in case the script is interrupted
SpreadsheetApp.flush();
}
}
}
I would guess your value is Column M is a boolean true/false and not a text string. You are comparing it to a text string. Try changing
var SENDYESNO = "FALSE";
to
var SENDYESNO = false;
to test against the boolean. I would also change the logic to test for true and then change the if test from != to ===
var SENDYESNO = true;
//more code...
if (yesNo === SENDYESNO) { //continue with your code...
I have a data range of which the first column are dates and the second column are numerical values. There are some rows where the second column value is blank and I do not want them to be counted.
I am trying to find the average of the second column values if they satisfy the criteria of being within 3 months ago from today (blank values should not be counted).
But I am stuck as I cannot even get the total correct. And I do not know how to proceed further to get the average.
The code belows seem to give me appended strings instead of summing up the numbers mathematically.
Can anyone help please?
Thanks in advance.
function average() {
// open spreadsheet
var spreadsheet = SpreadsheetApp.openById("spreadsheetID");
// set the named sheet as active
var sheet = SpreadsheetApp.setActiveSheet(spreadsheet.getSheetByName("SheetName"));
// figure out what the last row is
var lastRow = spreadsheet.getSheetByName("Form responses 1").getLastRow();
// the rows are indexed starting at 1, and the first row is the headers, so start with row 2
var startRow = 2;
// get the data range
var responsesValues = spreadsheet.getSheetByName("Form responses 1").getRange("A1:Q" + lastRow).getValues();
// define the dates
var timeToday = new Date().getTime();
var dateToday = new Date().getDate();
var date = new Date();
var threeMonthsAgo = new Date(date.getFullYear(), date.getMonth() - 3, 0);
// grab column 1 (date of entry column) (second variable in getRange function below)
var dataRange = sheet.getRange(2,1,lastRow-startRow+1,1 );
var numRows = dataRange.getNumRows();
var dateOfEntryValues = dataRange.getValues();
// grab column 2 (values to be averaged)
range = sheet.getRange(2, 2, lastRow-startRow+1, 1);
var Values = range.getValues();
var warning_count = 0;
var sumValues = 0;
// Loop over the values
for (var i = 0; i <= numRows - 1; i++) {
var dateOfEntry = dateOfEntryValues[i][0];
if(dateOfEntry > threeMonthsAgo && dateOfEntry !== "") {
// if it's within 3 months ago, add the values.
sumValues += Values[i][0];
warning_count++;
}
}
There is a lot more simple version. Just put into the Destination Cell the formula
=ArrayFormula(AVERAGE(IF(DATEDIF(C1:C;TODAY();"D")<=90;E1:E)))
replacing C1:C with the column with dates and E1:E with the column with numbers.