Sum up the time values corresponding to same date - google-apps-script

In my sheet column A is date and column B is time duration values, I want to find the dates which are repeated and sum up the corresponding time values of the repeated dates and show the sum in the last relevant repeated date. And delete all the other repeated dates. ie if 18/07/2019 is repeated 4 times i have to sum up all the four duration values and display the sum value in the 4th repeated position and delete the first three date 18/07/2019. I have to do this all those dates that are repeated. I have wrote code to my best knowledge
function countDate() {
var data = SpreadsheetApp.getActive();
var sheet = data.getSheetByName("Sheet5");
var lastRow = sheet.getLastRow();
var sh = sheet.getRange('A1:A'+lastRow);
var cell = sh.getValues();
var data= sheet.getRange('B1:B'+lastRow).getValues();
for (var i =0; i < lastRow; ++i){
var count = 0;
var column2 = cell[i][0];
for (var j =0; j < i; j++)
{
var p=0;
var column4 = cell[j][0];
if (column4 - column2 === 0 )
{
var value1 = data[j][0];
var value2 = data[i][0];
var d = value2;
d.setHours(value1.getHours()+value2.getHours()+0);
d.setMinutes(value1.getMinutes()+value2.getMinutes());
sheet.getRange('C'+(i+1)).setValue(d).setNumberFormat("[hh]:mm:ss");
sheet.deleteRow(j+1-p);
p++;
}
}
}
}
The copy of the sheet is shown
column C is the values I obtain through the above code AND column D is the desired value
After computing the sum I need to delete the repeated rows till 15 here

Answer:
You can do this by converting your B-column to a Plain text format and doing some data handling with a JavaScript dictionary.
Code:
function sumThemAllUp() {
var dict = {};
var lastRow = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0].getLastRow();
var dates = SpreadsheetApp.getActiveSpreadsheet().getRange('A1:A' + lastRow).getValues();
var times = SpreadsheetApp.getActiveSpreadsheet().getRange('B1:B' + lastRow).getValues();
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
sheet.getRange(1, 1, sheet.getLastRow(), sheet.getLastColumn()).setNumberFormat("#");
for (var i = 0; i < dates.length; i++) {
if (!dict[dates[i][0]]) {
dict[dates[i][0]] = times[i][0];
}
else {
var temp = dict[dates[i][0]];
var hours = parseInt(temp.split(':')[0]);
var minutes = parseInt(temp.split(':')[1]);
var additionalHours = parseInt(times[i][0].split(':')[0]);
var additionalMinutes = parseInt(times[i][0].split(':')[1]);
var newMinutes = minutes + additionalMinutes;
var newHours = hours + additionalHours;
if (newMinutes > 60) {
newHours = newHours + 1;
newMinutes = newMinutes - 60;
}
dict[dates[i][0]] = newHours + ':' + newMinutes;
}
}
SpreadsheetApp.getActiveSpreadsheet().getSheets()[0].getRange('A1:B' + lastRow).clear();
var keys = Object.keys(dict);
for (var i = 0; i < keys.length; i++) {
SpreadsheetApp.getActiveSpreadsheet().getSheets()[0].getRange('A' + (i + 1)).setValue(keys[i]);
SpreadsheetApp.getActiveSpreadsheet().getSheets()[0].getRange('B' + (i + 1)).setValue(dict[keys[i]]);
}
}
Assumptions I made:
There are a few assumptions I made when writing this, you can edit as needed but I figured I should let you know:
There are only dates in Column A and only times in Column B.
The times in column B are either Hours:Minutes or Minutes:Seconds. Either way, if the value to the right of the : hits 60, it adds one to the left value and resets.
The Sheet within the Spreadsheet is the first sheet; that which is returned by Spreadsheet.getSheets()[0].
References:
w3schools - JavaScript Objects
Spreadsheet.getSheets()
w3schools - JavaScript String split() Method
MDN web docs - parseInt() method
Google Sheets > API v4 - Date and Number Formats

Related

How to Include more sheets to write incremental number

