0
votes

EDIT#2:

I've decided to try and batch the copy-paste operations that run after all the emails are fired. In previous posted code, in the last lines of every loop, I copied and pasted the items from each purchase order ("Email" sheet) to another ("AsignRec"). Now, what I want to do is store the items from "Email" sheet in every loop to a Javascript array, and paste all together into "AsignRec" at the end, just once.

However, I'm still not doing it right. I'm stuck at the final pasting/setValues(). I believe the array is correctly formed, as it has a length of 49, which is the number of unique SKUs to be sent out to suppliers. Still, at the setValues([OCitems]) line 185, I get the error "Incorrect range height, was 1 but should be 49 (line 185, file "TestArrayMultiple4")".

I assume this means the destination/output range is not the same size as array/input (called OCitems). I don't see why though, since I defined the length of the output range using OCitems.length. I am missing something, and not sure what.

This is the important bit of the code, and full code below. Same GDocs link as before, script file TestArrayMultiple4, lines 160-185. https://docs.google.com/spreadsheets/d/1yzvMTh0VYhRhiexNzQPIjTwz1FCMq4XnbpGvCF1FYu8/edit#gid=436022027

    /// Get Range we want to change to creating Javascript array and paste at end only

var OcNoHeader = sheet.getRange("B9:J" + MaxTableRow).getValues(); // get items to send to supplier from "Email Sheet"

// if supplier number is 1, create array "OCitems" by storing OcNoHeader. If not supplier #1, then append to existing array "OCitems"

  if(y == 1){
    var OCitems = OcNoHeader}
    else
      {for(j=0;j<OcNoHeader.length;j++){
      OCitems.push(OcNoHeader[j]);
  }}


  Logger.log(OCitems);
  Logger.log("OCitems length = " + OCitems.length)

  debugger;

i++; // after firing email, y+1 to go to next supplier
Logger.log("i++ IF =" + 1);

Logger.log("new i ELSE =" + 1)

debugger;

} // only do while x = max number of suppliers reached
  while (i < x);  

  sheet3.getRange(3, 3, OCitems.length, 9).setValues([OCitems]); // paste operation, NumRows set equal to length of array

=========================

EDIT#1: Worked on improving performance using getValues of several cells instead of doing individual getValue() several times over. Unfortunately, this hasn't reduced the execution time in a stable or noticeable manner (sometimes it finished before 6min, sometimes not).

Posting code below (you can access it in script file "TestArrayMultiple2" in new Sheet shared below):

Using Execution transcript, I see that although the execution time of the getValue() lines that were previously taking very long has basically been reduced to zero, other lines of code are now taking more time and killing the gain achieved from the batching other getValue().

There are still 4-5 "individual" getValue() on specific cells (much less than before), but I don't understand why they would take so long. So it seems even if I removed the remaining "individual" getValue(), if only one remained, it would take even longer.

Seems to me it has something to do with caching (and I'm sure I don't fully understand this concept), for the following reasons: 1) It is always the first getValue() in the loop which takes the longest. 2) I tried to go down a different route, by changing the code for the copy/paste operation which occurrs after all emails are sent (line 150 in "TestArrayMultiple2" script file). I basically try to create an array which gets fed with more data at every loop (append/push method) but doesn't paste within every loop - the idea is to paste all the data at the end, after finishing looping. I still don't have it right (this second script file is the last one, "TestArrayMultiple3"), but I can see the emails get fired off much faster.

Once again, your help would be much appreciated.

> // version with 1 getDataRange array which stores for supplier ID, name, email, MaxTableRow for PO email, email subject all from Dashboard sheet

