Overlapping Date Formatting in Google Spreadsheet - google-apps-script

I just started exploring these spreadsheet scripts on Google Docs. I want to write a script that finds date overlaps between projects (changes the bg color of the given cell to red) and creates a new column that shows the number of conflicts on that project type. If you can provide me some examples or a way to do it, I would be very appreciated.
Here is my data set.
Data
What I tried is this. This only works for the first column though.
function formatting() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1'); // get the sheet
var columnF = sheet.getRange(1, 6, sheet.getLastRow(), 1).setBackgroundColor('white'); // get all the rows and clear colors
var columnG = sheet.getRange(1, 7, sheet.getLastRow(), 1).setBackgroundColor('white'); // get all the rows and clear colors
var fValues = columnF.getValues(); // get the values
var gValues = columnG.getValues();
var day = 24*3600*1000
Logger.log(gValues)
var startDay1 = parseInt(fValues[0][0].getTime()/day)
var endDay1 = parseInt(gValues[0][0].getTime()/day)
var startDay2 = parseInt(fValues[1][0].getTime()/day)
var endDay2 = parseInt(gValues[1][0].getTime()/day)
if (startDay1<endDay2 && startDay2<endDay1) {sheet.getRange(1, 6, 1, 1).setBackgroundColor('red')}
else {sheet.getRange(1, 6, 1, 1).setBackgroundColor('green')}
}

The code needed to loop through each row. Not sure how you want to contend with the last project, since there is no date to compare it to.
An easy way to keep count of the projects flagged red is to create a javascript object (projects), and store each of the projects, and their counts. Here is some documentation on javascript objects: Javascript.info - Objects
function formatting() {
try{
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1'); // get the sheet
// Going to need the column for project type
var projects = {};
var eValues = sheet.getRange(1, 5, sheet.getLastRow(), 1).getValues();
var columnF = sheet.getRange(1, 6, sheet.getLastRow(), 1).setBackgroundColor('white'); // get all the rows and clear colors
var columnG = sheet.getRange(1, 7, sheet.getLastRow(), 1).setBackgroundColor('white'); // get all the rows and clear colors
var fValues = columnF.getValues(); // get the values
var gValues = columnG.getValues();
var day = 24*3600*1000
Logger.log(gValues)
// loop through all the rows in the dataset
for(var r = 0; r < (fValues.length - 1); r++){
var startDay1 = parseInt(fValues[r][0].getTime()/day);
var endDay1 = parseInt(gValues[r][0].getTime()/day);
var startDay2 = parseInt(fValues[(r+1)][0].getTime()/day);
var endDay2 = parseInt(gValues[(r+1)][0].getTime()/day);
if (startDay1<endDay2 && startDay2<endDay1) {
sheet.getRange((r+1), 6, 1, 1).setBackgroundColor('red');
var projectName = eValues[r][0];
if(projects[projectName] !== undefined) { // strict(!) comparison
// add one to this projects count
projects[projectName] += 1;
}else{
// create the first count for this project
projects[projectName] = 1;
}
}
else {
sheet.getRange((r+1), 6, 1, 1).setBackgroundColor('green');
}
}
// updating is done, need to create the counts
// shove the results in column L
var rowCount = 1;
for(var key in projects) {
var val = projects[key];
sheet.getRange(rowCount, 12, 1, 1).setValue(key+": "+val);
rowCount += 1;
}
}catch(e){
Logger.log(e.lineNumber + ' - ' + e);
}
}