I am using this function which works upon submitting the entry via Google Forms. Currently the below script is adding increment number to the Sheet1 Last empty row
I want to include two more sheets where same incremental number will be added to last empty row.
Column will be same where increment number is pasting that is Column A but Sheet1 data starts from Row2 and Sheet2 and Sheet3 Data starts from row6.
Your help will be much appreciated.
function uPDATEiT() {
var aiColumnName = 'A'; //Sheet1,Sheet2,Sheet3 same column
var requieredColName = 'C' //it is just for Sheet1
var ss = SpreadsheetApp.getActiveSpreadsheet()
var worksheet = ss.getSheetByName('Sheet1','Sheet2','Sheet3')
var aiColRange = worksheet.getRange(aiColumnName + '1:' + aiColumnName + '1000');
var aiCol = aiColRange.getValues();
var aiColIndex = aiColRange.getColumn();
var reqCol = worksheet.getRange(requieredColName + '1:' + requieredColName + '1000').getValues();
var maxSeq = 0;
for (var i = 0; i <= aiCol.length; i++) {
if (parseInt(aiCol[i], 10) > maxSeq) { maxSeq = aiCol[i]; }
}
for (var i = 0; i <= aiCol.length; i++) {
if (('' + reqCol[i]).length > 0 && ('' + aiCol[i]).length === 0) {
maxSeq++;
worksheet.getRange(i + 1, aiColIndex).setValue(maxSeq);
}
}
}
You can use this formula in Sheet2!A4 and Sheet3!A4:
={"ID's";ARRAYFORMULA(IF(B5:B<>"",vlookup(B5:B,{'Data Sheet (Sheet1)'!$B$2:$B,'Data Sheet (Sheet1)'!$A$2:$A},2,false),""))}
What it does?
Check if column B is not empty, then get the id from Sheet1 based on the matched name(column B) as a key in the vlookup()
Output:
Original Sheet2:
Sheet 2 using the formula:
Update
If you just want to append your maxSeq in Sheet2, you can use this:
Code:
function uPDATEiT() {
var aiColumnName = 'A'; //Sheet1,Sheet2,Sheet3 same column
var requieredColName = 'C' //it is just for Sheet1
var ss = SpreadsheetApp.getActiveSpreadsheet()
var worksheet = ss.getSheetByName('Data Sheet (Sheet1)')
var sheet2 = ss.getSheetByName('Sheet2')
Logger.log(worksheet)
Logger.log(sheet2.getLastRow())
var aiColRange = worksheet.getRange(aiColumnName + '1:' + aiColumnName + '1000');
var aiCol = aiColRange.getValues();
var aiColIndex = aiColRange.getColumn();
var reqCol = worksheet.getRange(requieredColName + '1:' + requieredColName + '1000').getValues();
var maxSeq = 0;
for (var i = 0; i <= aiCol.length; i++) {
if (parseInt(aiCol[i], 10) > maxSeq) { maxSeq = aiCol[i]; }
}
for (var i = 0; i <= aiCol.length; i++) {
if (('' + reqCol[i]).length > 0 && ('' + aiCol[i]).length === 0) {
maxSeq++;
worksheet.getRange(i + 1, aiColIndex).setValue(maxSeq);
sheet2.getRange(sheet2.getLastRow()+1,aiColIndex).setValue(maxSeq);
}
}
}
Get the last row in the sheet that contains data using getLastRow() , then increment it by 1.
Output:

How to deal with duplicates in google sheets script?

