range.getValues() With specific Date in Specific Cell - google-apps-script

We are using a Google Script to import a Range from other Spreadsheet to another.
This helped us in the past but now the data is growing and we need to reduce the data that we import. (timeout problems)
We need to import the rows with a specific date on a specific column.
In this case, as you can see in the script below, we are importing cells from 'A1' to 'N last row' in the range variable.
What we need is that in the column 'H' from that range date is checked with something like "Date in column K >= Today()-90"
// iterate all the sheets
sourceSheetNames.forEach(function(sheetName, index) {
if (EXCLUDED_SHEETS.indexOf(sheetName) == -1) {
// get the sheet
var sheet = sourceSpreadSheet.getSheetByName(sheetName);
// selects the range of data that we want to pick. We know that row 1 is the header of the table,
// but we need to calculate which is the last row of the sheet. For that we use getLastRow() function
var lastRow = sheet.getLastRow();
// N is because we want to copy to the N column
var range = sheet.getRange('A1:N' + lastRow);
// get the values
var data = range.getValues();
data.forEach(function(value) {
value.unshift(sheetName);
});
}
});

To conditionally copy the only the rows that meet a criteria, you will want to push them to a new array if they qualify. This push would be added to your existing data.forEach() call:
...
var now = new Date();
var today = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
var kept = [];
var data = range.getValues();
// Add qualifying rows to kept
data.forEach(function(row) {
var colHvalue = row[7];
var colKvalue = row[10];
if( /* your desired test */) {
// Add the name of the sheet as the row's 1st column value.
row.unshift(sheetName);
// Keep this row
kept.push(row);
}
});
/* other stuff with `kept`, like writing it to the new sheet */
You'll have to implement your specific test as you have not shared how time is stored in column H or K (e.g. days since epoch, iso time string, etc). Be sure to review the Date reference.

I've solved this in the past by adding a new column in the spreadsheet which calculates n days past an event.
=ARRAYFORMULA(IF(ISBLANK(K2:K),"",ROUNDDOWN(K2:K - NOW())))
The core of the function is the countdown calculation. For instance, today is Thursday, March 1. Subtracting it from a date in the future like Sunday, March 4, returns a whole integer: 3. I can test for that integer (or any integer) in a simple script.
In your script, add a conditional statement before executing the rest of the function:
// ...
if(someDate === -90) {
// do something...
}
This way, you're just checking the value of a cell rather than doing a calculation in a helper function. The only change (if you want a longer or shorter interval) is in the conditional test.

Related

Google Apps Script - Function Changes Cell Value Dynamically Based On Other Cells

This will likely be an easy question to answer but its an issue I run into often. Most built in google sheet functions change dynamically when cell values are updated, I need my function to do the same rather than needing to run the function to check the dates. The following script is likely messy to anyone who knows what they're doing, but it works. It is built to scan if todays date matches any in the range named "Dates" and return the value in that objects column and row of the active cell. However, this only activates when I first plug the function into the cell. I need it to change the cells value whenever one of those dates matches todays date or when none of them match todays date.
function getClass() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getActiveSheet();
var dateRanges = ss.getRangeByName("Dates");
var dateValues = dateRanges.getValues();
var studentRow = sheet.getActiveCell().getRow();
var todaysDate = Utilities.formatDate(new Date(),"00:00:00", "MM/dd/yyyy");
var datesWithCells = [];
for (i=0;i<=dateRanges.getNumColumns() -1;i++){
var date = [Utilities.formatDate(dateValues[0][i], "00:00:00", "MM/dd/yyyy")];
var col = [dateRanges.getColumn() + i]
datesWithCells.push([col,date]);
};
for (i=0;i<=dateRanges.getNumColumns() -1;i++){;
var dataCol = datesWithCells[i][0];
if (datesWithCells[i][1] == todaysDate){
return sheet.getRange(studentRow,dataCol).getValue();
} else {
return "N/A";
};
};
}
Image shows the cells correctly displaying N/A as no dates match todays date. If todays date did match, it would not change value because the function has already ran and will not update this value as needed
It is unclear why you would want to do that with a custom function, because a plain vanilla spreadsheet formula would seem to suffice:
=hlookup(today(), D2:Z, row() - row(D2) + 1, false)
To answer your question, you should not hard-code the location of the data in the custom function. Instead, pass the values to the custom function through parameters. The formula that uses a custom function will update its result automatically when its parameters change.

