Google Form, prevent duplicate or overwrite contact - google-apps-script

excuse my English.
I'm a new user and I don't really understand the programming language very well! :-)
I need your precious help.
I'm dealing with a google form where I have to register customers to choose their birthday present.
Before writing I read a lot of posts here about the subject but I didn't find the solution to my problem. The closest is this: Overwriting Google sheets (for form response) rows if duplicate entered
I tried running the scripts I found but when I run them it gives me this problem:
TypeError: Could not call the "getSheetByName" method of null. (line 16, file "UPDATEcontact")
The sheet name is: compleanno2020
function updateExisting(columnWithUniqueIdentifier,sheetTabName) {
var dataFromColumnToMatch,lastColumn,lastRow,rowWithExistingUniqueValue,rowOfDataJustSaved,
sh,ss,valueToSearchFor;
// USER SETTINGS - if the values where not passed in to the function
if (!columnWithUniqueIdentifier) {//If you are not passing in the column number
columnWithUniqueIdentifier = 2;//Hard code column number if you want
}
if (!sheetTabName) {//The sheet tab name was not passed in to the function
sheetTabName = "compleanno2020";//Hard code if needed
}
//end of user settings
ss = SpreadsheetApp.getActiveSpreadsheet();//Get the active spreadsheet - this code must be in a project bound to spreadsheet
sh = ss.getSheetByName(sheetTabName);
lastRow = sh.getLastRow();
lastColumn = sh.getLastColumn();
//Logger.log('lastRow: ' + lastRow)
rowOfDataJustSaved = sh.getRange(lastRow, 1, 1, lastColumn).getValues();//Get the values that were just saved
valueToSearchFor = rowOfDataJustSaved[0][columnWithUniqueIdentifier-1];
//Logger.log('valueToSearchFor: ' + valueToSearchFor)
dataFromColumnToMatch = sh.getRange(1, columnWithUniqueIdentifier, lastRow-1, 1).getValues();
dataFromColumnToMatch = dataFromColumnToMatch.toString().split(",");
//Logger.log('dataFromColumnToMatch: ' + dataFromColumnToMatch)
rowWithExistingUniqueValue = dataFromColumnToMatch.indexOf(valueToSearchFor);
//Logger.log('rowWithExistingUniqueValue: ' + rowWithExistingUniqueValue)
if (rowWithExistingUniqueValue === -1) {//There is no existing data with the unique identifier
return;
}
sh.getRange(rowWithExistingUniqueValue + 1, 1, 1, rowOfDataJustSaved[0].length).setValues(rowOfDataJustSaved);
sh.deleteRow(lastRow);//delete the row that was at then end
}
What should I do:
I would like to have the customer register only once - without activating the login - or if it is not possible I would like to have the repeated data overwritten.
The variable to check is definitely the email address but if it were possible there should be a double check, or email address associated with the birthday date.
Below I attach my sample sheet.
my google-sheet
Do you have any solution?
I really hope so ... and I thank you in advance.
Thank you all.

Related

Have to run the script twice for it to fully execute and triggers are not working

