Circular dependency error in Google Sheets - google-apps-script

I have two formulas:
CELL W21:
=IF(AND(TODAY() >= DATE(2022,2,1), TODAY() <= DATE(2022,2,28)), SUM('Master Report'!$B$20:$B$24,'Master report'!$B$31:$B$32), W21)
CELL X21:
=IF(AND(TODAY() >= DATE(2022,3,1), TODAY() <= DATE(2022,3,31)), SUM('Master report'!$B$20:$B$24,'Master report'!$B$31:$B$32), X21)
The first formula works ok, while the second gives a circular dependency error. I want the value in X21 to be populated only between those dates but I want to set up the spreadsheet now for the entire year.
The values in "Master Report" gets updated automatically via API on 1st of every month and I am referencing those value in a table where each month is present. Example as follows:
Master Report (updated automatically every 1st of the month)
| Page | Visits |
| -------- | -------------- |
| Page 1| 20012|
This spreadsheet
| January 2022| March 2022| April 2022|
|:---- |:------:| -----:|
| Visits| Visits| Visits|

I managed to do this in Google App Script as suggested by #TheWizEd.
It's a very primitive script, but I hope it can help people with this problem in future:
function checkDateAndWrite(dateStart,dateEnd,cellFrom,cellTo) {
var activeSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var reportSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Master Report')
var today = new Date();
var dateOne = new Date(dateStart);
var dateTwo = new Date(dateEnd);
if(today.valueOf() >= dateOne.valueOf() && today.valueOf() <= dateTwo.valueOf()) {
activeSheet.getRange(cellTo).setValue(reportSheet.getRange(cellFrom).getValue());
}
return;
}

Related

Choose Multiple Columns in Google Sheets Script