Record a cell and copy data to cell in the same row when column matches todays date

As the title suggests I have a cell (B3) that updates it's value hourly. At a certain time each day, let's say 10pm, I'd like a script to run automatically that copies the value (x) from this cell to a cell in the same row, that matches the column headed with today's date.
Here is the example image
today = 26.8.21 (d,m,y :australia)
Here is where I am at so far:
// Record history from a cell and append to next available row
function recordValue() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheetoffruit");
// set today's date
var Date = sheet.getRange("J1").setValue(new Date());
// look up date range
var daterange = sheet.getRange("C1:1");
// find the column position of today's date
if (date = daterange) {
var col = daterange.getColumn(); }
var value = sheet.getRange("B55").getValue();
var row = 55;
sheet.getRange(row, col).setValue(value);
}
I have previously been able to post the value of the ("b55") cell (not in this example) however, I cannot get it to match with the column with today's date.
This function write the xtra fruit value in the column with todays data in row 1 and the last line of the data set.
function recordValue() {
let col = {}
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheetoffruit");
const dhA = sh.getRange(1,1,1,sh.getLastColumn()).getValues()[0];
dhA.forEach((h,i) => {col[h] = i+1});
let xf = sh.getRange(sh.getLastRow(),2).getValue();
sh.getRange(sh.getLastRow(),col[Utilities.formatDate(new Date(),Session.getScriptTimeZone(),"dd-MMM-yyyy")]).setValue(xf);
}
You can run the function from a timebased trigger but make sure you pick a time the ends up in the correct date. Because the col object and dts determine where the column is based upon matching with the strings in row 1.
After you explain how to determine the row then we can try the below function to see what going wrong with the col object which determines which column to write into.

Conditionally edit cells based of the value in cells in other rows - Google Sheet using Google Script

I've got limited knowledge of google script and I'm trying to get better every day. I'm trying to conditionally set the value of 2 cells based on the value in cells contained in other rows. I've got a sample sheet (here) with appointments on it. You'll also see the output desired on the output sheet.
When two or more appointments are taken by the same person based on his email address I want to write Yes in column Duplicate for every duplicate appointments but the most recent (based on Column E, that is the date when the appointment was created) and that are greater than the current date (if the appointment is already in the past no need to do anything). I also want to set the value of the column L to "Not Coming" which is a cell containing a data validation that I already automated on my main spreadsheet.
Here is the script that I already designed based on other questions answered here on stackoverflow. I'm not really familiar with indexes and how to proceed with them. The script runs without errors but nothing happens.
var currentDate = new Date()
// Master
var sheetMaster = ss.getSheets()[0];
var allValues=sheetMaster.getRange(2,1,sheetMaster.getLastRow()-1,sheetMaster.getLastColumn()).getValues();
var emailValues=sheetMaster.getRange(2,3,sheetMaster.getLastRow()-1,3).getValues();
var dateCreatedAtValues=sheetMaster.getRange(2,5,sheetMaster.getLastRow()-1,5).getValues();
var duplicateColumn=sheetMaster.getRange(2,11,sheetMaster.getLastRow()-1,11);
var eM=[];//emails
var dA=[];//dates
var eR=[];//entire rows
var dC=[];//duplicateColumn Yes or empty
function analyzeDuplicateEntries() {
for(var i=0;i<emailValues.length;i++) {
var idx=eM.indexOf(emailValues[i][0]);
if(idx==-1) {
eM.push(emailValues[i][0]);
dA.push(dateCreatedAtValues[i][0]);
eR.push(allValues[i]);
}
else if(new Date(dateCreatedAtValues[i][0]).valueOf() > new Date(dA[idx]).valueOf() && new Date(dateCreatedAtValues[i][0]).valueOf()> currentDate) {
duplicateColumn[i][0].setValue("Yes");
}
}
} ```
You are retrieving the wrong column and set the values to a range cell incorrectly
var mailValues=sheetMaster.getRange(2,3,sheetMaster.getLastRow()-1,3).getValues(); will return columns 3 to 5 (see documentation, while your emails are in column B, that is column 2.
Pay attention that the first parameter in getRange(row, column, numRows, numColumns) is the number of the columns to retrieve, rather than the last column
Mind that to use setValue on a range that contains more than one cell, you need to retrieve first the respective cell with getCell()
Take thereby into consideration that the cell indexes start with 1 (opposed to array indexes that start with 0`).
A simple (not optimal) way to rewrite your code would be:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var currentDate = new Date()
// Master
var sheetMaster = ss.getSheets()[0];
var allValues=sheetMaster.getRange(2,1,sheetMaster.getLastRow()-1,sheetMaster.getLastColumn()).getValues();
var emailValues=sheetMaster.getRange(2,2,sheetMaster.getLastRow()-1,1).getValues();
var dateCreatedAtValues=sheetMaster.getRange(2,5,sheetMaster.getLastRow()-1,1).getValues();
var duplicateColumn=sheetMaster.getRange(2,11,sheetMaster.getLastRow()-1,1);
var eM=[];//emails
var dA=[];//dates
var eR=[];//entire rows
var dC=[];//duplicateColumn Yes or empty
function analyzeDuplicateEntries() {
for(var i=0;i<emailValues.length;i++) {
var idx=eM.indexOf(emailValues[i][0]);
if(idx==-1) {
eM.push(emailValues[i][0]);
dA.push(dateCreatedAtValues[i][0]);
eR.push(allValues[i]);
}
else if(new Date(dateCreatedAtValues[i][0]).valueOf() > new Date(dA[idx]).valueOf() && new Date(dateCreatedAtValues[i][0]).valueOf()> currentDate) {
duplicateColumn.getCell(i+1, 1).setValue("Yes");
}
}
}

