Google Sheets - Identify duplicates and overwriting data - google-apps-script

I've heavily edited the original question I posted, as i have solved some of the issue myself. I'm now stuck on just one thing.
function payINVOICE() {
var ss = SpreadsheetApp.getActive();
var ds = SpreadsheetApp.openById("14imcEob2qIZbH6AjGYtf16MJxbnfkhQn1ae4jR-Nzq4");
var srcSheet = ss.getSheetByName("INVOICE_ENTRY");
var dstSheet = ds.getSheetByName("INVOICE_ENTRY");
var data_range = srcSheet.getRange('B4:J100');
var data_data = data_range.getValues();
var data_clean = data_data.filter(function (r) {return r[1]});
var clear_range = srcSheet.getRange('B4:I100');
var lr = dstSheet.getLastRow();
dstSheet.getRange(lr+1, 2,data_clean.length,9).setValues(data_clean);
clear_range.clear();
}
This code checks the range B4:J100 for a value in Column B.
If there is a value and the script is run, it copies those rows onto dstSheet.
My role is marking invoices as paid or not.
The dstSheet will already contain the data, which is pulled back into the srcSheet with a query. Column K is not part of the original query.
If I mark a row as "PAID" in column K on the srcSheet, I want the code to take the data_data variable and overwrite what is already in the dstSheet, so that the query then pulls the data back into srcSheet with column J then showing "PAID".
It means I can then change column K to "NOT PAID", run the script again and it will over-write the "PAID".
This makes better sense than my last post and I am so close to achieving what I need, just stuck on this last bit.

