Moving Rows from one sheet to another based on selected values - google-apps-script

I have an Apps Script I am using to move rows from one tab on my sheet to another based on what I select in a specific column.
The code I am using seems to work however it is putting two copies of the line on the second sheet I am pretty new to app script so i may have made an error in the code. I litterally just need the row to move over and delete from the original page.
function onEdit(e){
var sourceSheet = e.range.getSheet();
var row = e.range.getRow();
if(sourceSheet.getSheetName() === 'Submissions'){
var rowRange = sourceSheet.getRange(row, 1, 1, sourceSheet.getLastColumn());
var rowValues = rowRange.getValues()[0];
if(rowValues[16] === "Approved"){
var targetSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Completed");
targetSheet.appendRow(rowValues);
sourceSheet.deleteRow(row);
}
}
}

Delete Selected Rows
function onEdit(e) {
var sh = e.range.getSheet();
if(sh.getName() == 'Submissions'){
var rowRange = sh.getRange(e.range.rowStart, 1, 1, sh.getLastColumn());
var rowValues = rowRange.getValues()[0];
if(rowValues[16] == "Approved"){
var tsh = e.source.getSheetByName("Completed");
tsh.appendRow(rowValues);
sh.deleteRow(e.range.rowStart);
}
}
}
Perhaps you create and installable trigger with the name onEdit. In this case you are getting two triggers. One the installable version and one from the simple trigger. I also rewrote the function taking advantage of event object.

Related

Move row to new tab after sheet updates automatically

I need to move rows from a tab based on a cell including "lep". The problem is that it doesn't read the info already on the sheet when it's opened (since it is a sheet that automatically updates new sales from Shopify via Zapier).
What would be the solution for this? I tried onEdit & onOpen already and neither work.
function onOpen(e) {
// assumes source data in sheet named SHOPIFY
// target sheet of move to named ORG
// test column with yes is col 17 or Q
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = e.source.getActiveSheet();
var r = e.source.getActiveRange();
if(s.getName() == "SHOPIFY" && r.getColumn() == 17 && r.getValue().includes("lep")) {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("ORG");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 1, 1, numColumns).moveTo(target);
s.deleteRow(row);
}
}
function moveRows() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('SHOPIFY');
const tsh = ss.getSheetByName('ORG')
const vs = sh.getDataRange().getValues();//you may wish to change the range to not include headers
let d=0;
vs.forEach((r,i)=>{
if(r[16].toString().includes("lep")) {
tsh.appendRow(r);
sh.deleteRow(i+1-d++);
}
});
}
function onMyOpen() {
//needs installable trigger
moveRows();
}
An onEdit() simple trigger will only run when the user hand edits the spreadsheet.
An onOpen() simple trigger will only run when you open the spreadsheet in a web browser, and its event object does not include information about any recent changes in the spreadsheet.
To detect changes made by Zapier you will need an on change installable trigger. See the moveRowsFromSpreadsheetToSpreadsheet_ script for an example of how to do that.

How to add new rows when archiving a row in Google Sheets