I have a dataset of jobs for a gardening company with 50 or so columns.
status # ID Date Name Frequency Height Weight DOB...
☐ 4 340 09/06/20 Jack Once Off 175 100 11/1/60...
☐ 1 543 22/05/20 Sarah Weekly 170 201 12/2/87...
☐ 3 121 01/05/20 Emily Fortnightly 172 150 6/6/90...
☐ 3 577 11/06/20 Peter Once Off 165 165 31/1/89...
I have a custom script that recreates a new row when the checkbox is selected for weekly and fortnightly jobs and increases the # field by 1 and adds the appropriate date in the future for the date field. I also want it to copy all other data in the original row into the new row.
Currently it will copy up to column 9 but I have 50 columns that need the original data copied over. Is it possible to use more efficient code rather than using the below 50 times?
sheet.getRange(selectedRow,5).getValue(),
sheet.getRange(selectedRow,6).getValue(),
sheet.getRange(selectedRow,7).getValue(),
sheet.getRange(selectedRow,8).getValue(),
sheet.getRange(selectedRow,9).getValue(),
Here is the script:
function onEdit() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getActiveSheet();
var checkbox = sheet.getActiveRange().getValue();
var selectedRow = sheet.getActiveRange().getRow();
var selectedFreq = sheet.getRange(selectedRow, 6).getValue();
//Run if selected cell has a checkbox and ticked
while(checkbox == true){
addValues(selectedRow, sheet, selectedFreq);
break;
}
}
//Function to process values based on frequency
function addValues(selectedRow, sheet, selectedFreq){
var number = sheet.getRange(selectedRow,2).getValue();
var date = new Date(sheet.getRange(selectedRow,4).getValue());
if(selectedFreq == "Fortnightly"){
//Insert a new row after the ticked checkbox, setup a new date with 14 days (or 2 weeks), increments the # with 1 & the rest of the data are copied
var newDate = new Date(date.setDate(date.getDate()+14));
sheet.appendRow(["",number+1,sheet.getRange(selectedRow,3).getValue(),newDate,
sheet.getRange(selectedRow,5).getValue(),
sheet.getRange(selectedRow,6).getValue(),
sheet.getRange(selectedRow,7).getValue(),
sheet.getRange(selectedRow,8).getValue(),
sheet.getRange(selectedRow,9).getValue()]);
sheet.getRange(sheet.getLastRow(),1).insertCheckboxes();
sheet.getRange(sheet.getLastRow(),4).setValue(sheet.getRange(sheet.getLastRow(),4).getValue()).setNumberFormat("dd/MM/yy");
}
if(selectedFreq == "Weekly"){
//Insert a new row after the ticked checkbox, setup a new date with 7 days (1 week), increments the # with 1 & the rest of the data are copied
var newDate = new Date(date.setDate(date.getDate()+7));
sheet.appendRow(["",number+1,sheet.getRange(selectedRow,3).getValue(),newDate,
sheet.getRange(selectedRow,5).getValue(),
sheet.getRange(selectedRow,6).getValue(),
sheet.getRange(selectedRow,7).getValue(),
sheet.getRange(selectedRow,8).getValue(),
sheet.getRange(selectedRow,9).getValue()]);
sheet.getRange(sheet.getLastRow(),1).insertCheckboxes();
sheet.getRange(sheet.getLastRow(),4).setValue(sheet.getRange(sheet.getLastRow(),4).getValue()).setNumberFormat("dd/MM/yy");
}
}
I suggest that you use Sheet.getRange(row, column, numRows, numColumns) and Range.getValues() to get the row values with multiple columns.
Sample:
var values = sheet.getRange(selectedRow,1,1,50).getValues();
values[0][1] = number+1;
sheet.appendRow(values.flat());
With this, you can get the values of your selected row, from column 1 to column 50.
Then you can just update specific index in your array values depending on your preference. Like values[0][1] = number+1
Lastly, you can use your array values in Sheet.appendRow(). You can use array.flat() to convert 2-d array to 1-d array
(UPDATE)
Your code must be something like this:
//Function to process values based on frequency
function addValues(selectedRow, sheet, selectedFreq){
var rowValues = sheet.getRange(selectedRow,1,1,50).getValues().flat();
var date = new Date(sheet.getRange(selectedRow,4).getValue());
if(selectedFreq == "Fortnightly"){
//Insert a new row after the ticked checkbox, setup a new date with 14 days (or 2 weeks), increments the # with 1 & the rest of the data are copied
var newDate = new Date(date.setDate(date.getDate()+14));
//Update column A,B and D
rowValues[0] = "";
rowValues[1] += 1;
rowValues[3] = newDate;
sheet.appendRow(rowValues);
sheet.getRange(sheet.getLastRow(),1).insertCheckboxes();
sheet.getRange(sheet.getLastRow(),4).setValue(sheet.getRange(sheet.getLastRow(),4).getValue()).setNumberFormat("dd/MM/yy");
}
}
I get the selected row values from column 1 to 50. Then use array.flat() to change the result from 2-d array to 1-d array
I changed column A, B and D based on your original code
appended the updated data in the sheet

Using an onEdit Trigger on one spreadsheet to set values on another spreadsheet