function TestArrayMultiple2() {

  var ss = SpreadsheetApp.getActiveSpreadsheet ();
  var sheet = ss.getSheetByName("Email"); 
  var sheet1 = ss.getSheetByName("Pedido email");
  var sheet2 = ss.getSheetByName("Dashboard");
  var sheet3 = ss.getSheetByName("AsignRec");
  var sheet4 = ss.getSheetByName("ListadoProductos");
  var sheet5 = ss.getSheetByName("Registro-Consolid");
  var sheet6 = ss.getSheetByName("Registro-Unico");

  var x = ss.getSheetByName("Dashboard").getRange("C4").getValue();
  Logger.log("x = " + x)

  var offsetV = 4; // number of rows of offset for email status to be inserted in Dashboard sheet 

  var OffSetColProv = 1; // column in Dashboard sheet with supplier name
  var OffsetColMaxPOrows = 3; // Number of unique SKUs or rows in PO. Replace MaxTableRow formula in Email Sheet
  var OffSetColPzas = 4; // column in Dashboard sheet with number of items in supplier purchase order. 
  var OffSetColEmail = 5; // column in Dashboard sheet with supplier email
  var OffSetColCC = 13; // column in Dashboard sheet with supplier email CC
  var OffSetColSubject = 14; // column in Dashboard sheet with supplier email Subject

  var colStatus = 11; // column in Dashboard sheet where send status of email inserted -----> LEAVE AS IS FOR NOW, not an offset, is fixed, col. K = 11
  var OffsetEmailRows = 8 // number of rows in Email sheet before the items in PO are shown

  var ProvNumEmail = sheet.getRange(1,2); // Supplier number in email sheet used to refresh products in purchase order email via FILTER formula

  var StatusRange = sheet2.getRange("K5:K100");
  Logger.log("StatusRange = " + StatusRange)

  var currentTime =  new Date();
  var timestamp = Utilities.formatDate(currentTime,'GMT-0600','dd/MM/yyyy HH:mm:ss');
  Logger.log("timestamp = " + timestamp);

  var ProvArray = sheet2.getRange("E5:S100");
  var DashValues = ProvArray.getValues();

  i = 0;

  do {


  var y = DashValues[i][0];
  Logger.log("y = " + y)

  ProvNumEmail.setValue(y);   // set value of next supplier in Email sheet to load next purchase order products


    // emails var here in order to update email value in IF email = ERROR condition and skip to else

  var Prov = DashValues[i][OffSetColProv];
  Logger.log("Prov = " + Prov);

  var EmailSubject = DashValues[i][OffSetColSubject];
  Logger.log("EmailSubject = " + EmailSubject)

  var MaxTableRow = DashValues[i][OffsetColMaxPOrows] + OffsetEmailRows;
  Logger.log("MaxTableRow = " + MaxTableRow)

  var EmailTo = DashValues[i][OffSetColEmail];
  Logger.log("EmailTo = " + EmailTo)

  var EmailCC = DashValues[i][OffSetColCC];
  Logger.log("EmailCC = " + EmailCC)

  var Piezas = DashValues[i][OffSetColPzas];
  Logger.log("Piezas = " + Piezas)

  SpreadsheetApp.flush();

  var name = "Petsy Compras - Juan Carlos León";
  var ReplyToEmail = "[email protected]";
  var email = EmailTo;
  var subject = EmailSubject;
  var name = name;
  var replyTo = ReplyToEmail;
  var Emailcc = EmailCC;
  var schedRange = sheet.getRange("B7:J"+MaxTableRow);
  var body = '<div>';            
    body += "Estimados," +'<br>' + '<br>';
    body += "Envío la orden de compra, por un total de " + '<b>' + Piezas + " piezas." + '</b>' +'<br>' + '<br>';
    body += "Favor de confirmar las existencias lo más rápidamente posible, dentro del mismo correo y"+ '<b><a style="color:#FF0000">'+ " enviar factura a: "+ '</a></b>' + "[email protected]." +'<br>' + '<br>';
    body += "Al dar " +'<b><a style="color:#FF0000"> '+ "RESPONDER A TODOS" + '</a></b>' +" la tabla con los productos pedidos se hace editable: favor de marcar por cada item si será faltante." +'<br>' + '<br>';
    body += "Cualquier duda avísenme por favor." +'<br>' + '<br>';
    body += "Un saludo" +'<br>' + '<br>';
    body += '<b>'     + "Juan Carlos León" + '<b>' + '<br>';
    body += "Petsy Compras"+'<br>';
    body += "Mapa aquí: "+'<br>';
    body += "Fijo directo 1: (55) 68 12 07 97 / Fijo directo 2: (55) 68 12 07 99 / Cel y Whatsapp: 55 32 23 57 17"+'<br>' + '<br>';
    body += "" +'<br>' + '<br>';
    body += getHtmlTable(schedRange);
    body += '</div>';


  // variables for error email

  var emailERR = '[email protected]'
  var subjectERR = 'ERROR ENVIO OC' + ' // ' + Prov + ' ' + timestamp


  if(email == 'ERROR' || MaxTableRow == 0) // skip condition to go begin loop with y+1

{

// if above skip condition is true, y+1 to move to next purchase order

  Logger.log("y = " + y);
  Logger.log("IF");
  sheet2.getRange(y + offsetV,colStatus).setValue('NOT_SENT'); // set email send status next to supplier in Dashboard sheet
  {
     GmailApp.sendEmail(emailERR, subjectERR, "Requires HTML", 
                { 
                    'name':name, 
                    'replyTo':replyTo,
                    'htmlBody':'', 
                    'cc':''});
        }


  i++;

  Logger.log("new i IF =" + 1);
  continue

}
else
{

  // if skip condition is false, fire current supplier purchase order email email

  Logger.log("i = " + i);
  Logger.log("y = " + y);
  Logger.log("ELSE");

  GmailApp.sendEmail(email, subject, "Requires HTML", 
                { 
                    'name':name, 
                    'replyTo':replyTo,
                    'htmlBody':body, 
                    'cc':EmailCC});
        }        

sheet2.getRange(y + offsetV,colStatus).setValue('OK'); // set email send status next to supplier in Dashboard sheet



// START copy-paste Asign-Rec

var MaxTableRowASIGN = sheet3.getRange("A1").getValue();
Logger.log("MaxTableRowASIGN = " + MaxTableRowASIGN)


/// Get Range we want to change to creating Javascript array and paste at end only

  debugger; // stop debugger at this point !! REMOVE OR PLACE AT CORRECT LINE IF USING DEBUGGER

var OcNoHeader = sheet.getRange("B9:J" + MaxTableRow);
var ConsolAsignRec = sheet3.getRange("B3:K" + MaxTableRow);
var ProvOC = sheet.getRange("B2").getValue();
Logger.log("ProvOC = " + ProvOC)

var MaxRowB = sheet3.getRange("C1").getValue() + 1;
Logger.log("MaxRowB = " + MaxRowB);

var NextRowB = MaxRowB + 1;
Logger.log("NextRowB = " + NextRowB);

OcNoHeader.copyTo(sheet3.getRange(MaxTableRowASIGN + 1,3),{contentsOnly:true});
var NumRowsProv = OcNoHeader.getNumRows();

var ProvOCcolumn = sheet3.getRange(MaxRowB, 2, NumRowsProv)
Logger.log("ProvOCcolumn = " + ProvOCcolumn);

ProvOCcolumn.setValue(ProvOC);

// END copy-paste Asign-Rec

i++; // after firing email, y+1 to go to next supplier
Logger.log("i++ IF =" + 1);



Logger.log("new i ELSE =" + 1)



} // only do while x = max number of suppliers reached
  while (y<x);  

  // set y = 1 to reset value again after finishing loop 

  sheet.getRange(1,2).setValue(1); // reset ProvNumber = 1 to start again next time script is fired.

  var EmailsSent = sheet2.getRange("C10").getValue(); // set values
  Logger.log("EmailsSent = " + EmailsSent)

  var EmailErrors = sheet2.getRange("C11").getValue();
  Logger.log("EmailErrors = " + EmailErrors)

  var MaxTableRowEND = sheet2.getRange("C9").getValue();
  var schedRange = sheet2.getRange("E4:K" + MaxTableRowEND);
  var emailEND = "[email protected]";
  var subjectEND = 'OCs Inbound enviadas' + ' ' + timestamp + " (errores " +  EmailErrors + " / enviados " + EmailsSent + ")";
  var EmailCCEND = "";
  var bodyEND = getHtmlTable(schedRange);

   GmailApp.sendEmail(emailEND, subjectEND, "Requires HTML", 
                { 
                    'name':name, 
                    'replyTo':replyTo,
                    'htmlBody':bodyEND, 
                    'cc':EmailCCEND});

StatusRange.clearContent();


/// START RecordTimestamp code

  var Avals = sheet4.getRange("A1:A").getValues();
  var lastrow1 = Avals.filter(String).length;
  Logger.log('lastrow1 =' + lastrow1)

  var Avals2 = sheet5.getRange("A1:A").getValues();
  var lastrow2 = Avals2.filter(String).length;
  Logger.log('lastrow2 =' + lastrow2)

    sheet4.getRange("B2:B" + lastrow1).copyTo(sheet5.getRange(lastrow2 + 1, 1)) // copy order-items to Registro sheet, after last filled row

    sheet4.getRange("K2:K" + lastrow1).copyTo(sheet5.getRange(lastrow2 + 1, 2)) // copy Prov1 to Registro sheet, after last filled row


  var Avals3 = sheet5.getRange("C1:C").getValues();
  var lastrow2c = Avals3.filter(String).length;
  Logger.log('lastrow2c =' + lastrow2c);


  if(lastrow2 == 1)
  { sheet5.getRange(lastrow2c + 1, 3, lastrow1 - 1).setValue(timestamp)
    Logger.log('IF')

    }

    else
    {
    sheet5.getRange(lastrow2c + 1, 3, lastrow1 - 1).setValue(timestamp)
    Logger.log('ELSE')
    }

var MaxTableRowEMAIL = sheet6.getRange("G5").getValue()

var subject = "Items pedidos en OC automatizada " + timestamp
var email = "[email protected]";
var EmailCC = "";
var EmailBCC;
var name = "Petsy Compras";
var ReplyToEmail = "[email protected]"
var schedRange = sheet6.getRange("A1:C" + MaxTableRowEMAIL);
var body = getHtmlTable(schedRange);  


{
     GmailApp.sendEmail(email, subject, "Requires HTML", 
                { 
                    'name':name, 
                    'replyTo':ReplyToEmail,
                    'htmlBody':body, 
                    'cc':''});
        }

/// END RecordTimestamp code


Logger.log("MaxTableRowASIGN " + MaxTableRowASIGN);

var endtime = new Date();
Logger.log("timestamp end " + timestamp);
Logger.log("endtime " + Utilities.formatDate(endtime,'GMT-0600','dd/MM/yyyy HH:mm:ss'));

var scripttime = (endtime - currentTime);
Logger.log("scripttime original" + scripttime);

// strip the ms
scripttime /= 1000;
Logger.log("scripttime / 1000" + scripttime);

// get seconds (Original had 'round' which incorrectly counts 0:28, 0:29, 1:30 ... 1:59, 1:0)
var seconds = Math.round(scripttime % 60);
Logger.log("scripttime % 60" + scripttime);

// remove seconds from the date
scripttime = Math.floor(scripttime / 60);
Logger.log("scripttime / 60" + scripttime);

// Browser.msgBox("Script completado en " + seconds + " segundos",Browser.Buttons.OK_CANCEL); // removed MsgBox to measure real execution time
Logger.log("seconds " + seconds)

}  

=====================

ORIGINAL POST

I wrote a Google script to automate the purchase order process to several suppliers. The process takes a list of products (from sheet ListaProductos), formats the product info into an email format (sheet "Email"), sends the emails, and does some copy/pasting into other sheets of the same spreadsheet. However, I always run into the execution time at around 75% of the script. I'm fairly new to this, have been reading up but frankly don't know what to try next.

1

1 Answers

1
votes

I see the problem in this part of code:

do { 
  // read info from the sheet
  range.getValue();
  // more code here...

} // only do while x = max number of suppliers reached
  while (y<x)

Operation getValue takes much time to run. Best practice is to use the whole range:

var data = sheet.getDataRange().getValuses();

and then use data as source for further calculations.

See more info here:

https://developers.google.com/apps-script/best_practices