Hi and thank you in advance for your help--I may need a lot of it!
When I manually run my code it does pull the targeted formulas down and populates the cells I intend so that's nice but then nothing else beyond that in the code gets executed. If afterwards I run my code a second time, the remaining code does what I want it to do--message the user about bad data entry.
Can someone help me understand why my code does not continue on and execute all the lines?
I can understand my script is heavy, so I've tried placing utilities.sleep(3000) delays before a get command (I have a few in my code) but that did not work. I have also tried to parse out this code into separate functions but then I got stuck calling two spreadsheet-sourced triggers one after the other and that did not work well either.
Also, triggers are not working with this code--the error message I get is "Exception: Cannot call SpreadsheetApp.getUi() from this context." This is odd because when I manually execute, this line and the ui.alert() works just fine. Any ideas what is happening here?
My code is lacking organization and efficiencies, I'm sure--sorry for the challenge when reviewing these lines.
here's my code
function fillCheckNotifyDataEntry(){
//Check to see if function ran via trigger
Logger.log('function was triggered and did run');
var spreadsheet = SpreadsheetApp.openById("1cUdqsI0J6HoCAAbxeC3vSZBvNCSNyZ4CG0D23iBNfkM");
var sheet = SpreadsheetApp.setActiveSheet(spreadsheet.getSheets()[0]);
//Scan the Data Entry Okay column to check for the most recently added cell value and store that row number as lastRow
var lastRow = sheet.getLastRow();
var rangeDataEntryOkay = sheet.getRange('L:L' +lastRow);
//Get the value for Data Entry Okay column's most recently added cell to later check if the data entered was good or bad
var checkDataEntryRange = sheet.getRange('L' +lastRow);
var checkDataEntry = checkDataEntryRange.getValue();
//Get the details from the most recent data entry to the sheet
var rowOfInterestTime = sheet.getRange('B'+lastRow).getValue();
var rowOfInterestEmail = sheet.getRange('I'+lastRow).getValue();
var rowOfInterestTestedWhat = sheet.getRange('C'+lastRow).getValue();
var rowOfInterestFailedInspection = sheet.getRange('E'+lastRow).getValue();
var rowOfInterestFailedTest = sheet.getRange('F'+lastRow).getValue();
var rowOfInterestTotalAccepted = sheet.getRange('G'+lastRow).getValue();
var rowOfInterestTotalTested = sheet.getRange('H'+lastRow).getValue();
//Calculate the expected total number tested by adding all the failures with the number accepted
var rowOfInterestTotalExpected = (rowOfInterestFailedInspection + rowOfInterestFailedTest + rowOfInterestTotalAccepted);
//Triggers a pop-up window to appear with a message to the user
var ui = SpreadsheetApp.getUi();
//Pull the formulas in the columns Total Defects, Yield, and Data Entry Okay? down one row to populate that next row based on the lastRow value
spreadsheet.getRange('J3:L3').activate();
//Scan each successive row in column L starting with L3 to find the last one that contains a value and return that row number
if (rangeDataEntryOkay.getValue() !== '') {
//Check to see if that Data Entry Okay cell is bad and if so kick out a message to the user
if (checkDataEntry == 'bad') {
ui.alert('Email ' +rowOfInterestEmail+ ' and share that Data Entry per Form 851-1 CCA/Device Test Checklist may require your review because your submission shows:\n\nOn ' +rowOfInterestTime+ ', when you tested ' +rowOfInterestTestedWhat+ ', you declared ' +rowOfInterestFailedInspection+ ' failed inspection, ' +rowOfInterestFailedTest+ ' failed test and ' +rowOfInterestTotalAccepted+ ' were accepted but you declared that the total number tested was ' +rowOfInterestTotalTested+ '.\nWe were expecting the total number tested to be ' +rowOfInterestTotalExpected+ '.\n\nPlease resubmit the form to update these numbers at your earliest convenience--thank you.')
}
} else {
sheet.getActiveRange().autoFill(spreadsheet.getRange('J3:L' +lastRow), SpreadsheetApp.AutoFillSeries.DEFAULT_SERIES);
return rangeDataEntryOkay.getNextDataCell(SpreadsheetApp.Direction.UP).getRow();
}
}

onSubmit Trigger Executed Without Form Submission