So in the project I want to do I have a google sheet with timestamps and names next to those timestamps in the spreadsheet. I am having trouble accounting for duplicates and giving the name multiple timestamps in another google sheet.
for(var i = 0; i < length; i++){//for loop 1
if(currentCell.isBlank()){//current cell is blank
daySheet.getRange(i+10, 2).setValue(fullName);//set name to first cell
daySheet.getRange(i+10,3).setValue(pI);
daySheet.getRange(i+10,day+3).setValue(1);
}else if(counter > 1 ){//the index of the duplicate in the sheet month
//if counter is > 1 then write duplicates
for(var t = 1; t <= sheetLength ; t++){//loop through sign in sheet
//current index i
if(signInLN == signInSheet.getRange(t+1,3).getValue()){
//if there is a match
daySheet.getRange(t+10,day+3).setValue(1);
//day is equal to the day I spliced from the timestamp
//at this point I am confused on how to get the second date that has the same
//name and add to the row with the original name.
//when i splice the timestamp based on the row of index i, with duplicates I get
//the day number from the first instance where the name is read
}
}
}//for loop 1
How can I get this to work with duplicates so I can account for the dates but make sure that if there are
any duplicates they will be added to the row of the original name
Google Sheet EX:
12/10/2020 test1
12/11/202 test2
12/15/2020 test1
Should be something like this:
name 10 11 12 13 14 15 16
test1 1 1
test2 1
//the one is to identify that the date is when the user signed in on the sheets.
Sample Spreadsheet:
Code snippet done with Apps Script, adapt it to your needs.
use Logger.log() in case you don't understand parts of code
It is done mainly with functional JavaScript
function main(){
var inputRange = "A2:B";
var sheet = SpreadsheetApp.getActive().getSheets()[0]
var input = sheet.getRange(inputRange).getValues(); //Read data into array
var minDate, maxDate;
var presentDates = input.map(function(row) {return row[0];}); //Turns input into an array of only the element 0 (the date)
minDate = presentDates.reduce(function(a,b) { return a<b?a:b}); //For each value, if its the smallest: keep; otherwise: skip;
maxDate = presentDates.reduce(function(a,b) { return a>b?a:b}); //Same as above, but largest.
var dates = [];
for (var currentDate = minDate; currentDate <= maxDate; currentDate.setDate(currentDate.getDate()+1)) {
dates.push(getFormattedDate(currentDate)); //Insert each unique date from minDate to maxDate (to leave no gaps)
}
var uniqueNames = input.map(function(row) {return row[1];}) //Turns input into an array of only the element at 1 (the names)
.filter(function (value, index, self) {return self.indexOf(value) === index;}); //Removes duplicates from the array (Remove the element if the first appearence of it on the array is not the current element's index)
var output = {}; //Temporary dictionary for easier counting later on.
for (var i=0; i< dates.length; i++) {
var dateKey = dates[i];
for (var userIndex = 0; userIndex <= uniqueNames.length; userIndex++) {
var mapKey = uniqueNames[userIndex]+dateKey; //Match each name with each date
input.map(function(row) {return row[1]+getFormattedDate(row[0])}) //Translate input into name + date (for easier filtering)
.filter(function (row) {return row === mapKey}) //Grab all instances where the same date as dateKey is present for the current name
.forEach(function(row){output[mapKey] = (output[mapKey]||0) + 1;}); //Count them.
}
}
var toInsert = []; //Array that will be outputted into sheets
var firstLine = ['names X Dates'].concat(dates); //Initialize with header (first element is hard-coded)
toInsert.push(firstLine); //Insert header line into output.
uniqueNames.forEach(function(name) {
var newLine = [name];
for (var i=0; i< dates.length; i++) { //For each name + date combination, insert the value from the output dictionary.
var currentDate = dates[i];
newLine.push(output[name+currentDate]||0);
}
toInsert.push(newLine); //Insert into the output.
});
sheet.getRange(1, 5, toInsert.length, toInsert[0].length).setValues(toInsert); //Write the output to the sheet
}
// Returns a date in the format MM/dd/YYYY
function getFormattedDate(date) {
var year = date.getFullYear();
var month = (1 + date.getMonth()).toString();
month = month.length > 1 ? month : '0' + month;
var day = date.getDate().toString();
day = day.length > 1 ? day : '0' + day;
return month + '/' + day + '/' + year;
}
Run script results:

writing a count program to find the number of repeatitions

I am writing a code to count the number of times a time duration in column B is repeated.After writing a code according to my logic I am still getting error or no output. Here's the app script code I have written. Help me out.
function count() {
var data = SpreadsheetApp.getActive();
var sheet = data.getSheetByName("Sheet4");
var lastRow = sheet.getLastRow();
var sh = sheet.getRange('B1:B' + lastRow);
var cell = sh.getValues();
var count = 0;
for (var i = 0; i < lastRow; j++) {
var column2 = cell[i][1];
for (var j = 0; j < i; j++) {
var column4 = cell[j][1];
if (column4 == column2) {
count++;
}
}
sheet.getRange('F' + (i + 1).toString()).setValue(count);
}
}
This is the intended output instead of column F i put the sample output in column C
Issue(s):
Outer loop's increment variable doesn't change.
for (var i = 0; i < lastRow;/*i never changes =>*/ j++)
Date/Time are retrieved as JavaScript objects and cannot be compared with ==:
if (column4 == column2) {//Date objects cannot be compared
cell doesn't have a value at array index [1]
Solution:
Increment all variables in the for -loop
Compare value of date objects instead
Array indexes start at [0]
Snippet:
for (var i = 0; i < lastRow; ++i) {
var column2 = cell[i][0];
/*....*/
var column4 = cell[j][0];
if (column4 - column2 === 0) {//#see https://stackoverflow.com/questions/492994

Troubleshoot Google Script for Hiding Columns based on date range

CORRECTED
I have a google sheet with a table of weekly data (one year's worth).
https://docs.google.com/spreadsheets/d/1u5o0rqEFTiGcZygtbMcohuQAWn6AxfXF78c4Vjp2D8g/edit#gid=84545445
At the top of the sheet I have "to" and "from" date fields.
I am trying to restrict the user's view by hiding all columns which relate to dates outside of the "to"/"from" range.
I have researched how to do this but am clearly unable to craft the script correctly.
UPDATE I have now corrected this but my script will only hide the
first column no matter what dates I have in the "to"/"from" cells.
Script exactly as below.
Thank you.
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName('NEW - HOURS');
var startColumn = 5;
var values = s.getRange('E7:BD7').getValues();
var start = s.getRange('B4').getValue();
var end = s.getRange('B5').getValue();
var column, len, date, hideCount = 0, showCount = 0;
for (column = values.length - 1; column >= 0; --column) {
date = values[column][0];
if ( typeof date != 'object' || !(date >= start && date < end) ) {
if (showCount) {
s.showColumns(column + startColumn + 1, showCount);
showCount = 0;
}
hideCount++;
} else {
if (hideCount) {
s.hideColumns(column + startColumn + 1, hideCount);
hideCount = 0;
}
showCount++;
}
}
if (showCount) s.showColumns(column + startColumn + 1, showCount);
if (hideCount) s.hideColumns(column + startColumn + 1, hideCount);
}
Mind the row-column notation
Be aware that a range is a 2-dimensional array, where the outer array corresponds to the rows, and the nested array to the columns, as can be withdrawn from the method getRange(row, column).
As a consequence, the values matrix is defined as values[row][column], and values.length will give you the number of rows - for the number of columns use values[0].length.
In summary, you need to change
for (column = values.length - 1; column >= 0; --column) {
date = values[column][0];
...
to
for (column = values[0].length - 1; column >= 0; --column) {
date = values[0][column];
...
ADDITIONAL UPDATE:
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName('NEW - HOURS');
var startColumn = 5;
var values = s.getRange('E7:7').getValues();

Issue copying invoice data from one sheet to another when payment is selected

I am looking to save products that are chosen in an invoice to another sheet in the same workbook when a payment method is selected. Here is a copy of the sheet. How the sheet works:
1) User places "x" into selection column in "Protocol Selection" (WORKING)
2) In the next sheet in the workbook "Patient Invoice", an invoice with bottle count is generated (WORKING)
3) I want the past invoices (with product, date, pill-count, etc.) to be copied over to the "Past Invoices" sheet when the "Method of Payment" is selected. This is a drop-down Data Validation cell. (NOT WORKING)
Is there a way to do this without a custom script? IF not, what is the script?
Probably not the most efficient method but it does work...
Script
function makePastInvoice() {
var patientInvoice = SpreadsheetApp.getActive().getSheetByName('Patient Invoice');
var pastInvoices = SpreadsheetApp.getActive().getSheetByName('Past Invoices');
// vistor details
var visitDate = patientInvoice.getRange("D3").getValue();
var patientName = patientInvoice.getRange("G5").getValue();
var doctorName = patientInvoice.getRange("I5").getValue();
var programLength = patientInvoice.getRange("I3").getValue();
var invoiceNumber = patientInvoice.getRange("G3").getValue();
var total = patientInvoice.getRange("K30").getValue();
var paymentMethod = patientInvoice.getRange("D5").getValue();
var visitorDetails = [visitDate, patientName, doctorName, programLength, invoiceNumber, total, paymentMethod];
var lastRow = pastInvoices.getLastRow();
var currentRow = lastRow + 1;
// set values of patient details to Past Invoices sheet
for (var i = 0; i < visitorDetails.length; i++) {
pastInvoices.getRange(currentRow, i+1).setValue(visitorDetails[i]);
}
// 15 possible max items (per Past Invoices)
var productIDs = patientInvoice.getRange("A8:A22").getValues();
// count number of products
var productCount = 0;
for(var i = 0; i < productIDs.length; i++) {
if (productIDs[i] > "") {
productCount++;
}
}
// Patient Invoice
var firstItemRow = 8;
// Past Invoices
var firstItemCol = 8 // column H
// loop through purchases
for(var i = 0; i < productCount; i++) {
// purchase details
var productName = patientInvoice.getRange(firstItemRow, 2).getValue(); // col B
var dosage = patientInvoice.getRange(firstItemRow, 5).getValue(); // col E
var numBottles = patientInvoice.getRange(firstItemRow, 6).getValue(); // col F
var purchaseDetails = [productName, dosage, numBottles];
// set values of purchase details to Past Invoices sheet
for(var j = 0; j < purchaseDetails.length; j++) {
pastInvoices.getRange(currentRow, firstItemCol).setValue(purchaseDetails[j])
firstItemCol ++;
}
firstItemRow++;
}
}
Trigger
I'm sure there's a way to trigger the script when the payment method changes or any other method of choice but I created a "submit" trigger with a drawing and assigned it to the makePastInvoice function.
Also, you'll probably want to change the "Visit Date" column's format to date.