How can I make a 'google sheet script' or formula to change values in different cells based on a checkbox in the sheet

I am really stuck to convert this idea to a script or a formula.
The task is sequential A then B and then C. I have this google sheet that has a column with only checkboxes. I want to click the checkbox when a payment is done. And then the sequence of things need to take place.
Pay count has to be increased by 1.
Renewal date has to be reset to the new date.
The Checkbox should go dimmed(unclickable) after the previous operations are done and remain there until there are only about 20 days left when it should become active(clickable) again.
Now there are formulae in some of the cells:
A. Pay Count column (contains how many times a payment is done) has this formula:
=IF(ISBLANK(PAIDON),,IF(RENEWON="",1,ROUNDUP(DAYS(RENEWON,PAIDON)/period)))
B. RENEWON column (calculates the next renewal date) has this formula:
=IF(ISBLANK(PAIDON),,IF(OR(SUBSCRIPTION="LifeTime",SUBSCRIPTION="OneTime"),,DATE(YEAR(L2),MONTH(L2)+IFS(SUBSCRIPTION="Yearly",12,SUBSCRIPTION="2Yearly",24,SUBSCRIPTION="3Yearly",36,SUBSCRIPTION="4Yearly",48,SUBSCRIPTION="5Yearly",60, SUBSCRIPTION="Monthly",1),DAY(L2)-1)))
You can understand that I am kind of a newbie here. So please do ask me for any information I missed here.
Need suggestions of how to convert the whole idea to a script or formula.
Any idea/guidance is helpful to me.
UPDATE: Additional info: My ranges are given in here for further help:
Also thanks for the right formatting! I definitely need lessons on them
you can get checkboxes column values first using :
// This function gets the full column Range like doing 'A1:A9999' in excel
// #param {String} column The column name to get ('A', 'G', etc)
// #param {Number} startIndex The row number to start from (1, 5, 15)
// #return {Range} The "Range" object containing the full column: https://developers.google.com/apps-script/class_range
function getFullColumn(column, startIndex){
var sheet = SpreadsheetApp.getActiveSpreadsheet();
// sheet.setActiveSheet(sheet.getSheetByName('sheet_name')); //use this if you have different sheets
sheet = SpreadsheetApp.getActiveSheet();
var lastRow = sheet.getLastRow();
return sheet.getRange(column+startIndex+':'+column+lastRow);
}
//make a call to the function to get all cells' values.
var checkboxesValues = getFullColumn('O', 1).getValues();
Now you have all the values, create a simple array and make for-loop on checkboxesValues length , where you check inside it if the value of the checkbox is TRUE or FALSE (checked - unchecked), and add it's index tp the newly created array.
example
var arr =[]
for (var i = 0; i < checkboxesValues.length; i++) {
if(checkboxesValues[i][0] === true){
arr.push(i +1); //adding row index (+1 because index start from zero in loop)
}
After that, you can make a new loop on the new array "arr" where it contains only true checkboxes values, and change values as you want.
So you go like:
for (var i = 0; i < arr.length; i++) {
//Step A (Pay count)
var payCount = SpreadsheetApp.getActiveSheet().getRange('N'+arr[i]).getValue()
payCount ++
SpreadsheetApp.getActiveSheet().getRange('N'+arr[i]).setValue(payCount);
//step B should go here
//Finally Step C
SpreadsheetApp.getActiveSheet().getRange('O'+arr[i]).setValue(false); // this will uncheck it
}
I think checkboxes can't be dimmed or disabled, they are only allowed to be checked or unchecked.
Step B, I don't really get it.
I hope this helps you by any means, and that I understood your question correctly.
Issue:
Every time a checkbox is checked, you want to do the following:
Update the Pay Count (+1).
Update the Date Paid with current date.
Update Renewal Due on date based on the currently existing formula.
If the difference between the current date and the renewal date is more than 20 days, disable the corresponding checkbox.
Also, you want to re-enable the checkbox when the renewable date is less than 20 days from now.
Solution:
There is no option for disabling checkboxes, but you just can remove them with removeCheckboxes().
In order to track when a checkbox is checked, I'd suggest you to use an onEdit trigger. This should (1) check if a checkbox was checked and, if that's the case (2) update the dates and (3) remove the corresponding checkbox if there're more than 20 days remaining. Check the code sample below for an example of how this could be done.
In order to enable the checkboxes again when the renewal date approaches (or to insert them again, which you can do with insertCheckboxes()), I'd suggest you to create a time-driven trigger which will periodically check the dates, and create the corresponding checkboxes.
I think, in this case, checking this once a day could be an appropriate periodicity. So you could use everyDays(n). This trigger can either be installed manually, or programmatically via executing the createDailyTrigger function below. Once the trigger is installed, the function enableCheckboxes (check code sample below) would run daily and check if the renewable date is less than 20 days from now (and insert the checkbox if that's the case).
Code sample:
function onEdit(e) {
const range = e.range;
const column = range.getColumn();
const row = range.getRow();
const value = e.value;
if (column === 15 && row > 1 && value == "TRUE") {
const sheet = e.source.getActiveSheet();
const countCell = sheet.getRange(row, 14);
countCell.setValue(countCell.getValue() + 1);
const now = new Date();
sheet.getRange(row, 12).setValue(now);
SpreadsheetApp.flush(); // Force update: pay date and renewable date
const renewalDate = sheet.getRange(row, 13).getValue();
// Remove checkbox if renewal date is more than 20 days from now
if (!isPaymentTime(renewalDate)) {
sheet.getRange(row, 15).removeCheckboxes();
}
}
}
function isPaymentTime(date) { // Check if renewal date is less than 20 days from now
const twentyDays = 1000 * 3600 * 24 * 20; // 20 days in milliseconds
const now = new Date();
if (date instanceof Date) return date.getTime() - now.getTime() < twentyDays;
else return false;
}
function enableCheckboxes() {
const sheet = SpreadsheetApp.getActive().getSheetByName("PODexpenses");
const firstRow = 2;
const renewalColumn = 13;
const numRows = sheet.getLastRow() - firstRow + 1;
const renewalDates = sheet.getRange(firstRow, renewalColumn, numRows).getValues().flat();
renewalDates.forEach((renewalDate, i) => {
if (isPaymentTime(renewalDate)) { // Check if less than 20 days
sheet.getRange(i + firstRow, 15).insertCheckboxes(); // Insert checkbox
}
})
}
function createDailyTrigger() {
ScriptApp.newTrigger("enableCheckboxes")
.timeBased()
.everyDays(1)
.create();
}
Note:
I saw there is another onEdit function in your current script. Please integrate this on the same function. There can only be one onEdit.

Sheets function to add content to another sheet

I've been making slow but steady progress on this app that creates the daily bulletin for the school where I teach.
Data is submitted by staff via a form, and is then naturally in a sheet. I already created a script to purge old data from the sheet, thanks in part to help I've gotten here. An additional script orders content on the data sheet by bulletin category, creates a copy of a template sheet, names it by the desired date, puts the date at the top. That's about as far as I've gotten. It also adds the first category heading by default, which is mostly a test.
What I'm attempting to do now is loop through each row of the data sheet to determine if any of the three date columns contains the desired date (entered via a dialog box earlier in the script). If any of them match today's date, we then will check to see if the current category and the category in the row are the same. If they are not, we change the current category and add a new heading to the bulletin sheet. If they are the same, we get the announcement itself and add that to the bulletin sheet. I suspect I'll use embedded functions for these two purposes.
Right now I'm stuck on the loop portion. Again, this should cycle through each row of the data sheet. There are three columns containing the dates (C, D, E). If I can get it to recognize date matches from one of the cells in this range, I can move forward with the rest.
function writeBulletin() {
//get the bulletin date
var bullSheet = todayDay;
//make the bulletin sheet active
var ss = SpreadsheetApp.getActiveSpreadsheet();
ss.setActiveSheet(ss.getSheetByName(todayDate));
//set var for needed sheets
var responses = ss.getSheetByName("Form Responses 1")
var bulletin = ss.getSheetByName(todayDate)
//get the date from the sheet title and apply it to the date range
var dateCell = bulletin.getRange(3,1);
var sheetDate = bulletin.getName();
dateCell.setValue(sheetDate);
//works
//Now we start building the bulletin
//currentDataRow is a reference to the Responses sheet. Used in later for loop
var currentDataRow = 2;
var currentBulletinRow = 11;
var catCurrent = "01 Administration";
var catCurrentSS=catCurrent.substring(3,30);
var lastRow = responses.getLastRow(); //get last row of data sheet
var lastBull = bulletin.getLastRow(); //get last row of bulletin sheet
var nextBullRow = lastBull+2;
var testOutput = bulletin.getRange(6,3);
var nextBullItem = bulletin.getRange(nextBullRow,1);
nextBullItem.setValue(catCurrentSS);
//testOutput.setValue("dude"); //this works
if(responses.getRange(2,3).getValue()==todayDate) {
testOutput.setValue("dude");
}
//bulletin.getRange(2,3).setValue("dude"); //test row
for(var i = 2; i<=lastRow; i++) {
if(5>3) {
//if(responses.getRange(i,3).getValue()==sheetDate||responses.getRange(i,4).getValue()==sheetDate||responses.getRange(i,5).getValue()==sheetDate){
//bulletin.getRange(nextBullRow,3).setValue("dude");//works
bulletin.getRange(nextBullRow,1).setValue(responses.getRange(i,7).getValue());
nextBullRow+=2;
}
}
}
I did notice that my loop condition statement had a reversed inequality sign; however, fixing this did not seem to help.
jdv: Good point. fixed it now
Aside from the issue of repeatedly interacting with the Spreadsheet interface (the alternative being to read values from the Spreadsheet once, then work with the resulting javascript Array object), the issue is that you are comparing a Range object with a String:
var sheetDate = bulletin.getName();
...
if(responses.getRange(i, 3) == sheetDate || ..... ) {
This will not work :) You need to access the value of the Range:
if(responses.getRange(i, 3).getValue() == sheetDate || ... ) {
edit: as mentioned in comments, the values in these responses cells are interpreted as Date objects. Date comparisons are fun, because you get to play with time zones and/or format strings. I recommend avoiding needing to use dates in this manner, especially when starting out with scripts.
One possible fix for this new issue is to use the value from dateCell.getValue() after calling SpreadsheetApp.flush() (to ensure the writing of sheetDate is performed first). This will let the spreadsheet do the nasty work making the correct date:
dateCell.setValue(sheetDate);
SpreadsheetApp.flush();
// Construct the proper Date object from the sheetDate value
var compareDate = dateCell.getValue();
...
for(var i = 2; i <= lastRow; ++i) {
// Read into an array [[ 0: elem#(i,3), 1: elem#(i,4), 2: elem#(i,5), 3: elem#(i,6), 4: elem#(i,7) ]]
var row = responses.getRange(i, 3, 1, 5).getValues();
if(row[0][0] == compareDate || row[0][1] == compareDate || row[0][2] == compareDate) {
...