I currently have working code inside of my Google Sheet. The code moves certain rows in the sheet over to another sheet when labeled as "Archive" in a drop down menu.
The problem I have is that when this happens, the entire row gets deleted. I only need the information from column C:O (C2:O) to be archived. Another problem that this creates, when it deletes the row the other rows move up, thus deleting the set amount of rows I have created for input.
I need it to automatically replace the archive rows with another row with all the same conditional formatting and functions so that it does not disrupt the rest of the sheet and there is no need to go in and manually create more rows.
Please help, thank you very much in advance.
Current code used in Google App Scripts is attached below.
function myFunction() {
// moves a row from a sheet to another when a magic value is entered in a column
// adjust the following variables to fit your needs
// see https://productforums.google.com/d/topic/docs/ehoCZjFPBao/discussion
var sheetNameToWatch = 'Campaigns';
var columnNumberToWatch = 6;
// column A = 1, B = 2, etc…
var valueToWatch = 'Archive';
var sheetNameToMoveTheRowTo = 'Archive';
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveCell();
if (sheet.getName() == sheetNameToWatch && range.getColumn() == columnNumberToWatch && range.getValue() == valueToWatch) {
var targetSheet = ss.getSheetByName(sheetNameToMoveTheRowTo);
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
sheet.getRange(range.getRow(), 1, 1, sheet.getLastColumn()).moveTo(targetRange);
sheet.deleteRow(range.getRow());
}
}
You just need to use copyTo() [1] function instead of moveTo() [2] function which is cutting and pasting the source row. Also, you need to remove the deleteRow function if you just want to leave the row cells with same format and functions set. You only would need to change this:
sheet.getRange(range.getRow(), 1, 1, sheet.getLastColumn()).moveTo(targetRange);
sheet.deleteRow(range.getRow());
To this:
sheet.getRange(range.getRow(), 1, 1, sheet.getLastColumn()).copyTo(targetRange);
Moreover, if you just want to copy the values and not the formulas from the cells to the target sheet, use the copyTo function with options [3], like this:
sheet.getRange(range.getRow(), 1, 1, sheet.getLastColumn()).copyTo(targetRange, SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
[1] https://developers.google.com/apps-script/reference/spreadsheet/range#copytodestination
[2] https://developers.google.com/apps-script/reference/spreadsheet/range#movetotarget
[3] https://developers.google.com/apps-script/reference/spreadsheet/range#copytodestination-copypastetype-transposed
If you want to clear the values without deleting the row, you'll want to change your approach in the following ways:
obtain a Range which includes the data you want to archive
to populate the archive sheet, use copyTo() instead of moveTo()
obtain a Range which includes only the cells you want to clear out
use range.clearContent() to clear that range
Here's a reimplementation of your function. I've extracted the configurable bits into global variables, which makes adjusting them down the line a bit easier to do.
var SOURCE_SHEET_NAME = 'Campaigns';
var TARGET_SHEET_NAME = 'Archive Campaigns';
// The cell value that will trigger the archiving action.
var ARCHIVE_VALUE = 'Archive';
// The column number where you expect the ARCHIVE_VALUE to appear.
var ARCHIVE_COLUMN_NUMBER = 4;
// The starting and ending columns for the range of cells to archive.
var ARCHIVE_START_COLUMN = 1;
var ARCHIVE_END_COLUMN = 15;
// The starting and ending columns for the range of cells to clear.
var CLEAR_START_COLUMN = 1;
var CLEAR_END_COLUMN = 10;
function archiveRow() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var activeCellRange = sheet.getActiveCell();
if (sheet.getName() != SHEET_NAME ||
activeCellRange.getColumn() != ARCHIVE_COLUMN_NUMBER ||
activeCellRange.getValue() != ARCHIVE_VALUE) {
return;
}
var activeCellRow = activeCellRange.getRow();
// Get the range for the data to archive.
var archiveNumColumns = ARCHIVE_END_COLUMN - ARCHIVE_START_COLUMN + 1;
var archiveRowRange = sheet.getRange(activeCellRow, ARCHIVE_START_COLUMN, 1, archiveNumColumns)
// Get the range for the archive destination.
var targetSheet = ss.getSheetByName(TARGET_SHEET_NAME);
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1, 1, archiveNumColumns);
// Copy the data to the target range.
archiveRowRange.copyTo(targetRange);
// Get the range for the data to clear.
var clearNumColumns = CLEAR_END_COLUMN - CLEAR_START_COLUMN + 1;
var clearRowRange = sheet.getRange(activeCellRow, CLEAR_START_COLUMN, 1, clearNumColumns);
// Clear the row.
clearRowRange.clearContent();
}

Auto Archive Schedule; Time-based Trigger