I created a Google Form, linked to a Sheet to capture responses, and added an Apps Script that runs each time the Form is submitted. Ran through a bunch of tests and everything was working fine - form responses fed through, onSubmit function working great. Last night, though, we received a few executions of the script even though the Form was not submitted.
Looking at the Responses page on the Form itself, there were no submissions when I got the notification. Also, this is not a public Form in my organization, and only one other person has the link besides myself. He confirmed he didn't submit the form at the time of the executions.
There are two sheets in the Google Sheet: 1> Form Responses and 2> Data. The data sheet uses a few QUERY functions to pull data from the responses sheet, formatting it differently (e.g. putting hyphens in phone numbers, rendering some fields in upper case, etc.). Also, the data sheet headers are labeled differently than the Form questions (e.g. 'homeAdd1' instead of 'Home Address Line 1'). This is because the script creates a PDF, using the Form responses to replace placeholders ('%homeAdd1%') on a template Google Doc. The script then takes the generated PDF and emails it to the submitter.
Again, everything was working great until yesterday's testing. I didn't realize it at the time, but when my colleague was inputting random values to test the Form, for the Home Address Line 2 he only input a 5-digit ZIP code. It generated a PDF fine, and also emailed it to him, but this caused the QUERY function to render a #VALUE error. The functions look like this:
=QUERY(Responses!L2:S,"SELECT UPPER(L) UPPER(M)...
So when Sheets saw a cell with just 5 digits, it automatically rendered it as a number, and UPPER doesn't work on number values. I (stupidly) didn't think to pre-format all of both sheets as plain text, so this occurred.
Would a #VALUE error on a Google Sheet linked to a Form and an Apps Script cause a misfire of the onSubmit function? This is the only thing I can see that could have possibly caused it, but it doesn't make sense. I've fixed the formatting issue, but I don't know if an erroneous execution could mean some other issue.
With the extra submissions, the script just sent the most recent PDF again and again. Within 20 seconds, it fired 5 times, sending the last PDF that was generated via email each time. Looking at the Stackdriver logs, there's nothing different from when we were testing it earlier yesterday. The console.log and console.info commands work fine, and they all come through listed as having been triggered by the onSubmit function.
Here's the script:
Submit function:
function onSubmit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Data');
var list = ss.getRange("A1:A").getValues();
var row = list.filter(String).length;
var email = ss.getRange(row,2).getValue();
var newResponse = ss.getRange(row,3).getValue();
if (newResponse == 'Generate New') {
newOne(ss,row,email);
} else if (newResponse == 'Upload Completed') {
completed(ss,row,email);
} else {
}
}
Function that was executed:
function newOne(ss,row,email) {
var name = ss.getRange(row,4).getValue();
console.log('Function Start - ' + name);
var newType = ss.getRange(row,6).getValue();
var copyFile = DriveApp.getFileById('[file id]').makeCopy();
var copyDoc = DocumentApp.openById(copyFile.getId());
var copyBody = copyDoc.getActiveSection();
// Replacing variables with values on spreadsheet
console.log('Create file start - ' + name);
var newInfo = ss.getRange(row, 1, 1, 29).getDisplayValues();
var header = ss.getRange(1, 1, 1, 29).getDisplayValues();
for (var i = 1; i <= 5; i++) {
copyBody.replaceText('%' + header[0][i] + '%', newInfo[0][i].toString());
}
var x;
if (newType == 'Office 1') {
x = 6;
} else if (newType == 'Office 2') {
x = 15;
} else {
}
for (var i = x; i <= (x + 8); i++) {
copyBody.replaceText('%' + header[0][i] + '%', newInfo[0][i].toString());
}
copyBody.replaceText('%' + header[0][26] + '%', newInfo[0][26].toString());
// Create the PDF file, rename it, and delete the doc copy
copyDoc.saveAndClose();
var newFile = DriveApp.createFile(copyFile.getAs('application/pdf'));
newFile.setName('New - ' + name + '.pdf');
copyFile.setTrashed(true);
console.log('Create file finished - ' + name);
//Mails PDF to submitter
console.info('Pre-email log for ' + name);
MailApp.sendEmail(email,'Email Subject','', {
noReply: true,
htmlBody: "<body>Hello, and thank you.</body>",
attachments: [newFile]
});
console.info('Email sent for ' + name);
appFile.setTrashed(true);
}
Any insight / help would be appreciated; thanks!
Josh
Spurious unwanted Event Triggers
I've had problems with spurious triggers coming from onFormSubmit event triggers. In my case they were always immediately after a real trigger occurred from a Form Submission. I found that I could identify them because none of my required questions were answered. I discuss it here.
It might be worth your time to capture the e.values array and see if you can find a consistent way to keep them from causing a misfire of your processing function.
As far as I know, onSubmit(e) doesn't work the way you're expecting it to.
I think what you're looking for is an onFormSubmit trigger, try using the following from Class SpreadsheetTriggerBuilder documentation to create a script trigger that executes every time someone submits a response to your linked form:
var sheet = SpreadsheetApp.getActive();
ScriptApp.newTrigger("function name")
.forSpreadsheet(sheet)
.onFormSubmit()
.create();
I have an installation of a spreadsheet and corresponding form where I get frequent duplicate on form submit events, inexplicably. It does not occur in other installations. If this is your situation you can't just check if the event is null because to test it for null you have to have something to test. If it's undefined you will get an error. So first test if it's undefined. Try this code:
`function formSubmitted(e) {
// Deal with the unusual case that this is a bogus event
if ((typeof e === "undefined") || (e == null) || (e.length == 0)) {
Logger.log("formSubmitted() received a bogus or empty event");
return;
}
...`