Here is the answer in case anyone is interested.
function formatting(m, d) {
try {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1'); // get the sheet
var output = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet2'); // output sheet
output.clear()
// Going to need the column for project type
var counts = {};
var eValues = sheet.getRange(1, 3, sheet.getLastRow(), 1).getValues();
var columnF = sheet.getRange(1, 1, sheet.getLastRow(), 1).setBackgroundColor('white'); // get all the rows and clear colors
var columnG = sheet.getRange(1, 2, sheet.getLastRow(), 1).setBackgroundColor('white'); // get all the rows and clear colors
var columnD = sheet.getRange(1, 4, sheet.getLastRow(), 1).setBackgroundColor('white').clearContent(); // get all the rows and clear colors
var columnCritical = sheet.getRange(1, 7, sheet.getLastRow(), 1).setBackgroundColor('white').clearContent(); // get all the rows and clear colors
var fValues = columnF.getValues(); // get the values
var gValues = columnG.getValues();
var day = 24 * 3600 * 1000
// loop through all the rows in the dataset
for (var r = 0; r < (fValues.length - 1); r++) {
var startDay1 = parseInt(fValues[r][0].getTime() / day);
var endDay1 = parseInt(gValues[r][0].getTime() / day);
// loop through all the rows for given date
for (var z = 0; z < (fValues.length - 1); z++) {
var startDay2 = parseInt(fValues[(z)][0].getTime() / day);
var endDay2 = parseInt(gValues[(z)][0].getTime() / day);
// if the date is the same go to the next one
if (startDay1 == startDay2 && endDay2 == endDay1) continue;
// check for conflicts
else if (startDay1 < endDay2 && startDay2 < endDay1) {
//Here is our conflict!!!;
var projectn = r + 1
if (counts[projectn] !== undefined) { // strict(!) comparison
// add one to this projects count
counts[projectn] += 1;
} else {
// create the first count for this project
counts[projectn] = 1;
}
} else {
// if there is no conflict
}
} //end of for loop for var z
// change the background of the counts to red and set values
if (counts[r + 1] == undefined) {
sheet.getRange(r + 1, 4, 1, 1).setValue(counts[r + 1]).setBackground('green');
} else {
sheet.getRange(r + 1, 4, 1, 1).setValue(counts[r + 1]).setBackground('red');
}
} // end of for loop for var r
// show if there is any errors
} catch (e) {
Logger.log(e.lineNumber + ' - ' + e);
}
}

Related

Trying to create a script for trigger mail based on a condition in google sheets

So this is the script am using, it sends out a blank mail without any content in them. Here I am trying to compare two columns and if the value in one column is great am trying to add the row header to the list which has to be sent out via mail. Can someone help me out pls.
function Mailer1() {
var ss = SpreadsheetApp.openById("");
var sheet = ss.getSheetByName("Today");
var values = sheet.getRange("AD2:AD").getValues();
var value1s = sheet.getRange("AE2:AE").getValues();
var results = [];
for(var i=0;i<values.length;i++)
{
if(values[i]>value1s[i])
{
var value5 = sheet.getRange(i+2,1).getValue();
results.concat(value5);
}
}
MailApp.sendEmail('vinay_narayanan#playshifu.com','Restock Needed At:',results);
};
I'm guessing that the values in 30 and 31 are some sort of quantities and the value in column 1 is a date.
Try doing this:
function Mailer1() {
var ss = SpreadsheetApp.openById("");
var sh = ss.getSheetByName("Today");
var values = sh.getRange(2, 30, sh.getLastRow() - 1).getValues().flat();
var value1s = sh.getRange(2, 31, sh.getLastRow() - 1).getValues().flat();
var results = [];
for (var i = 0; i < values.length; i++) {
if (values[i] > value1s[i]) {
results.push(sh.getRange(i + 2, 1).getValue());
}
}
MailApp.sendEmail('vinay_narayanan#playshifu.com', 'Restock Needed At:', results.join('\n'));
};

Move multiple rows at once with multiple conditions