If you simply want to monitor the changes between the two mentioned sheets, it would be much easier to use an onEdit(e) trigger which will tell you which cell has been edited.
Snippet
function payINVOICE(e) {
var srcSheet = SpreadsheetApp.getActiveSheet(); //gets the active sheet which is supposed to be source sheet
var dstSheet = SpreadsheetApp.openById('DEST_SHEET_ID').getSheetByName('INVOICE_ENTRY'); //gets the destination sheet
if (e.range.getSheet().getName() == 'INVOICE_ENTRY' && e.range.getColumn() == 11) { //e specifies where the edit has been made - therefore this if condition checks if the edit is in the INVOICE ENTRY sheet and if the column is the K column
var row =e.range.getRow(); //this gathers the row at which the edit has been made
var data = srcSheet.getRange(row, 2, 1, 10).getValues(); //this gathers the data corresponding to the row at which the edit has been made
dstSheet.getRange(row, 2, 1, 10).setValues(data); //this sets the data into the corresponding row in the destination sheet
}
Explanation
The above code uses the onEdit(e) installable trigger and the e event object. In this way, when an edit is being made on the srcSheet on the 11th column (aka K column) and the sheet name is "INVOICE_ENTRY", then the row at which the change has been made is kept in the row variable. Afterwards, the corresponding row of data is kept in the data variable; the getRange(row, 2, 1, 10) references the range for the row at which the change on the K column has been made. In order to update the dstSheet, the data value is set to the according range using setValues(data).
Installing the trigger
To make the payINVOICE(e) function trigger on an edit action, you need to install an onEdit trigger.
This is being done by accessing the project's triggers by clicking this icon:
After that, you just need to create a new trigger by clicking the Add trigger button and create a trigger with the following settings:
Trying the function
In order to try the behavior for this, you just need to make an edit on the srcSheet on the K column and this change will be reflected in the destSheet.
Note
The ranges that have been used in this script are chosen considering the fact that:
K column consists of the PAID/NOT PAID text;
The srcSheet and the dstSheet have the data wanted in the same ranges.
You might need to customize these according to your sheet and add the needed formulas/filters you have mentioned.
Reference
Apps Script Installable Triggers;
Apps Script Event Objects.

Related

Re-Insert deleted rows Google Sheet

I would like to prevent the deletion of rows on a google sheet, but this is not possible to set it through permissions especially if it is the same user who can modify the sheet, can accidentally delete one or more rows and/or insert new ones. For inserting of a new row I have a script that works correctly which simply get the "INSERT_ROW" event onChange (), alerts the user and deletes the newly inserted row. For deletion instead,the user can delete a single row or even more rows at the same time ... and I don't know how to restore them in the same range from which they were deleted (and therefore also recover the values).
Here is the first working point:
/** RESTORE ROW OR COLUMN */
function onChange(e){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var range = SpreadsheetApp.getActiveRange();
if (e.changeType === 'INSERT_ROW'){
var row = range.getRow();
var lastRow = range.getLastRow();
var numRows = 1+lastRow-row;
sheet.deleteRows(row, numRows)
SpreadsheetApp.getUi().alert("Warning for Row");
}else if (e.changeType === 'INSERT_COLUMN'){
var col = range.getColumn();
var lastCol = range.getLastColumn();
var numCols = 1+lastCol-col;
sheet.deleteColumns(col, numCols);
SpreadsheetApp.getUi().alert("Warning for Column");
}
GmailApp.sendEmail(email#company.com,
'Warning:'+e.changeType+" row:"+ row,
"",
{
name :'WARNING'
});
}
There is no built-in / direct way to prevent that editors delete rows in Google Sheets while allowing them to user other features like filtering, sorting etc.
Under certain scenarios it might work to protect the sheet and setting some protections exceptions to allow some users to edit some cells.
If the above doesn't work for you, you might have to implement a way to backup the rows but that might not scalable and very reliable.
The most reliable approach might be to have a "backend database" (it could be another spreadsheet or another service i.e. BigQuery) then import the data into the "frontend" spreadsheet and push the changes to the backend database after they were validated.
Yes I have a backend database. In the sense that I have a goolge form that fills in the answer sheet. Then I have 4 more sheets which simply read the sheet values ​​from the answers(but on which, other users can take actions). So for example in sheet2 I have something like (= 'Form Answers'! A: A) and next to it, I have a cell with a checkbox like "OPEN" / "CLOSED" . The point is that if the user in Sheet2 inserts/delete a row, the answers are misaligned from the checkboxes...so the values ​​of the checkboxes no longer correspond to the information taken from the sheet "'Form Answers'!".I wanted to avoid having him insert/delete a row by mistake. So with the above procedure, I can do an undo of a new row ... it would have been nice to be able to restore any deleted row.
it would take something like "If you delete row3 of sheet2 then reinsert row3 of sheet 'Form Answer', exactly in the position of row 3 and not in append "(it would be the top to recover the "old value" of the checkbox that was lost due to deletion of the entire row

Paste formula as value on a certain date

I've been digging around on line for a couple of hours now to no avail. I'm trying to write a formula(which I assume is only possible through AppScript), that based on a certain date, will re-paste contents in a cell as values only.
The issue I've had with other scripts is that in my situation I have several different values that need to be checked. I've set up a continuously updating calendar that pulls from an array with listed dates for each item. However, in that array, the row gets moved into an archive once it hits todays date. Thus, they get deleted from the calendar too.
If there is another solution to this, like pasting based off conditional formatting(this would then keep the formulas that haven't found a value yet, and paste ones that have been found as values only), that would be great too.
Any help or advice would be greatly appreciated. If you need any other info, please ask.
EDIT:
The spreadsheet link is in comments. Attached are a before and after picture to help visualize whats going on here. Since the calendar is pulling from the active sheet, and rows on the active sheet go to "archive" when J2-Today() is -1(yesterday), those then disappear from the calendar. I've tried to make a mixed sheet with both archive and active, but I get duplicates as a frequent issue.
See code below:
function archiveRows() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var mainSheet = ss.getSheetByName("Sheet1");
var archiveSheet = ss.getSheetByName("Sheet2");
// last row should be on the data itself, same with column
var lastRow = mainSheet.getLastRow();
var lastCol = mainSheet.getLastColumn();
// set time to 0 so we can only compare dates
var dateToday = new Date().setHours(0, 0, 0, 0);
var range = mainSheet.getRange(1, 1, lastRow, lastCol);
var values = range.getValues();
// remove header to only process data
var header = values.shift();
var indices = [], difference = [];
// remove dates that are earlier than today
// collect rows and indices data when condition is not met
values.forEach(function (row, index) {
if (row[9] >= dateToday)
return true;
difference.push(row);
indices.push(index);
});
// remove the row with earlier date
// offset with 2 due to 0-indexing and header
indices.forEach(index => mainSheet.deleteRow(index + 2));
// all rows that were deleted are copied to archive sheet
difference.forEach(row => archiveSheet.appendRow(row));
}
Sample Data:
Output:
Note:
If you need to have this run on a daily basis, Time Driven trigger is your friend. Just have it set on a daily basis triggering archiveRows
Make sure that the main sheet only contains the data. (I see your sheet has rows containing 9AM values below. Make sure to clear those as the script will mistakenly include it on the processing)
Adjust date condition if needed.

Google Sheets (Google Finance) - Get stock price once and track

I would like to, upon data being entered into a new row (from external sheet - date, time, stock ticker), use google finance to get the current price of the stock into a cell and then no longer update the price in that cell. In the cell next to it, I want to track the price and in the cell next to that, I want to track the high, since creation (the highest value of the 'current price' cell).
Here is my sheet https://docs.google.com/spreadsheets/d/1EUU1bZnIfBatNI8H9pPk202PCD1g8wWXt5M0wx6S-jM/edit?usp=sharing
All I've got so far is a high value tracker that runs on the first row only for the right cells. Embarrasingly enough I can't figure out how to apply it to the entire columns.
So in summary, date time and stock will be entered into column A B and C. When that happens, I want to get the current price of the stock in C, and have that number no longer update. In D I want the price to be tracked, like Google Finance normally functions, and in E, the highest value of the D, in the same row.
That's the goal. Any help would be very much appreciated :)
All I've got so far is a high value tracker that runs on the first row
only for the right cells. Embarrasingly enough I can't figure out how
to apply it to the entire columns.
The code bound to your spreadsheet is
function onEdit() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange("D2:E2");
var values = range.getValues()[0];
range.setValues([[values[0], Math.max(values[0], values[1])]]);
}
What does this code do?
It sets the range to work with always to D2:E2 - no matter what the actual edited range is
It works only with the first row of the range (range.getValues()[0])
It compares the 0 and 1 columns of the range (e.g. columns D and E) and assigns the value of column D back to column D (is it necessary?) and the higher of the two values to column E
How to modify your code?
It is not quite clear from your description how column D is populated and what you want to do with column F, but to give your general advice:
If you want your function to run on all rows:
Modify your range and expand it until the last row. Subsequently loop
through all rows:
function onEdit() {
var sheet = SpreadsheetApp.getActiveSheet();
var lastRow = sheet.getLastRow();
var range = sheet.getRange("D2:E"+lastRow);
for (var i = 0; i < lastRow-1; i++){
var values = range.getValues()[i];
range.setValues([[values[0], Math.max(values[0], values[1])]]);
}
}
If you want you code to run only on the currently edited row:
Use the event object e.range to find out which is the edited row and work on this row:
function onEdit(e) {
var sheet = SpreadsheetApp.getActiveSheet();
var range = e.range;
var row = range.getRow();
var values = sheet.getRange(row, 4, 1, 2).getValues()[0];
range.setValues([[values[0], Math.max(values[0], values[1])]]);
}
Note: getRange(row, 4, 1, 2) is the notation to get the range starting with the defined row and column 4 (D), 1 row long and two columns wide, see here.
IMPORTANT: If your sheet is being populated automatically from an
external sheet - the onEdit trigger will not work for you (it only
fires on manual, human-made edits). In this case you will need a
workaround as described here.

Delete Row in Google Sheet depending on other Google Sheet cell valuie

Is it possible to write a google script that will delete a row in a Google Sheet, based on cell values for a given range, in another google sheet?
I've done some research on how to do this all in the same google sheet, but my limited experience with writing google sheet scripts has prevented me from understanding if what I described is possible, and how.
This is a script I have so far. This will delete rows in a range of my active spreadsheet if a cell in Column F contains the word "id:123456". What I'd like to be able to do, is to modify this code so that if the word "id:123456" is found, it will look in another column of another Google Sheet, and delete rows that contain "id:123456".
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName('Sheet1'); // change to your own
var values = s.getDataRange().getValues();
for (var row in values)
if (values[row][6] == 'id:123456')
s.deleteRow(parseInt(row)+1);
};
I haven't tested this code, so you might need to do some modifications to run successfully. But the basic idea is simple: have two sheets open at once, and based on the if from the first sheet make changes in the second sheet.
var ss1 = SpreadsheetApp.getActiveSpreadsheet();
var ss2 = SpreadsheetApp.getByID(0); //Change to the other sheet's ID, or somehow open it.
var s1 = ss1.getSheetByName('Sheet1'); // change to your own
var s2 = ss2.getSheetByName('Sheet1'); // change to your own
var values = s1.getDataRange().getValues();
for (var row in values)
if (values[row][6] == 'id:123456')
var other_values = s2.getDataRange().getValues();
for (var other_row in other_values)
if (other_values[row][6] == 'id:123456')
s2.deleteRow(parseInt(other_row)+1);
Main thing to be worried about and test: I copied your delete code, but since we're deleting rows, unless we start from the bottom we might start missing. (Suppose rows 4 and 8 out of 10 contain something to be deleted; you might end up deleting 4, which turns 8 to 7, but then we still delete row 8--meaning that we've missed a row we should have deleted and deleted a row we shouldn't have.)