I've been working on a time in/time out google sheet with a script. My original question was how to share a sheet to a user(let's call them client) where the Client can edit the sheet that had an onEdit trigger that would then set values into another sheet that the Client did not have permission to use.
I first tried just sharing a spreadsheet and making one of the sheets in that spreadsheet locked. But this prevented the trigger from running correctly when used by the client. So I asked about it and someone suggested using an installable edit trigger on the client sheet. And have the information be appended to a new spreadsheet. However, I don't think this is what I wanted because the way the code works needs the clients to not be able to edit the information that will be appended, not because I don't want false information but because if they get rid of certain columns, the script doesn't know what to do.
But it did give me an idea, instead of having one spreadsheet with two sheets, just have 2 spreadsheets with 1 sheet each. Much less likely for the people using the client account to find the shared spreadsheet vs finding the sheet in the open spreadsheet they are using.
So now to show you some code. Sorry I feel I have to show all of it due to any small thing that could be wrong I am over looking.
Host Spreadsheet Id is the Id of the spreadsheet the information is being logged into. This is the information I don't want being edited by the client.
ACTIVE refers to the name of the sheet the client is clicking on and editing. PASSIVE refers to the name of the sheet that is collecting information. Originally these two sheets were on the same spreadsheet.
function setValue(cellName, value) {
SpreadsheetApp.getActiveSpreadsheet().getRange(cellName).setValue(value);
}
function getCurrentRow() {
var currentRow = SpreadsheetApp.getActiveSheet().getActiveSelection().getRowIndex();
return currentRow;
}
function getValue(cellName) {
return SpreadsheetApp.getActiveSpreadsheet().getRange(cellName).getValue()
}
function getNextRow() {
var sas = SpreadsheetApp.openById("Host Spreadsheet Id");
var ss = SpreadsheetApp.setActiveSpreadsheet(sas)
return SpreadsheetApp.getActiveSpreadsheet().getSheetByName('PASSIVE').getLastRow() +1;
}
function getLasttRow() {
var sas = SpreadsheetApp.openById("Host Spreadsheet Id");
var ss = SpreadsheetApp.setActiveSpreadsheet(sas)
return SpreadsheetApp.getActiveSpreadsheet().getSheetByName('PASSIVE').getLastRow();
}
function addRecord(a, b, c, d) {
var sas = SpreadsheetApp.openById("Host Spreadsheet Id");
SpreadsheetApp.setActiveSpreadsheet(sas)
var row = getNextRow();
setValue('PASSIVE!A' + row, a);
setValue('PASSIVE!B' + row, b);
setValue('PASSIVE!C' + row, c);
setValue('PASSIVE!D' + row, d);
}
/// this function is to find the row number(s) that match the criteria in
/// the timeIn() function so that the time in date can be placed
/// directly to the right of the individual that signed out.
function findRows(c1,n1,c2,n2,c3,n3,name) {
var sas = SpreadsheetApp.openById("Host Spreadsheet Id")
SpreadsheetApp.setActiveSpreadsheet(sas)
var ss=SpreadsheetApp.getActiveSpreadsheet()
var sh=ss.getSheetByName(name);
var rg=sh.getDataRange();
var vA=rg.getValues();
var rA=[];
for(var i=0;i<vA.length;i++) {
if(vA[i][c1-1]==n1 && vA[i][c2-1]==n2 && vA[i][c3-1]==n3) {
rA.push(i+1);
}
}
return rA
}
function timeIn() {
var row = getCurrentRow()
var LocationA = getValue('ACTIVE!A' + row)
var LocationB = getValue('ACTIVE!B' + row)
var passiveRow = findRows(1,LocationA,2,LocationB,5,"",'PASSIVE');
Logger.log(passiveRow);
setValue('PASSIVE!E' + passiveRow, new Date().toLocaleString());
}
function places() {
var row = getCurrentRow()
addRecord(getValue('A' + row), getValue('B' + row), getValue('C' + row), new Date().toLocaleString());
}
function onEdit(e) {
var row = getCurrentRow()
var Location = getValue('ACTIVE!C' + row)
var LocationA = getValue('ACTIVE!A' + row)
var LocationB = getValue('ACTIVE!B' + row)
var passiveRow = findRows(1,LocationA,2,LocationB,5,"",'PASSIVE');
Logger.log(row)
Logger.log(Location)
Logger.log(LocationA)
Logger.log(LocationB)
Logger.log(passiveRow)
if(SpreadsheetApp.getActiveSheet().getName() !== "ACTIVE") return;
if(Location !== 'HOME' && Location !== "" && passiveRow == "") {
places();
Logger.log(passiveRow)
}
else if(Location !== 'HOME' && Location !== "" && passiveRow !== "") {
timeIn();
places();
Logger.log(passiveRow)
}
else if(Location === 'HOME' && passiveRow !== "") {
timeIn();
Logger.log(passiveRow)
}
}
So this was my attempt at turning what used to be 2 sheets in the same spreadsheet into 2 different spreadsheets. However, the onEdit(e) function doesn't work. If I manually run the timeIn() function, it works and the places() function works as well. Even goes and finds the last row of the spreadsheet it is importing information into. But alas, when they edit Column C, the column that is suppose to activate the onEdit(e) if else to work, it doesn't run the functions. What am I missing?
Let me give some visuals of what I'm trying to do.
Here is the sheet the clients should see. Column C has drop down boxes in each row full of locations.
First | Last | LOCATIONS
=================================
James | Carter | HOME
Kyle | Johnson | MALL
Micheal | Wilson | BANK
Sarah | Smith | HOME
Tray | Tin | CLINIC
John | Becks | HOME
Here would be the sheet collected by the Host Spreadsheet
First | Last | LOCATION | OUT | IN
=====================================================
Tray | Tin | CLINIC | 10:00 |
James | Carter | MALL | 12:30 | 1:30
Kyle | Johnson | MALL | 12:45 |
Micheal | Wilson | BANK | 01:00 |
James | Carter | POOL | 01:30 | 2:00
I hope this is a good enough visual to get the point across as to what I'm trying to accomplish. The code did work on the same spreadsheet, but now I'm trying to make it work on two in order to prevent the Client from messing with the Collected information. Because if something like this happens :
First | Last | LOCATION | OUT | IN
=====================================================
Tray | Tin | CLINIC | 10:00 |
James | Carter | MALL | 12:30 | 1:30
Kyle | Johnson | MALL | 12:45 |
Micheal | Wilson | BANK | 01:00 |
James | Carter | POOL | 01:30 | 2:00
Sarah | Smith | POOL | 01:30 |
Sarah | Smith | POOL | 01:31 |
You see Sarah Smith is on twice with no Sign in. This causes a bug because of how I get passive rows, it now sees 2 rows where "IN" is empty, so the function findRows() it gives me 2 values where I can only have 1 value in the function setValue() inside the timeIn() function.

