combine duplicate rows in apps script - javascript

Current sheet
Required sheet
I have an addon that combines duplicate rows, but I want apps script to run onEdit trigger.

Here is the script:
const HDR = 1 // header height
const GROUP_COL = 1 // in which column is the grouping value
function onEdit(e){
// we are interested only if value is not empty and the content of colum GROUP_COL has been changed
if(!e.value || e.value===e.oldValue || e.range.columnStart!==GROUP_COL || e.range.rowStart<=HDR){
return
}
const sh = SpreadsheetApp.getActiveSheet()
const values = sh.getDataRange().getValues().slice(HDR-1)
const existingRows = values.map((r,i)=>({ row:HDR+i, cells:r, found:r[GROUP_COL-1]==e.value })).filter(x=>x.found)
console.log(existingRows)
if(existingRows.length===0){
return
}
const destRange = sh.getRange(e.range.rowStart,1,1,existingRows[0].cells.length)
const destRow = destRange.getValues()[0]
// merge content into the new row
existingRows.forEach(er=>{
er.cells.forEach((c,i)=>{
if(i===GROUP_COL-1 || er.row===e.range.rowStart){
return
}
destRow[i] = (destRow[i] + "\n" + er.cells[i]).trim()
})
})
destRange.setValues([destRow])
// delete extra rows
existingRows.forEach((er,i)=>{
if(er.row!==e.range.rowStart){
sh.deleteRow(er.row-i)
}
})
}

Related

BUTTON to move data from multiple google sheets into one master

My boss told to move same set of data from 40 multiple google sheets into one master sheets which are filled by 40 people separately currently am working on it with an ADD on function
is there any way to put a submit button on each sheets so that when user fill the data and click on submit button the data go direct to the master sheets ?
I have the script which is working fine with sheets but the only cons of that is the script only combine the tabs into same spreadsheet
here is code :
const masterSheet = "ASM-A30";
const masterSheetFormulaCell = "A2";
const ignoreSheets = ["Verified NDRx","Business Tracker","NDRX","PMT_EBx","PMT_EBx.","NDRx PMT Business Tracker.","Analysis"];
const dataRange = "A2:AA";
const checkRange = "A2:A" ;
//end set variables
const ss = SpreadsheetApp.getActiveSpreadsheet() ;
ignoreSheets.push(masterSheet) ;
const allsheets = ss.getSheets() ;
const filteredListofSheets = allsheets.filter(s => ignoreSheets.indexOf(s.getSheetName()) == -1);
let formulaArray = filteredListofSheets.map(s => `FILTER({'${s.getSheetName()}'!${dataRange},"${s.getSheetName()} - Row "&ROW('${s.getSheetName()}'!${dataRange})}, '${s.getSheetName()}'!${checkRange}<>"")`);
let formulaText = "={" + formulaArray.join(";")+ "}"
//console.1og( formulaText) ;
ss. getSheetByName(masterSheet).getRange(masterSheetFormulaCell) .setFormula(formulaText) ;
}
Try this
const ignoreSheets = ["Verified NDRx","Business Tracker","NDRX","PMT_EBx","PMT_EBx.","NDRx PMT Business Tracker.","Analysis"];
const masterSheet = "ASM-A30";
function compile() {
let result = []
SpreadsheetApp.getActiveSpreadsheet().getSheets().filter(s => ignoreSheets.indexOf(s.getSheetName()) == -1).forEach((s,i) => {
let [headers, ...data] = s.getDataRange().getValues()
if (i == 0) { result.push(headers.flat()) }
data.forEach(r => result.push(r))
})
if(result.length) {
var master = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(masterSheet)
master.clearContents()
master.getRange(1, 1, result.length, result[0].length).setValues(result)
SpreadsheetApp.getActiveSpreadsheet().toast('Updated!');
}
}
to add rows, change
if (i == 0) { }
and try
var master = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(masterSheet)
// master.clearContents()
master.getRange(+master.getLastRow()+1, 1, result.length, result[0].length).setValues(result)
SpreadsheetApp.getActiveSpreadsheet().toast('Updated!');