Google Script to copy specific columns from one sheet to another sheets last row onEdit

I am looking for some help on google app scripting. Currently I have a spreadsheet that has 3 sheets. First sheet is data that comes from a google form. Second sheet is an "open" tab and third is a "closed" tab.
When data comes into the spreadsheet from the form it has only the columns that the form has. The "open" sheet has a combination of data from the form (sheet 1) and some additional cells to add more information to by me and not the person submitting the form. Because of this column miss-match I cannot just copy a whole row from sheet 1 to sheet 2. This also needs to be done onEdit trigger. So when an edit trigger fires, I want to store that rows data into memory and then copy column B to the last row of tab 2, column C. Column C > E, column D > B. etc..
How would I store the whole row into memory (an array I presume), and then copy only specific cells, in a specific order into the last row of a different sheet?
Hope that makes sense :-/
As you said, you'll have to use arrays to get data and write it back to another sheet in your selected order.
This is pretty basic manipulation and there are many ways to achieve it.
One way that is very easy to understand is as follow :
get row data in an array
pick up each value one by one and store in another array
write back to the last row +1 on another sheet.
Note that arrays are indexed starting from 0 , rows and columns start from 1 and A so you'll have to do some math !
function copyRow(){
var ss = SpreadsheetApp.getActive();
var sheet = ss.getActiveSheet();
var rowIdx = sheet.getActiveRange().getRowIndex();
var rowValues = sheet.getRange(rowIdx,1,1,sheet.getLastRow()).getValues();
Logger.log(rowValues);
var destValues = [];
destValues.push(rowValues[0][0]);// copy data from col A to col A
destValues.push(rowValues[0][2]);// copy data from col C to col B
destValues.push(rowValues[0][1]);// copy data from col B to col C
destValues.push(rowValues[0][3]);// copy data from col D to col D
// continue as you want
var dest = ss.getSheets()[1];//go on chosen sheet (here is the 2cond one)
dest.getRange(dest.getLastRow()+1,1,1,destValues.length).setValues([destValues]);//update destination sheet with selected values in the right order, the brackets are there to build the 2D array needed to write to a range
}
If you want that function to run on edit then just call it onEdit() and you're done !
Source: https://stackoverflow.com/a/64297866/14427194
function onEdit(e) {
//e.source.toast("Entry");
//console.log(JSON.stringify(e));
const sh=e.range.getSheet();
if(sh.getName()=="Sheet1" && e.range.columnStart==20 && e.value=="TRUE") {
const tsh=e.source.getSheetByName('Sheet2');
const nr=tsh.getLastRow()+1;
sh.getRange(e.range.rowStart,1,1,12).moveTo(tsh.getRange(nr,1,1,12));
sh.getRange(e.range.rowStart,16,1,4).moveTo(tsh.getRange(nr,16,1,4));
sh.deleteRow(e.range.rowStart);
}
}