I have been asked to create a live Google Sheets Spreadsheet to track the work schedule at our yard. I have no experience with a script but found out I could program my sheet instead of hiding formulas and it would yield a cleaner result. I have been able to make the sheet organize itself and I was able to make it Archive manually (onEdit). What I'm looking for is to have it automatically run the code at 1 am so when we arrive at work it archives based on a cell value in a certain column.
This is an example of my onEdit script that works, but when someone is trying to check off the "YES" column there is some lag and can cause the wrong cell to be checked, which I then manually correct.
function onEdit() {
var sheetNameToWatch = "Schedule";
var columnNumberToWatch = 28;
var valueToWatch = "Yes";
var sheetNameToMoveTheRowTo = "Archive";
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveCell();
if (sheet.getName() == sheetNameToWatch && range.getColumn() == columnNumberToWatch && range.getValue() == valueToWatch) {
var targetSheet = ss.getSheetByName(sheetNameToMoveTheRowTo);
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
sheet.getRange(range.getRow(), 1, 1, sheet.getLastColumn()).moveTo(targetRange);
sheet.deleteRow(range.getRow());
}
}
So this code runs at 100% failure but saves and executes, and I honestly don't know why. Could be I misunderstand the values I need to insert after the "function" area. I did have this setup with an "Auto Archive" trigger that created a menu button with a "Run" option on the sheet, but when you click that it only does the last row with "Yes" in column 28 (every press of the button will move 1 row until all rows are moved) and the button won't work for the other users of the sheet.
function createTrigger() {
ScriptApp.newTrigger("Move Archive") //Move Archive is the name of the script
.timeBased()
.everyMinutes(1) // only set to 1 minute for testing, I can change this out for a daily timer
.create();
}
function myFunction() {
var sheetNameToWatch = "Schedule"; // "schedule" is the sheet we enter info on
var columnNumberToWatch = 28; //Column is "AB"
var valueToWatch = "Yes";
var sheetNameToMoveTheRowTo = "Archive"; //"Archive is the sheet the info is sent to"
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveCell();
if (sheet.getName() == sheetNameToWatch && range.getColumn() == columnNumberToWatch && range.getValue() == valueToWatch) {
var targetSheet = ss.getSheetByName(sheetNameToMoveTheRowTo);
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
sheet.getRange(range.getRow(), 1, 1, sheet.getLastColumn()).moveTo(targetRange); // I get its programmed for last row with "Yes" here, unsure on how to change this.
sheet.deleteRow(range.getRow());
function myfunction() {
ScriptApp.deleteTrigger("Move Archive"); // could have wrong value here
}
}
}
All I want is the sheet to "Archive" based on a "Yes" value in Column 28 (AB). I want every row with that column value to Archive at 1 am automatically. Any help is appreciated. If someone even wants to recommend a book or digital instruction for beginners that would be great.
You have four project files each containing scripts of the same name, performing the same or similar tasks. You are experiencing lagging because you have multiple simple and installable scripts of the same name. Essentially, they are all trying to execute at the same time.
The following answer should be considered as one possible solution to your situation.
The elements of this script are:
There is a single project file. Unnecessary project files have been deleted.
There is a single function.
The function is a single "simple" trigger (onEdit(e)) which takes advantage of the various event objects returns by onEdit. There are no installable triggers, and any/all installable triggers have been deleted.
The function updates the "Schedule" and "Archive" sheets as described in the question; and then sorts both the "Schedule" and "Archive" sheets.
If there is a change on the "Railcars" sheet, the function sorts that sheet.
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var testrange = e.range;
var testsheet = testrange.getSheet();
var testsheetname = testsheet.getSheetName();
var testrow = testrange.getRow();
var testcolumn = testrange.getColumn();
var testvalue = e.value;
var testsheetLC = testsheet.getLastColumn();
var testsheetRange = testsheet.getRange(testrow,1,1,testsheetLC);
//Logger.log("DEBUG: the test sheet range is "+testsheetRange.getA1Notation());
//Logger.log("DEBUG: Range: "+testrange.getA1Notation());
//Logger.log("DEBUG: The row is "+testrow+", and the column is "+testcolumn);
//Logger.log("DEBUG: The spreadheetsheet is "+e.source.getName()+", the sheet name is "+testsheet+", the range = "+testrange.getA1Notation()+", and the new value = "+testvalue);
//Logger.log(JSON.stringify(e));
// Copy/Paste to Schedule/Archive
var sheetNameSchedule = "Schedule";
var colNumberSchedule = 28;
var valueSchedule = "Yes";
var sheetNameArchive = "Archive";
// Sort Schedule
var sortSchedule = [{column: 1, ascending: true},{column: 2, ascending: true},{column: 7, ascending: false}];// date // Appt (time) // Type (Out/In/RR)
// Sort Railcars
var sheetNameRailcars = "Railcars";
var sortRailcars = [{column: 1, ascending: true}];
if (testsheetname === sheetNameSchedule && testcolumn === colNumberSchedule && testvalue === valueSchedule){
// this is a match
// Logger.log("DEBUG: this was a match");
// copy/paste to archive
var targetSheet = ss.getSheetByName(sheetNameArchive);
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
//Logger.log("DEBUG: the target range is "+targetRange.getA1Notation());
testsheetRange.moveTo(targetRange);
testsheet.deleteRow(testrow);
// sort the Schedule Sheet
var Avals = testsheet.getRange("A1:A").getValues();
var Alast = Avals.filter(String).length;
//Logger.log("DEBUG: The last row of content (in column A) = "+Alast+", and the last column = "+testsheetLC);
var sortrange = testsheet.getRange(2,1,Alast-1,testsheetLC);
//Logger.log("DEBUG: the sort range = "+sortrange.getA1Notation());
sortrange.sort(sortSchedule);
// sort the Archive Sheet
var ATvals = targetSheet.getRange("A1:A").getValues();
var ATlast = ATvals.filter(String).length;
//Logger.log("DEBUG: The last row of content (in column A) = "+ATlast+", and the last column = "+testsheetLC);
var sortrange = targetSheet.getRange(2,1,ATlast-1,testsheetLC);
//Logger.log("DEBUG: the sort range = "+sortrange.getA1Notation());
sortrange.sort(sortSchedule);
}
else if (testsheetname === sheetNameRailcars){
// sort the sheet
var Avals = testsheet.getRange("A1:A").getValues();
var Alast = Avals.filter(String).length;
//Logger.log("DEBUG: The last row of content (in column A) = "+Alast+", and the last column = "+testsheetLC);
var sortrange = testsheet.getRange(2,1,Alast-1,testsheetLC);
//Logger.log("DEBUG: the sort range = "+sortrange.getA1Notation());
sortrange.sort(sortRailcars);
}
}
You can set up an installable trigger to run this function at 1am every morning:
function scheduleToArchive() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var schedule = sheet.getSheetByName("Schedule");
var archive = sheet.getSheetByName("Archive");
var scheduleData = schedule.getRange(2, 1, (sheet.getRange('A:A').getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow()), 28);
for (row = 2; row <= scheduleData.getNumRows(); row++){
var data = schedule.getRange(row, 1, 1, 28).getValues();
if (data[0][27] == 'Yes'){
archive.getRange((archive.getLastRow() + 1), 1, 1, 28).setValues(data);
}
schedule.getRange(row, 1, 1, 28).clear();
}
}
In your spreadsheet, when getDataRange() is run on the sheet named 'Schedule', it returns row 631 as the last row with data even though there is only data in the first 20 rows, so to get around that I've used SpreadsheetApp.Direction.DOWN instead and run that on column A.