Google Forms response in new sheet with notification

I've created a order capture form that captures the
1. location (dropdown),
2. delivery date (date)
3. Products (small text)
I have then added a formula to generate a new order no. everytime a new response is added.
=arrayformula( if( len(A2:A), "Order " & text(row(A2:A) - row(A2) + 1, "022"), iferror(1/0) ) )
I need a new sheet to be created with the tab name with the new order no. and the columns transposed along with the response inputs against them.
For example response form
Column B | Column C | Column D |Column E |Column F
Order no. | Location | Delivery Date | Product 1 | Product 2
Order 122 | XYZ | 2/16/2017 | 35kg | 45kg
New sheet generated named "Order 122"
Order no. | Order 122
Location | XYZ
Delivery Date | 2/16/2017
Product 1 | 35kg
Product 2 | 45kg
and a notification to be sent to an email.
Is this possible with google spreadsheets, if so, please do let me know how it can be done. Thank you in advance.
I am using the following to create a new sheet named after the order no. but it doesnt seem to be working based on this article.
function subTheForm(){
Logger.log('submit ran');
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var lastRow = sheet.getLastRow();
var colB_Data = sheet.getRange(lastRow, 2).getValue();
ss.insertSheet(colB_Data);
};
I get this error when I run the script
The sheet name cannot be empty. (line 9, file "Code")
Solved.
I used the following code similar as what I'd posted. For some reason I kept getting an error, cleared the responses, deleted and recreated the script and triggers and it worked.
function onSubmit(e){
Logger.log('submit ran');
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
//Get last row of data
var lastRow = sheet.getLastRow();
var colB_Data = sheet.getRange(lastRow, 2).getValue();
ss.insertSheet(colB_Data);
};

Finding next empty row after a particular "Header" row