I want to iterate through each row at once with the for loop. When the row has the value "Done" in column P and "Update" in column M, I want to move the row to Done Sheet, if the row just has "Done" in P, I want it to move to Control Sheet.
function done() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getActiveSheet();
var numColumns = s.getLastColumn();
for(var i = 1; i < 50; i++) {
var cP = s.getRange(i, 13).getValue();
var cM = s.getRange(i, 10).getValue();
if (cP == "Done" && cM == "Update") {
var targetSheet = ss.getSheetByName("Done Sheet"); // Logger.log(targetSheet); —> no output
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(i, 1, 1, numColumns).moveTo(target);
s.deleteRow(i);
}
else if (cP == "Done") {
var targetSheet = ss.getSheetByName("Control Sheet");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(i, 1, 1, numColumns).moveTo(target);
s.deleteRow(i);
}
}
}
Please check if this code works for you:
function done() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName('Main Sheet'); // ss.getActiveSheet(); <= better call by name
var numColumns = s.getLastColumn();
var values = s.getDataRange().getValues(); // values keeps an array from the whole Sheet
var doneSheet = ss.getSheetByName("Done Sheet");
var ctrlSheet = ss.getSheetByName("Control Sheet");
var rows_to_delete = []; // will receive the indexes to delete
var doneSheetInputs = []; // will receive data to be inputed on doneSheet
var ctrlSheetInputs = []; // will receive data to be inputed on ctrlSheet
for(var i = 0; i < values.length; i++) {
var cP = values[i][15]; //s.getRange(i, 13).getValue();
var cM = values[i][12]; //s.getRange(i, 10).getValue();
if (cP == "Done" && cM == "Update") {
doneSheetInputs.push(values[i]);
rows_to_delete.push(i + 1); // +1 because of difference in Array versus Google Sheets index
Logger.log('Moving row ' + (i + 1) + ' to DONE');
}
else if (cP == "Done") {
ctrlSheetInputs.push(values[i]);
rows_to_delete.push(i + 1);
Logger.log('Moving row ' + (i + 1) + ' to CONTROL');
}
}
Logger.log('doneSheetInputs has ' + doneSheetInputs.length + ' entries');
if (doneSheetInputs.length > 0) {
doneSheet.getRange(doneSheet.getLastRow() + 1, 1, doneSheetInputs.length, doneSheetInputs[0].length).setValues(doneSheetInputs);
}
Logger.log('ctrlSheetInputs has ' + ctrlSheetInputs.length + ' entries');
if (ctrlSheetInputs.length > 0) {
ctrlSheet.getRange(ctrlSheet.getLastRow() + 1, 1, ctrlSheetInputs.length, ctrlSheetInputs[0].length).setValues(ctrlSheetInputs);
}
Logger.log('rows_to_delete has ' + rows_to_delete.length + ' entries');
var rev = rows_to_delete.reverse(); // delete from bottom to top
for (var i = 0; i < rev.length; i++) {
s.deleteRow(rev[i]);
}
}
It's not recommended to call getRange().getValues() so many times, specially inside a long for statement as described in Google Apps Script Best Practices. I always try to call getValues before doing any iterations and return the values at the end of the code, in a batch operation.
EDIT: found out that the indexes for P and M columns were wrong - didn't check that out. Here is a working example: https://docs.google.com/spreadsheets/d/1n68KqmI7HongEzJ_MSwmeQUqlAExjvCu2epEjEBZrlg/edit#gid=0 (send me request to access if you need it).

Script exceeds maximum execution time

I am using a script to first copy a list of all terminated products from "data" tab of the sheet to the "terminated tab"
The data tab looks like below
The code checks if there is an end date written
if it is - the row is copied and pasted in the "terminated" tab
Once all rows (around 2000) are completed
the code the deletes all rows from the "data" tab that have an end date on it
But the code is not very efficient and data is huge - I get a "maximum execution time exceeded" error
function movingTerms() {
var app = SpreadsheetApp ;
var sheet1 = app.getActiveSpreadsheet().getSheetByName("data") ;
var sheet3 = app.getActiveSpreadsheet().getSheetByName("Terminations");
var range1 = sheet1.getRange(2, 1, sheet1.getLastRow() - 1,9);
var range3 = sheet3.getRange(2, 1, sheet3.getLastRow(), 9);
var values1 = range1.getValues();
var values3 = range3.getValues();
var rowcount = sheet1.getLastRow();
var row_deleted = 0;
for (var i = 0; i < (sheet1.getLastRow() - 1); i++)
{
if (values1[i][4] !== "")
{
var rowtodelete = sheet1.getRange(i + 2, 1, 1, 10);
var rowtoadd = sheet3.getRange(sheet3.getLastRow() + 1, 1);
rowtodelete.copyTo(rowtoadd);
}
}
for (var k = 0; k < values1.length; k++)
{
var row = k + 1 - row_deleted;
if (values1[k][4] !== "")
{
var getridof = row +1;
sheet1.deleteRow(getridof);
row_deleted++;
}
}
}
I generally like to see the spreadsheet to do this correctly but this is the way that I would do it.
function movingTerms() {
var ss=SpreadsheetApp.getActive();
var sheet1=ss.getSheetByName("data") ;
var sheet3=ss.getSheetByName("Terminations");
var range1=sheet1.getRange(2, 1, sheet1.getLastRow()-1,9);
var range3=sheet3.getRange(2, 1, sheet3.getLastRow(),9);//You don't really need this
var values1=range1.getValues();
var values3=range3.getValues();//You don't really need this
var rowcount=sheet1.getLastRow();
var rowsdeleted = 0;
for (var i=0;i<values1.length;i++) {
if (values1[i][4]!="") {//column5
var rowtodelete = sheet1.getRange(i-rowsdeleted+2, 1, 1, 10);
var rowtoadd = sheet3.getRange(sheet3.getLastRow()+1,1);//You could also append to sheet 3 if you wish
rowtodelete.copyTo(rowtoadd);
sheet1.deleteRow(i-rowsdeleted+2);
rowsdeleted++;
}
}
}

Google App Script to find a date across multiple Sheets