Google sheet script to match and copy based from another adjacent cell

I am trying to look for matching cells in column R,T,and V in the reference table with column C in the main sheet. If there is a match then copy the assigned number in Q,S, and U and past it in column B next to the its match.
I have done the example manually.
Suggestion
Perhaps you can try this sample script:
Script [UPDATED]
function findMatch() {
var sh = SpreadsheetApp.getActiveSpreadsheet();
var lastRow = sh.getDataRange().getLastRow();
var reference = [];
var row = 1;
var data = sh.getRange("Q1:V"+lastRow).getDisplayValues().filter(function(x) {
return (x.join('').length !== 0);
});
sh.getRange("C1:C"+lastRow).getDisplayValues().forEach( x => {
reference.push([fixForUsage(x),row]);
row += 1;
});
reference.forEach(aisle => {
if(aisle[0] == "" || aisle[0].toString().toLowerCase().includes("aisle"))return;
data.forEach(match => {
var currentData = match[1] +"-"+match[3]+"-"+match[5];
if(currentData.includes(aisle[0])){
//get its number
if(match[1] == ""){
if(match[3] == ""){
if(match[5] == ""){
}else{
//if not empty
Logger.log("Find \""+aisle[0]+"\" from sheet row#"+aisle[1]+"\nRESULT:"+"\nFound a match on these data!: "+match+"\nHeader: \'"+data[0][4]+"\'\nNumber: "+match[4]);
pasteData(sh,aisle[1], data[0][4], match[4]);
}
}else{
//if not empty
Logger.log(+"Find \""+aisle[0]+"\" from sheet row#"+aisle[1]+"\nRESULT:"+"\nFound a match on these data!: "+match+"\nHeader: \'"+data[0][2]+"\'\nNumber: "+match[2]);
pasteData(sh,aisle[1],data[0][2],match[2]);
}
}else{
//if not empty
Logger.log("Find \""+aisle[0]+"\" from sheet row#"+aisle[1]+"\nRESULT:"+"\nFound a match on these data!: "+match+"\nHeader: \'"+data[0][0]+"\'\nNumber: "+match[0]);
pasteData(sh,aisle[1],data[0][0],match[0]);
}
}
});
});
}
function pasteData(sh,row, colAData, colBData){
sh.getRange("B"+row).setValue(colBData);
sh.getRange("A"+row).setValue(colAData);
}
function fixForUsage(x){ //This is to let the code know that e.q. 53-7 is 53-07
var part = x.toString().split("-");
if(part.length == 2 && part[1].length == 1){
return part[0]+"-0"+part[1];
}else{
return x;
}
}
Sample Sheet
Columns A, B, C:
Columns Q, R, S, T, U, V:
Sample Demonstration
After running the script from the Apps Script editor:
Apps Script editor log results for review:
Here is another solution (updated):
function myFunction() {
var sh = SpreadsheetApp.getActiveSheet();
// get all data
var data = sh.getRange('q2:v16').getDisplayValues();
var cols_QR = data.map(x => ({'crane': 'C5-1', 'seq': x[0], 'aisle': x[1]}));
var cols_ST = data.map(x => ({'crane': 'C5-4', 'seq': x[2], 'aisle': x[3]}));
var cols_UV = data.map(x => ({'crane': 'C5-2', 'seq': x[4], 'aisle': x[5]}));
data = [...cols_QR, ...cols_ST, ...cols_UV];
data = data.filter(x => x.aisle != '');
data.forEach((x,i) => data[i].aisle = fix(x.aisle)); // <--- new line
// create the object 'aisles'
var aisles = {}
for (let obj of data) aisles[obj.aisle] = {'crane': obj.crane, 'seq': obj.seq}
// get target range and target data
var target_range = sh.getRange('a2:c' + sh.getLastRow());
var target_data = target_range.getDisplayValues();
// fill target range with info from the 'aisles' object
for (let row in target_data) {
try {
let orig_key = target_data[row][2]; // <--- updated line
let key = fix(orig_key); // <--- new line
target_data[row] = [ aisles[key].crane, aisles[key].seq, orig_key ]; // <--- updated line
} catch(e) {}
}
// fill the target range with updated data
target_range.setValues(target_data);
}
// function to convert '10-1 1/4' --> '10-01', '8-0' --> '08-00', etc
function fix(key) {
return key.split(' ')[0]
.split('-').map(x => x.length == 1 ? '0' + x : x)
.join('-').slice(0,5);
}
Initial data:
Results:
Sheet
I decided don't add in my variant the function that makes true this: '53-7' == '53-07' and '11' == '11-0', etc, because I think it's a rather bad and error-prone idea. It will silently hide many errors in your data. But I can add it if you want.
Update
The function can be something like this:
function fix(key) {
return key.split(' ')[0]
.split('-').map(x => x.length == 1 ? '0' + x : x)
.join('-').slice(0,5);
}
// test
const keys = ['10-1 1/4', '11-0', '9-11 1/2', '8-0', '53-06', '52-7', '53-07', '104', '8-02-S', '08-01'];
keys.forEach(key => console.log(fix(key) + ' <--- ' + key));
I've added the function fix() and several lines to my original code.
But actually functions like this can cause additional troubles. For example I have no idea what means '104'? If it should be '10-04'? Or may be '01-04'? Or something else? You need be well prepared if you decide to play the risky game.
Note: you can change orig_key to key in this line:
target_data[row] = [ aisles[key].crane, aisles[key].seq, orig_key ];
This way you will get the 'fixed' values in the target table instead of original ones.