Overwriting Google sheets (for form response) rows if duplicate entered

So, I've been trying to figure out how to stop the duplicate rows appearing in my google sheets response output from a google form. If found this link which sounds like it does exactly what I want (Form Google Script Prevent Duplicates), but cannot for the life of me work out how to edit the given answer to work on my sheet.
I have included a screenshot of my workbook to give an example of the structure of the data I'd like the edited code to run on, and also below is my attempt at making the code run correctly on my data structure.
My sheet structure that I'd like to run the code on. I want to use the email address as the 'unique' identifier, so any duplicate rows can be identified using that.
My attempt at adapting the code to work on the above data structure (I have absolutely no background with this scripting language, so please go easy on me if I've made a glaringly obvious error):
function updateExisting() {
var s = SpreadsheetApp.getActiveSheet(),
// s = ss.getSheetByName(''),
lastRow = s.getLastRow(),
lastValues = s.getRange('A'+lastRow+':C'+lastRow).getValues(),
name = lastValues[0][0],
allNames = s.getRange('B2:B').getValues(),
row, len;
// TRY AND FIND EXISTING NAME
for (row = 0, len = allNames.length; row < len - 1; row++)
if (allNames[row][0] == name) {
// OVERWRITE OLD DATA
s.getRange('A2').offset(0, 0, row,
lastValues.length).setValues([lastValues]);
// DELETE THE LAST ROW
s.deleteRow(lastRow);
break;}
}
Key words: duplicates, Google, spreadsheet, Sheets, Form, submission, edit, row, unique.
This code prevents duplicates in a Google Sheet when submitting a Google Form, by overwriting an existing row with the existing unique value, if one exists.
The code searches one column in a spreadsheet and looks for a match. I tried to make it generic so that the code doesn't need to be changed depending upon what column the unique identifier is in. You need to make a couple of settings in the "User Settings" section to make it work. But that is better than needing to rewrite the code.
function updateExisting(columnWithUniqueIdentifier,sheetTabName) {
var dataFromColumnToMatch,lastColumn,lastRow,rowWithExistingUniqueValue,rowOfDataJustSaved,
sh,ss,valueToSearchFor;
// USER SETTINGS - if the values where not passed in to the function
if (!columnWithUniqueIdentifier) {//If you are not passing in the column number
columnWithUniqueIdentifier = 2;//Hard code column number if you want
}
if (!sheetTabName) {//The sheet tab name was not passed in to the function
sheetTabName = "Put your Sheet tab name here";//Hard code if needed
}
//end of user settings
ss = SpreadsheetApp.getActiveSpreadsheet();//Get the active spreadsheet - this code must be in a project bound to spreadsheet
sh = ss.getSheetByName(sheetTabName);
lastRow = sh.getLastRow();
lastColumn = sh.getLastColumn();
//Logger.log('lastRow: ' + lastRow)
rowOfDataJustSaved = sh.getRange(lastRow, 1, 1, lastColumn).getValues();//Get the values that were just saved
valueToSearchFor = rowOfDataJustSaved[0][columnWithUniqueIdentifier-1];
//Logger.log('valueToSearchFor: ' + valueToSearchFor)
dataFromColumnToMatch = sh.getRange(1, columnWithUniqueIdentifier, lastRow-1, 1).getValues();
dataFromColumnToMatch = dataFromColumnToMatch.toString().split(",");
//Logger.log('dataFromColumnToMatch: ' + dataFromColumnToMatch)
rowWithExistingUniqueValue = dataFromColumnToMatch.indexOf(valueToSearchFor);
//Logger.log('rowWithExistingUniqueValue: ' + rowWithExistingUniqueValue)
if (rowWithExistingUniqueValue === -1) {//There is no existing data with the unique identifier
return;
}
sh.getRange(rowWithExistingUniqueValue + 1, 1, 1, rowOfDataJustSaved[0].length).setValues(rowOfDataJustSaved);
sh.deleteRow(lastRow);//delete the row that was at then end
}

Sending Email Notification Google Sheets

My company has created a Google Form set up to make one of our processes a lot easier. The Google Form is based off of the Master Spreadsheet that contains all of the data inputted from the Form. This spreadsheet then filters out the the form submission and sends the data to each department’s spreadsheet, which as previously stated before, gets all of the information from the "Master Spreadsheet."
We previously had it set up so when employees would go in and approve or deny these requests in their spreadsheet, we would receive an email notification if someone entered "Approved" or "Denied." Recently we changed it so if a certain person submitted a request for a customer, it would be automatically approved, but when we did this the email notification stopped working because no one is manually entering in "Approved" or "Denied" for these requests. It still works when it's manually typed in, but when the cell is automatically filled in, the sendNotification does not work.
Since no actual data is being input into the individual department sheets, we wanted to put the notification trigger on the "Master Sheet," but we are having a heck of a time getting the email notification to send. Basically we want it so if any cell in "Column F" contains a certain list of email addresses it will send an email to a third party notifying them to actually go ahead and make the changes.
Here is what we have so far. Keep in mind this is the code that worked originally. I've tried many different variations of things and have had no luck whatsoever, but I'm not the most educated coder:
function sendNotification() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Form Responses 3");
//Get Active cell
var mycell = ss.getActiveSelection();
var cellcol = mycell.getColumn();
var cellrow = mycell.getRow();
var cellValue = mycell.getValue();
var activeUser = Session.getActiveUser();
var recipients = "xxxx#xxxxxxxxxx.com";
var subject = "Update to "+ss.getName();
var body = activeUser + " has marked row " + cellrow + " \"" + cellValue + "\" in \"" + ss.getName() + "\". Visit " + ss.getUrl() + " to view the changes.";
if (cellcol == 2) {
if (cellValue.indexOf('test1#test.com') >= 0) {
var subject = "Lunch Is Served";
Logger.log('Sending approval notification of row ' + cellrow + ' to ' + recipients);
MailApp.sendEmail(recipients, subject, body);
}
}
}
Please keep in mind that we can't use lastRowNumber (at least I didn't think we could) because we already have over one thousand rows listed so the information will fill in to the array automatically.Lastly, our current trigger is set to "On Form Submission" because we want these emails to come in as the forms are submitted.
I have included a sample spreadsheet for you guys to look at. Please use test1#test.com as your email address when completing the form.
The Google Sheet can be found at the following site:
Test Sheet!
Thank you so much and I look forward to reading your responses!
You can't use the line:
var mycell = ss.getActiveSelection();
If that function is running from an "On Form Submit" trigger, there is no active selection. Although, there is a property available to the "On Form Submit" event object that gives the currently edited range. You must get the event object from the form submission. Then you have 3 options. 1) Just get the values 2) Get an object of questions and their values 3) Get the range of the range edited. First, you get the event object that is passed into the function. When the form is submitted, data is automatically made available to the function associated with the On Form Submit trigger. The letter e is typically used as the variable name to get the event object:
function sendNotification(e) {
But you can use any variable name:
function sendNotification(objOfData) {
Apps Script Documentation - Spreadsheet - On Form Submit
function sendNotification(e) {
var cellValue = e.values[4];//Get the value in column 5

GAS - Ranged Cells Protection

I noticed there is quite a number of questions here regarding protection on cells in a spreadsheet.
But there seems to be no viable solution.
For example, column 'A' can only be edited by person1#email.com, and column 'B' can only be edited by person2#email.com.
There seems to be an issue tracker on google site since 2013...but Google has not come up with an API for it yet.
Does anyone have a workaround?
The code below only works for entire page protection..
sheet.setSheetProtection(permissions);
Use an onEdit() function that checks what user is editing the Sheet, then check what column is being edited. Have an object of user names, and what columns they can edit. If a user is not allowed to edit, undo the change.
You can only undo the change if you have a way of knowing what the last cell value was. There is no undo method in Apps Script, or other built in way to get the old value with Apps Script. But there is a way to configure the data to achieve a way to undo the edit.
Have a central sheet with all formulas referring to other sheets. In other words, the data that people view is a copy of the stored data in another sheet. Divide the data into sheets according to who can edit what. The code will write data to the correct sheet when a cell is edited.
Basically, you would have sheets that are the database where the data is stored. Those sheets could even be hidden, and of course they would be protected.
The viewing and editing would be done in a separate sheet from the sheets that are the official data storage.
So, the sheet that people are viewing and editing is the "User Interface"; it's the "Front End" of the "App". The sheets that are the official data storage are the "Back End".
function onEdit(e){
Logger.log("e.value: " + e.value);
Logger.log("e.range.getRow: " + e.range.getRow());
Logger.log("e.range.getColumn: " + e.range.getColumn());
var objWhoCanEditWhat = {"user1":"[A,B]", "user2":"[A]"};
//Get this user
var thisUserIs = Session.getActiveUser().getEmail();
Logger.log('thisUserIs: ' + thisUserIs);
Logger.log('Index of #: ' + thisUserIs.indexOf("#"));
thisUserIs = thisUserIs.substring(0, thisUserIs.indexOf("#"));
Logger.log('thisUserIs: ' + thisUserIs);
var whatColumnCanEdit = objWhoCanEditWhat[thisUserIs];
Logger.log('whatColumnCanEdit: ' + whatColumnCanEdit);
var editedColumn = e.range.getColumn();
var editedRow = e.range.getRow();
Logger.log('editedColumn: ' + editedColumn)
var ss = SpreadsheetApp.getActiveSpreadsheet();
//There must be a way to determine what sheet needs to be accessed, and that sheet name
//is set dynamically.
var objColumnEditedToSheetName = {"ColA":"Sheet6TY", "ColB":"SheetColumnB"};
var whatSheetToUse = objColumnEditedToSheetName[editedColumn];
if (whatColumnCanEdit != editedColumn) { //If the column this user can edit is not the same as
//the column that just was edited, then
//Undo the change with this code
//Retrieve the old official data from the data storage sheet
var sheet = ss.getSheetByName(whatSheetToUse);
} else {
//If the user is allowed to edit this column, write the data to the official data storage sheet
var sheet = ss.getSheetByName(whatSheetToUse);
};
//Always put a formula back into the cell that was just edited in order
//to show data from the back end data source
var viewSheet = ss.getSheetByName("SheetForEditingAndViewing");
//You know the row and column of the cell that was just edited, so use that to
//reference what cell to put the formula back into.
viewSheet.getRange(editedRow, editedColumn).setFormula("Sheet1!A3");
};