If AND statements in google script to move data to another sheet based on change in one cell and value in another

OK, I'm fairly new at doing this. I have a google spreadsheet that I populate and then run a script to create forms are created to complete tasks. My end users mark the task as completed. I am wanting entries in the spreadsheet that change to yes AND are Validate tasks to copy to another sheet (Log). I can get the entries to copy if I just have a watcher for yes but am having problems putting the other if statement for the task in there. Here is what I have for a test...
function onChange() {
// moves a row from any sheet to an archive sheet when a magic value is entered in a column
var columnNumberToWatch = /* column D */ 4; // column A = 1, B = 2, etc.
var valueToWatch = "yes";
var sheetNameToMoveTheRowTo = "LOG";
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet2");
var cell = sheet.getActiveCell();
if ( sheet.getName() != sheetNameToMoveTheRowTo && cell.getColumn() == columnNumberToWatch
&& cell.getValue().toLowerCase() == valueToWatch) {
if (cell.getColumn() == 2 && cell.getValue() == "Validate") {
var targetSheet = ss.getSheetByName(sheetNameToMoveTheRowTo);
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
sheet.getRange(cell.getRow(), 1, 1, sheet.getLastColumn())
.copyTo(targetRange);
}
}
}
It looks like you are trying to watch two separate locations with the same cell variable. You start by initializing cell as the active cell, then check that the column is #4, then in the second if statement you use cell to getColumn 2.
Maybe create a new set of variables to use for the validation check:
var activeRow=cell.getRow();
var valCheck=sheet.getRange(activeRow, 2);
then the if statement changes to:
if (valCheck.getValue() == "Validate") {

Apps Script script in Google Sheets moving the wrong row with two onEdit triggers

I am using 2 functions in a tracker that I have. 1 function is for sorting column C by date (earliest first) automatically when a cell in that column is edited. If the word 'done' or 'void' is typed into that cell then the entire row is copied onto the next sheet.
However my issue is that when I type 'done' or 'void' into this cell, Google sheets is sorting the list and also processing the move function and thus moving the wrong row (the resulting row number, after the sort). How can i fix or improve the code.
function movedonevoid(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
var value = r.getValue();
if(s.getName() == "tracker" && r.getColumn() == 3 && (value=="done" || value=="void" )) {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("tracker2");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 1, 1, numColumns).moveTo(target);
s.deleteRow(row);
}
}
and
function sorttracker(event){
var sheet = event.source.getActiveSheet();
if(sheet.getName() == 'tracker'){
var editedCell = sheet.getActiveCell();
var columnToSortBy = 3;
var tableRange = "A2:D"; // What to sort.
if(editedCell.getColumn() == columnToSortBy) {
var range = sheet.getRange(tableRange);
range.sort( { column : columnToSortBy, ascending: true } );
}
}
}
I've inserted the above 2 bits of code as 2 different scripts and then used project triggers for both 'on edit'. Not sure if that is the best approach?
As you already noticed, having two different functions acting over the same range and being called by two different onEdit triggers isn't a good idea.
Instead of using two onEdit triggers, use only one to call an orchestral conductor function that will call the original two. The orchestral conductor function could be something like the following:
function onEdit(e) {
movedonevoid(e);
sorttracker(e);
}