Send email for each project owner including all notes and subtasks

I am trying to write an Appscript/Javascript that can match a project to its tasks and notes relationship. Grab all the tasks + notes for each project and email the owner of the project. Im not sure how to do this at all. Since they are in three diffrent sheets.
Right now I have a script to send Weekly emails for each persons tasks assigned to them that works becasue it only requires the single IPTM_Task sheet.
My spreadsheet looks like this Tasks:
Task Sheet
SubTasks:
Subtasks
Notes:
Notes
Script to email task owners
function sendEmails() {
let s = '';
const sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("ITPM_Tasks");
const lastRow = sh.getLastRow();
const startRow = 2; // First row of data to process
const numRows = lastRow - 1; // Number of rows to process
const rg = sh.getRange(startRow, 3, numRows, 6);
const vs = rg.getValues();
let oners = {pA:[]};
vs.forEach((r,i) => {
let [name,desc,status,owner,due] = r;
if(status != 'Complete') {
if(!oners.hasOwnProperty(owner)) {
oners[owner]=[];
oners[owner].push(r);
oners.pA.push(owner)
} else {
oners[owner].push(r);
}
}
});
let subject = 'Weekly Reminder: The following tasks are assigned to you.';
oners.pA.forEach(p => {
let msg = `These Tasks below are assigned to you:\n`
oners[p].forEach((r,i) => {
let [name,desc,status,owner,due] = r;
msg += `Task - ${i+1}\n`;
msg += `Description: ${desc}\n`;
msg += `Due Date: ${due.toDateString()}\n\n`
});
msg += `some message to task owners`;
MailApp.sendEmail(oners[p][0][3], subject, msg);
});
}
EDIT:
Basically, I would like the script to see the Project ID on the Task Sheet grab the project name (Project) and the Owner
Then find the related "SubTask" Project Names" and related "Notes" to that project name
and send the Project owner an email with
Project Name
Subtask 1
subtask 2...
Note 1
Note 2
idealoutput
Makes sense to break it down, e.g.like this:
const ss = SpreadsheetApp.getActive()
const projects = ss.getSheetByName("Task Sheet").getDataRange().getValues()
projects.forEach( project => {
const projectId = project[0]
const status = project[2]
if( status.toLowerCase() == "completed") return
const notes = getNotesByProjectId(projectId)
const subtasks = getSubtasksByProjectId(projectId)
// here you can build your message
let message = ""
notes.forEach( note => {
// add conditions here
message += note[2] + "\n"
})
subtasks.forEach( subtask => {
// add conditions here
message += subtask[3] + "\n"
})
// and then send it
})
function getNotesByProjectId( projectId ){
const allNotes = ss.getSheetByName("Notes").getDataRange().getValues()
// Filter where Column 2 (index 1) is the project
const filteredNotes = allNotes.filter( note => note[1] == projectId )
return filteredNotes
}
function getSubtasksByProjectId( projectId ){
const allNotes = ss.getSheetByName("Subtasks").getDataRange().getValues()
// Filter where Column 3 (index 2) is the project
const filteredNotes = allNotes.filter( note => note[2] == projectId )
return filteredNotes
}
Reference
Sheet.getDataRange