I have just started trying out the script editor of the Google Sheets.
Here is what I am trying to do:
I have a few "headers" in Sheet A. I will name them Header A, B, and C.
1 | Header A
2 | text 1
3 | text 2
4 | (empty row)
5 | Header B
6 | text 1
7 | text 2
8 | (empty row)
9 | Header C
10 | text 1
11 | text 2
Sheet B
1 | text 1 | date | Header A
2 | text 2 | date | Header B
3 | text 3 | date | Header B
4 | text 4 | date | Header C
5 | text 5 | date | Header A
When I update my data on Sheet B, it will be automatically be updated on Sheet A based on my custom attributes in Sheet B. I need it to be able to update the data on Sheet B onto Sheet A's empty row after the specific Headers.
Currently I am stuck at getting the next empty row in Sheet A. I am able to get the row that my Headers are in but I couldn't find any help online finding the next empty row after each specific Headers.
I could not find any classes that is provided. All I can see is getLastRow which is not what I want. I wonder can this be done?
Below is my current code:
function getScheduleStatus(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var name = ss.getSheetByName("Production Schedule");
var rng = name.getDataRange();
var last_row = rng.getLastRow();
var data = rng.getValues();
var str_upcoming = "upcoming"
var rows = [];
for (var i=0; i < data.length; i++) {
if (data[i][0].toLowerCase() == str_upcoming) {
Logger.log("true");
rows.push(i);
}
}
var row = Number(rows)+Number(rng.getRow());
Logger.log(row);
}
Currently I am able to find the Header as well as getting the row number.
Hope you guys can understand.
Take a look at Faster way to find the first empty row; the answers there can be adapted to your situation.
The fastest solution there was Don Kirby's, and since you have a single column of data, it is ideal. We just need to change it to start searching from a particular headerRow!
/**
* Search column A starting at the given row, to find
* the first following empty row. Uses spreadsheet row numbers.
* From: https://stackoverflow.com/a/38194740/1677912
*
* #param {Number} headerRow Row number to start search at.
*
* #returns {Number} Target empty row.
*/
function getFirstEmptyRowAfter(headerRow) {
var spr = SpreadsheetApp.getActiveSpreadsheet();
var column = spr.getRange('A:A');
var values = column.getValues(); // get all data in one call
var ct = headerRow; // Start at row after headerRow (0-adjusted)
while ( values[ct][0] != "" ) {
ct++;
}
return (ct + 1); // (1-adjusted)
}

Search Google Spreadsheet for same value and append in next column in GAS

I've got a Google Spreadsheet which looks like this:
ID | winkelName | 26/05/2015 |
1 | Foo | |
2 | Bar | |
3 | Foo2 | |
4 | Bar2 | |
I'm trying to read and write an array of objects with the following structure:
history[i].storeName
history[i].checkIns
What I would like to do is to write all the number of checkins (with the same winkelName) on the same row under the column date (26/05/2015). But I'm still stuck what is the best way to achieve this?
This is my first attempt:
function writeHistory(history) {
var sheet = historySheet.getSheets()[0];
historySheet.setActiveSheet(sheet);
var nextRow = sheet.getLastRow(); // get next row
var rows = sheet.getDataRange().getValues();
var lastColumn = sheet.getLastColumn();
sheet.insertColumnAfter(lastColumn);
lastColumn++;
sheet.getRange(1, lastColumn).setValue(new Date());
var column = sheet.getLastColumn();
for(i in rows) {
if(i != 0) {
if(rows[i][1] == history[i].winkelName) {
sheet.getRange(i, column).setValue(history[i].checkIns); // this is not fully working
}
}
}
}
In a Google spreadsheet you can write a range of cells at once. To do so, create a multi dimensional array with one column and for each row another dimension and within each row a dimension for each cell you want to write.
Loop through your checkings, set the cell values in your array and write the values using sheet.getRange(1,column-index).setValue([your array]);
Also check: Google's best practices on dealing with range values