I have a Spreadsheet with many sheets inside it, Each sheet represents one month, and has dates in column B. I'm trying to create a function that searches for the current date and inserts the current time in column C. I've got a function to work by specifying the sheet, but I want it to find the sheets itself.
I've got the following code:
function insertTime() {
var date = new Date();
var hours = date.getHours()
var minutes = date.getMinutes()
var timeStamp = hours + ':' + minutes
var ss = SpreadsheetApp.getActiveSpreadsheet()//// get the sheet
var sheets = ss.getSheets();// get all the sheets
var today = new Date(); //get todays date
for (var sheetNum = 1; sheetNum < sheets.length; sheetNum++){
var sheet = ss.getSheets()[sheetNum]
var columnB = sheet.getRange(2, 2, sheet.getLastRow()-1, 1); // get all the rows
var bValues = columnB.getValues(); // get the values
for (var i = 0; i < bValues.length; i++) { // repeat loop
var fDate = new Date(bValues[i][0]);
if (fDate.getDate() == today.getDate() &&
fDate.getMonth() == today.getMonth() &&
fDate.getFullYear() == today.getFullYear()) { // if the date in the cell is today's date...
sheet.getRange(i + 2, 3, 1, 1).setValue(timeStamp);
}
}
}
}
Which is giving me the following error: "The coordinates or dimensions of the range are invalid. (line 16)"
At least one of your sheets is empty or has only 1 row of data.
In this case on line 16, sheet.getLastRow()-1 will be 0 or -1 which is creating the error.
I figured it out in the end, I used the forEach function:
function myFunction(){
var date = new Date();
var hours = date.getHours()
var minutes = date.getMinutes()
var timeStamp = hours + ':' + minutes
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
sheets.forEach (function (sheet) {
var columnB = sheet.getRange(2, 2, 70, 1);
var bValues = columnB.getValues();
var today = new Date();
for (var i = 0; i < bValues.length; i++) {
var fDate = new Date(bValues[i][0]);
if (fDate.getDate() == today.getDate() &&
fDate.getMonth() == today.getMonth() &&
fDate.getFullYear() == today.getFullYear()) {
sheet.getRange(i + 2, 7, 1, 1).setValue(timeStamp);
}
}
}

Google Script to delete row if date in certain cell is smaller than current date

I want a Google Script which checks once a day on my spreadsheet and copies the values to another sheet and than deletes all the rows if the date in that cell is small than the current.
The file looks like this:
And this is what I wrote so far:
function DeleteIfDateIsToSmall(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
var today = new Date();
var today2 = Utilities.formatDate(today,'GMT+0200','dd.MM.yyyy');
var startRow = 2; // First row of data to process
var numRows = sheet.getLastRow()-1
for (var i=2; i < numRows; i++) {
var DateCell = s.getRange(i, 13);
var sheetDate = DateCell.getValue()
var sheetDate2 = Utilities.formatDate(sheetDate,'GMT+0200','dd.MM.yyyy');
var row = i;
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("Ended or Deleted");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
if (s.getName() == "Start" && sheetDate != 0 && sheetDate != "" && today2.valueOf() > sheetDate2.valueOf()){
s.getRange(row, 1, 1, numColumns).moveTo(target);
s.deleteRow(row);
}
}
}
It doesn't seem to work, please help.
A few things are wrong or suboptimal here.
For a timed trigger, the event object does not have any range/source data; it only has time data. You need to pick a sheet by name (or loop through all sheets obtained by getSheets, if this is what you want).
Utilities.formatDate returns a string, which is not the best way to compare dates; in any case calling valueOf on it seems pointless.
Instead of using getValue in a loop, it is more efficient to obtain values before entering the loop, with getValues. There are some other things you do in the loop that should be outside.
Deleting rows when moving top to bottom in a sheet is tricky, because rows shift, messing up their indexing. For this reason, I collect the rows to be deleted in an array, and then delete them in bottom-to-top order.
function DeleteIfDateIsTooSmall() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName("Start");
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("Ended or Deleted");
var today = new Date();
var dates = s.getRange(2, 13, s.getLastRow()-1, 1).getValues();
var rowsToDelete = [];
for (var i = 0; i < dates.length; i++) {
if (dates[i][0] && dates[i][0] < today) {
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(i + 2, 1, 1, numColumns).moveTo(target);
rowsToDelete.push(i + 2);
}
}
for (i = rowsToDelete.length - 1; i >= 0; i--) {
s.deleteRow(rowsToDelete[i]);
}
}