How to send full row and list name from Google Sheet, not only specific (fixed) cells and list name?

So I have a script, that sends msg to group chat, if in Column ( 2 ) in any cell's some one printed "Yanson" bot sends only fixed cell - .getRange(row,8). In my case this cell holds link to document.
Bot msg looks like this - Link to document New Added Document List Name ( This time I get List name coz it fixed in var ws, if script work's in another list , I don't receive the right list Name I still receive the fixed one in var ws)
If we delete === ws and print "Yanson" in another list - I'll receive only info from .getRange(row,8) and "Added New Document.
But I need to send full string ( row ) with all the cell inside it, not only cell 8 with link. And I also need to see in msg from bot list name where "Yanson" was printed. Because I have more then 10+ list in Sheet. Sheet looks like this Tablepicture
const token = "Token";
function onEdit(e) {
sendTelegram(e)
}
function sendTelegram(e){
var row = e.range.getRow();
var col = e.range.getColumn();
var startRow = 2; // Starting row
var targetColumn = 2; // If in this column, cell changes to Yanson - send to Telegram
var ws = "List name"; //List name
let chatId = "ChatId";
let Company = e.source.getActiveSheet().getRange(row,8).getValue();
var text = encodeURIComponent(Company + " New Document Added" + ws)
var currentDate = new Date();
var url = "https://api.telegram.org/bot" + token + "/sendMessage?chat_id=" + chatId + "&text=" + text;
if (col === targetColumn && row >= startRow && e.source.getActiveSheet().getName() === ws){
if(e.source.getActiveSheet().getRange(row,2).getValue() == "Yanson"){ //Yanson - Trigger. If Yanson printed in cell in column 2 - send to telegram
sendText(chatId,Company + " New Document Added" +" "+ ws);
//Doing nothig right now.
// e.source.getActiveSheet().getRange(row,4).setValue(currentDate);
// if(e.source.getActiveSheet().getRange(row,3).getValue() == ""){
// e.source.getActiveSheet().getRange(row,3).setValue(currentDate)
// }
}
}
}
Based on what I could gather from your description, you are looking for a way to send the entire contents of the row as a string.
To do that, you get the range of that row, which looks like this:
sheet.getRange(starting row, starting column, # of rows, # of cols)
Sheets uses a two dimensional array that looks like this:
[[row1Col1, row1Col2, row1Col3], [row2Col1, row2Col2, row2Col3], etc]
const token = "Token";
function onEdit(e) {
sendTelegram(e)
}
function sendTelegram(e){
var row = e.range.getRow();
var col = e.range.getColumn();
var startRow = 2; // Starting row
var targetColumn = 2; // If in this column, cell changes to Yanson - send to Telegram
var ws = "List name"; //List name
/*--- Updated this section ----*/
//Adding variables to improve readiblity
var sheet = e.source.getActiveSheet();
var sheetName = e.source.getActiveSheet().getName();
let company = e.source.getActiveSheet().getRange(row,8).getValue();
var listName = ; //Is the list name the same as the sheet name? if not, reference the list names location here
//Define the range of the whole row
var firstCol = 1;
var numOfCols = 8;
var fullRowValues = sheet.getRange(row, firstCol, 1, numOfCols).getValues();
//since this is a single row, you can use .flat() to make it a 1D array
//Then convert it to a string
var fullRowString = fullRowValues.flat().toString();
/*---- End updates ---*/
let chatId = "ChatId";
var text = encodeURIComponent(Company + " New Document Added" + ws)
var currentDate = new Date();
var url = "https://api.telegram.org/bot" + token + "/sendMessage?chat_id=" + chatId + "&text=" + text;
if (col === targetColumn && row >= startRow && sheetName === ws){
if(company == "Yanson"){ //Yanson - Trigger. If Yanson printed in cell in column 2 - send to telegram
// Not sure what the output is supposed to look like,
// so I just added it to the end of your existing output
sendText(chatId,Company + " New Document Added" +" "+ ws + " All Values: " + fullRowString);
//Doing nothig right now.
// e.source.getActiveSheet().getRange(row,4).setValue(currentDate);
// if(e.source.getActiveSheet().getRange(row,3).getValue() == ""){
// e.source.getActiveSheet().getRange(row,3).setValue(currentDate)
// }
}
}
}
function onEdit(e) {
const sh = e.range.getSheet();
const row = sh.getRange(e.range.rowStart,1,1,sh.getLastColumn()).getDisplayValues()[0].join(',');//current row of active sheet
const name = e.source.getName();//spreadsheet name
//const name = sh.getName();//sheet name not sure which one you want
sendText('chatId', `${name)\n ${row}`);
}
You probably want to limit the trigger to a certain sheet and given row and column but I'll leave that up to you.

Placing a timestamp on a cell accross multiple sheets on edit

I tried many different variations of this script and can't figure out why it's not working.
I want to update the same cell "J3" with a timestamp across multiple tabs onedit. I have another function on the spreadsheet already using onEdit(e), so i set a new function with an onEdit trigger.
I retrieve the names of the tabs I want to update and store them in an array.
function Overview_timestamp(e) //function to use in the onEdit trigger
{
var activeSheet = e.source.getActiveSheet();
var range = e.range;
var cellToStamp = "J3" ;
var name_range = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Structure').getRange(2, 5, 14, 1).getValues(); //sheets i want to update
var flattened_name_range = [] ; //converting into 1D array
for (var i = 0; i < name_range.length; i++) {
flattened_name_range.push(name_range[i][0]);
}
if(flattened_name_range.indexOf(activeSheet())!==-1 ) {
activeSheet.getRange(cellToStamp).setValue(new Date()).setNumberFormat("yyyy-MM-dd hh:mm:ss");}
}
Try this:
function Overview_timestamp(e) {
const sh = e.range.getSheet();
if (sh.getName() == 'Sheet name') {//just to limit action to one sheet but it would be nice to have additional control like clicking a checkbox or selecting a value on a validated cell.
const shts = e.source.getSheetByName('Structure').getRange(2, 5, 14).getValues().flat();
shts.forEach(n => {
e.source.getSheetByName(n).getRange('J3').setValue(new Date()).setNumberFormat("yyyy-MM-dd hh:mm:ss")
});
}
}
I did it this way on my sheet using a checkbox at A1 on Sheet1
function onEdit(e) {
//e.source.toast('entry');
const sh = e.range.getSheet();
if (sh.getName() == 'Sheet1' && e.range.columnStart==1 && e.range.rowStart==1 && e.value=='TRUE') {
//e.source.toast('cond');
const shts = e.source.getSheetByName('Sheet2').getRange(2, 5, 14).getValues().flat().filter(e=>e);
shts.forEach(n => {
e.source.getSheetByName(n).getRange('J3').setValue(new Date()).setNumberFormat("yyyy-MM-dd hh:mm:ss")
});
e.range.setValue('FALSE');
}
}

Categories