FrontApp is the first inbox for teams. Organize all your conversations in one place, route them to the right people, and get more done as a team. A custom app is created for FrontApp were the user can add the customer in NetSuite from the FrontApp screen. User can see the corresponding customer details when an email is open. If the email is not related to the customer then there is a provision to create the customer in NetSuite.
SuiteLet
/**
* @NApiVersion 2.x
* @NScriptType Suitelet
* @NModuleScope SameAccount
*/
/**
* Script Description
* This suitelet is to load HTML and recive post request
* HTML consists A form
*/
/*******************************************************************************
*
* Support Files
*
* *****************************************************************************
*
*
*
* $Author: Jobin & Jismi IT Services LLP $
*
* DESCRIPTION
* Frontapp Integereation
*
* REVISION HISTORY Update:
*
*
*
******************************************************************************/
define(['N/file', 'N/url', 'N/search', 'N/runtime', 'N/record', 'N/https'],
function paypal(file, url, search, runtime, record, https) {
var main = {
attachMessage: function(type, transactionId, transactionType, frontObj) {
var messageObj = record.create({ type: "message" });
var authorId = main.findAuthor(frontObj.message.from.handle);
log.debug("authorId", authorId);
if (authorId == false) {
//return "No Author Found"
} else {
messageObj.setValue({ fieldId: "author", value: authorId });
}
// var recipientId = main.findRecipient(frontObj.message.recipients[1].handle);
for (var i = 1; i < frontObj.message.recipients.length; i++) {
var recipientId = main.findRecipient(frontObj.message.recipients[i].handle);
if (recipientId != false) {
break;
}
}
log.debug("recipientId", recipientId);
if (recipientId == false) {
//return "No Recipient Found"
messageObj.setValue({ fieldId: "recipientemail", value: frontObj.message.recipients[1].handle });
} else {
messageObj.setValue({ fieldId: "recipient", value: recipientId });
}
messageObj.setValue({ fieldId: "subject", value: frontObj.message.subject });
messageObj.setValue({ fieldId: "message", value: frontObj.message.body });
log.debug("transactionId", transactionId);
if (transactionType != "customerattach")
messageObj.setValue({ fieldId: "transaction", value: transactionId });
var messageId = messageObj.save();
log.debug("messageId", messageId);
return messageId;
},
findAuthor: function(email) {
log.debug("findAuthor", email);
var customerObj = main.findCustomer(email);
if (customerObj == false) {
var empId = main.findEmployee(email);
return empId
} else {
return customerObj.internalid
}
return false;
},
findRecipient: function(email) {
log.debug("findRecipient", email);
var empId = main.findEmployee(email);
if (empId == false) {
var customerObj = main.findCustomer(email);
if (customerObj == false) {
return false
}
return customerObj.internalid;
} else {
return empId;
}
return false;
},
createCustomer: function(data) {
var customerEmail = data.message.from.handle;
var customerName = data.message.from.display_name;
var recordObj = record.create({ type: "customer" });
recordObj.setValue({ fieldId: "email", value: customerEmail });
recordObj.setValue({ fieldId: "firstname", value: customerName });
recordObj.setValue({ fieldId: "category", value: 7 });
var recId = recordObj.save({
enableSourcing: true,
ignoreMandatoryFields: true
});
log.debug("customerCreated", recId);
return customerEmail;
// recordObj.setValue({ fieldId: "firstname", customerName.split(" ",)[0] });
},
onRequest: function(context) {
if (context.request.method === 'POST') {
var customer = context.request.parameters.customer;
var type = context.request.parameters.type;
var transactionId = context.request.parameters.internal;
var transactionType = context.request.parameters.tranType;
var data = context.request.parameters.data;
var searchKey = context.request.parameters.search;
log.debug("data", data);
if (type == "primarySearch") {
data = JSON.parse(data);
var fileid = 3197;
var newhtml = main.gethtml(fileid, customer, data);
context.response.write(newhtml);
} else if (type == "message") {
data = JSON.parse(data);
var response = main.attachMessage("message", transactionId, transactionType, data);
if (response) {
context.response.write("Message Attached To the Record");
} else {
context.response.write("Couldnt Find Enough Netsuite Records Related");
}
} else if (type == "createcustomer") {
data = JSON.parse(data);
var fileid = 3197;
var customer = main.createCustomer(data);
var newhtml = main.gethtml(fileid, customer, data);
context.response.write(newhtml);
} else if (type == "recordSearch") {
var customer = main.recordSearch(searchKey);
var newhtml = main.getSearchhtml(customer);
log.debug("newhtml", newhtml);
context.response.write(newhtml);
}
} else {
var customer = context.request.parameters.customer;
if (customer == "" || customer == undefined) {
var fileid = 3199;
var fileObj = file.load({
id: fileid
});
if (fileObj.size < 10485760) {
var newhtml = fileObj.getContents();
}
}
context.response.write({
output: newhtml
});
}
},
gethtml: function(fileid, customer, data) {
var fileObj = file.load({
id: fileid
});
if (fileObj.size < 10485760) {
var html = fileObj.getContents();
}
/*Check For primary email*/
var customerObj = main.findCustomer(customer);
if (customerObj == false) {
/*Check For recipent email*/
for (var i = 0; i < data.message.recipients.length; i++) {
var customerObj = main.findCustomer(data.message.recipients[i].handle);
if (customerObj != false) {
break;
}
}
/*No Customer found in both primary and recipient emails*/
if (customerObj == false) {
var newhtml = main.getTemplatehtml("", false, html);
} else {
var newhtml = main.getTemplatehtml(customerObj, true, html);
}
} else {
var newhtml = main.getTemplatehtml(customerObj, true, html);
}
return newhtml;
},
getTemplatehtml: function(customerObj, status, html) {
log.debug("customerObj", customerObj);
if (status == false) {
var customerRow = '<tr> <td colspan="3"> <div class="alert alert-primary alert-dismissible fade show" role="alert" style=" text-align: center; "> No Results <button type="button" class="close" data-dismiss="alert" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> </td> </tr>';
var newhtml = html.replace("ReplacewithCustomerROW", customerRow);
newhtml = newhtml.replace("Replacewithcustnum", 0);
newhtml = newhtml.replace(/ReplacewithNum/g, '0').replace("ReplacewithSearchResults", " ");
} else {
var customerRow = '<tr> <td> <input type="radio" align="center" value="ReplacewithCustomerID" class="customerattach" onchange="checkboxEvent(this)" id="checkbox" /> </td> <td> <div> <div class="lead" style=" font-weight: 600; ">*ReplacewithCustomer*</div> <div>*Replacewithemail*</div> </div> </td> <td> <div> <a href="javascript:Front.openUrl(replaceurl);"> <img align="center" src="https://system.na3.netsuite.com/core/media/media.nl?id=3829&c=5210448&h=9c99f454f63b3ae8469c" style="width:20px;height:20px;border:0;" /> </a> </div> </td> </tr> <tr> <td colspan="3" style=" color: grey; font-weight: 400; font-size: small; "> CUSTOMER NOTES</td> </tr> <tr> <td colspan="3" style=" font-size: small; "> ReplacewithCustomercomments</td> </tr>'
var newhtml = html.replace("ReplacewithCustomerROW", customerRow);
newhtml = newhtml.replace("*ReplacewithCustomer*", customerObj.name);
newhtml = newhtml.replace("ReplacewithCustomercomments", customerObj.comments);
newhtml = newhtml.replace("Replacewithcustnum", 1);
newhtml = newhtml.replace(/ReplacewithCustomerID/g, customerObj.internalid);
newhtml = newhtml.replace("*Replacewithemail*", customerObj.email);
newhtml = newhtml.replace("replaceurl", "'https://system.na3.netsuite.com/app/common/entity/custjob.nl?id=" + customerObj.internalid + "&whence='");
newhtml = main.createRelated(newhtml, customerObj);
}
return newhtml;
},
createRelated: function(newhtml, customerObj) {
var relatedRecords = customerObj.related;
var count = 0;
var tablecontents = "";
for (var key in relatedRecords) {
if (key != "") {
// var rowTag = '<tr height="10" style="border-bottom-style: solid; border-bottom-color: darkgray;"><td height="30" class="addressheader"> <input type="checkbox" value="Replacewithinternal" class="ReplacewithType" onchange="checkboxEvent(this)" id="checkbox" /> <b> ReplacewithTranid </b><a href="javascript:Front.openUrl(replaceurl);"><img align="center" src="https://system.na3.netsuite.com/core/media/media.nl?id=3829&c=5210448&h=9c99f454f63b3ae8469c"style="width:30px;height:30px;border:0;"></a></td></tr>'
var rowTag = '<tr> <td> <input type="radio" value="Replacewithinternal" class="ReplacewithType" onchange="checkboxEvent(this)" id="checkbox"> </td> <td> <div class="search-results"> <div class="" style="font-weight: 600; "> ReplacewithTranid </div> <div>Replacewithtrantype | Replacewithtrancustomer | Replacewithtrandate</div> </div> </td> <td> <div> <a href="javascript:Front.openUrl(replaceurl);"><img align="center" src="https://system.na3.netsuite.com/core/media/media.nl?id=3829&c=5210448&h=9c99f454f63b3ae8469c" style="width:20px;height:20px;border:0;"> </a> </div> </td> </tr>'
count = count + 1;
rowTag = rowTag.replace("ReplacewithTranid", key);
var type = relatedRecords[key].type.toLowerCase();
rowTag = rowTag.replace("replaceurl", "'https://system.na3.netsuite.com/app/accounting/transactions/" + type + ".nl?id=" + relatedRecords[key].internalid + "&whence='");
rowTag = rowTag.replace("Replacewithinternal", relatedRecords[key].internalid);
rowTag = rowTag.replace("ReplacewithType", relatedRecords[key].type);
rowTag = rowTag.replace("Replacewithtrantype", relatedRecords[key].type);
rowTag = rowTag.replace("Replacewithtrancustomer", customerObj.name);
rowTag = rowTag.replace("Replacewithtrandate", relatedRecords[key].trandate);
tablecontents += rowTag;
} else {
var rowTag = '<tr> <td colspan="3"> <div class="alert alert-primary alert-dismissible fade show" role="alert" style=" text-align: center; "> No Results <button type="button" class="close" data-dismiss="alert" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> </td> </tr>'
tablecontents += rowTag;
}
}
newhtml = newhtml.replace("ReplacewithSearchResults", tablecontents);
newhtml = newhtml.replace(/ReplacewithNum/g, count);
return newhtml;
},
findCustomer: function(customer) {
var customerSearchObj = search.create({
type: "customer",
filters: [
["email", "is", customer]
],
columns: [
search.createColumn({
name: "companyname",
sort: search.Sort.ASC,
label: "name"
}),
search.createColumn({ name: "comments", label: "comments" }),
search.createColumn({
name: "tranid",
join: "transaction",
label: "tranid"
}),
search.createColumn({
name: "internalid",
join: "transaction",
label: "internalid"
}),
search.createColumn({
name: "type",
join: "transaction",
label: "Type"
}),
search.createColumn({ name: "internalid", label: "internalid" }),
search.createColumn({ name: "email", label: "email" }),
search.createColumn({
name: "trandate",
join: "transaction",
label: "trandate"
})
]
});
var searchResultCount = customerSearchObj.runPaged().count;
var columns = customerSearchObj.columns;
log.debug("Customer Found", searchResultCount);
if (searchResultCount < 1) {
return false;
}
var dataObj = {};
dataObj["related"] = {}
customerSearchObj.run().each(function(result) {
dataObj["name"] = result.getValue(columns[0]);
dataObj["internalid"] = result.getValue(columns[5]);
dataObj["email"] = result.getValue(columns[6]);
dataObj["comments"] = result.getValue(columns[1]).substring(0, 120);
dataObj["related"][result.getValue(columns[2])] = {};
dataObj["related"][result.getValue(columns[2])]["internalid"] = result.getValue(columns[3]);
dataObj["related"][result.getValue(columns[2])]["type"] = result.getValue(columns[4]);
dataObj["related"][result.getValue(columns[2])]["trandate"] = result.getValue(columns[7]);
return true;
});
return dataObj;
},
findEmployee: function(email) {
var employeeSearchObj = search.create({
type: "employee",
filters: [
["email", "is", email]
],
columns: [
search.createColumn({ name: "internalid", label: "Internal ID" })
]
});
var searchResultCount = employeeSearchObj.runPaged().count;
log.debug("employeeSearchObj result count", searchResultCount);
if (searchResultCount < 1) {
return false
}
var internalid;
employeeSearchObj.run().each(function(result) {
internalid = result.getValue({ name: "internalid", label: "Internal ID" })
return true;
});
return internalid;
},
recordSearch: function(searchKey) {
var transactionSearchObj = search.create({
type: "transaction",
filters: [
["numbertext", "haskeywords", searchKey],
"AND",
["mainline", "is", "T"]
],
columns: [
search.createColumn({ name: "internalid", label: "Internal ID" }),
search.createColumn({ name: "tranid", label: "Document Number" }),
search.createColumn({ name: "type", label: "Type" }),
search.createColumn({ name: "trandate", label: "Date" }),
search.createColumn({ name: "trandate", label: "Date" }),
search.createColumn({
name: "entityid",
join: "customerMain",
label: "Name"
})
]
});
var searchResultCount = transactionSearchObj.runPaged().count;
log.debug("transactionSearchObj result count", searchResultCount);
var tranObj = {};
transactionSearchObj.run().each(function(result) {
tranObj.internalid = result.getValue({ name: "internalid", label: "Internal ID" });
tranObj.tranid = result.getValue({ name: "tranid", label: "Document Number" });
tranObj.type = result.getValue({ name: "type", label: "Type" });
tranObj.trandate = result.getValue({ name: "trandate", label: "Date" });
tranObj.customer = result.getValue({
name: "entityid",
join: "customerMain",
label: "Name"
});
// .run().each has a limit of 4,000 results
return true;
});
return tranObj;
},
getSearchhtml: function(customerObj) {
var rowTag = '<tr> <td> <input type="radio" value="Replacewithinternal" class="ReplacewithType" onchange="checkboxEvent(this)" id="checkbox"> </td> <td> <div> <div class="" style=" font-weight: 600; "> ReplacewithTranid </div> <div>Replacewithtrantype | Replacewithtrancustomer | Replacewithtrandate</div> </div> </td> <td> <div> <a href="javascript:Front.openUrl(replaceurl);"><img align="center" src="https://system.na3.netsuite.com/core/media/media.nl?id=3829&c=5210448&h=9c99f454f63b3ae8469c" style="width:20px;height:20px;border:0;"> </a> </div> </td> </tr>'
rowTag = rowTag.replace("ReplacewithTranid", customerObj.tranid);
var type = customerObj.type.toLowerCase();
rowTag = rowTag.replace("replaceurl", "'https://system.na3.netsuite.com/app/accounting/transactions/" + type + ".nl?id=" + customerObj.internalid + "&whence='");
rowTag = rowTag.replace("Replacewithinternal", customerObj.internalid);
rowTag = rowTag.replace("ReplacewithType", customerObj.type);
rowTag = rowTag.replace("Replacewithtrantype", customerObj.type);
rowTag = rowTag.replace("Replacewithtrancustomer", customerObj.customer);
rowTag = rowTag.replace("Replacewithtrandate", customerObj.trandate);
return rowTag;
}
}
for (var key in main) {
if (typeof main[key] === 'function') {
main[key] = trycatch(main[key], key);
}
}
function trycatch(myfunction, key) {
return function() {
try {
return myfunction.apply(this, arguments);
} catch (e) {
log.debug("e in " + key, e);
}
}
};
return main;
});
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.loader {
margin-top: 20px;
border: 10px solid #f3f3f3;
border-radius: 50%;
border-top: 10px solid #D50C8C;
width: 100px;
height:100px;
-webkit-animation: spin 2s linear infinite;
/* Safari */
animation: spin 2s linear infinite;
}
.btn-link, .btn-link:hover, .btn-link:active {
color: #D50C8C !important;
text-decoration: none !important;
}
.card-header {
padding: 0 !important;
}
.card {
border-radius: 0 !important;
border 0;
}
.search-results {
font-size: 14px;
}
/* Safari */
@-webkit-keyframes spin {
0% {
-webkit-transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
}
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
</head>
<body>
<script src="https://dl.frontapp.com/libs/frontjs.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
var currentMessage;
/*When Page Simply Loads*/
Front.on('panel_visible', function(visible) {
$('#mainfront').html(' <div align="center"> <div class="loader"></div> </div> ');
$.post("https://forms.na3.netsuite.com/app/site/hosting/scriptlet.nl?script=422&deploy=1&compid=5210448&h=a7397c40e3e074d577d9", {
type: "primarySearch",
customer: "data.message.from.handle",
data: "data"
},
function(data, status) {
$('#mainfront').html(data);
});
});
/*When user selects a conversation search for customer*/
Front.on('conversation', function(data) {
$('#mainfront').html(' <div align="center"> <div class="loader"></div> </div> ');
// triggered when a conversation is loaded
console.log(data);
currentMessage = data;
console.log(data.message.from.handle);
$.post("https://forms.na3.netsuite.com/app/site/hosting/scriptlet.nl?script=422&deploy=1&compid=5210448&h=a7397c40e3e074d577d9", {
type: "primarySearch",
customer: data.message.from.handle,
data: JSON.stringify(data)
},
function(data, status) {
$('#mainfront').html(data);
});
});
/*To Attach the email to corresponding record*/
function checkboxEvent(argument) {
$(argument).attr("disabled", true);
if ($(argument).is(':checked'))
$.post("https://forms.na3.netsuite.com/app/site/hosting/scriptlet.nl?script=422&deploy=1&compid=5210448&h=a7397c40e3e074d577d9", {
type: "message",
internal: $(argument).val(),
tranType: $(argument).attr('class'),
data: JSON.stringify(currentMessage)
},
function(data, status) {
Front.alert({
title: 'Response',
message: data,
okTitle: 'OK'
}, function() {});
});
}
// function searchRecord() {
// var input, filter, table, tr, td, i;
// input = document.getElementById("searchs");
// filter = input.value.toUpperCase();
// table = document.getElementById("resultstable");
// tr = table.getElementsByTagName("tr");
// for (i = 0; i < tr.length; i++) {
// td = tr[i].getElementsByTagName("td")[0];
// if (td) {
// if (td.innerHTML.toUpperCase().indexOf(filter) > -1) {
// tr[i].style.display = "";
// } else {
// tr[i].style.display = "none";
// }
// }
// }
// }
function searchRecord() {
var searchKey = $("#searchs").val();
$.post("https://forms.na3.netsuite.com/app/site/hosting/scriptlet.nl?script=422&deploy=1&compid=5210448&h=a7397c40e3e074d577d9", {
type: "recordSearch",
search: searchKey,
data: "data"
},
function(data, status) {
console.log(data);
$('#customerrecord').hide();
$('#transrecord').show();
$('#transrecord').append(data);
});
}
function searchnull() {
var searchKey = $("#searchs").val();
if (searchKey == "") {
$('#customerrecord').show();
$('#transrecord').hide();
}
}
function createcustomer() {
$.post("https://forms.na3.netsuite.com/app/site/hosting/scriptlet.nl?script=422&deploy=1&compid=5210448&h=a7397c40e3e074d577d9", {
type: "createcustomer",
data: JSON.stringify(currentMessage)
},
function(data, status) {
if (data.length > 1) {
Front.alert({
title: 'Response',
message: "Customer Created",
okTitle: 'OK'
}, function() {
$('#mainfront').html(data);
});
} else {
Front.alert({
title: 'Response',
message: "Customer Record Cannot Be Created",
okTitle: 'OK'
}, function() {
$('#mainfront').html(data);
});
}
});
}
</script>
<div id="mainfront">
<div align="center">
<div class="loader"></div>
</div>
</div>
</body>
</html>
<div id="accordion">
<div class="card">
<div class="card-header" id="headingOne">
<h5 class="mb-0">
<button class="btn btn-link" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">▾ Customer <span class="badge badge-pill badge-dark">Replacewithcustnum</span>
</button>
</h5>
</div>
<div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordion">
<div class="card-body table-responsive">
<table class="" style="width: 100%;">
ReplacewithCustomerROW
</table>
</div>
</div>
</div>
<div class="card">
<div class="card-header" id="headingTwo">
<h5 class="mb-0">
<button class="btn btn-link" data-toggle="collapse" data-target="#collapseTwo" aria-expanded="true" aria-controls="collapseTwo">▾ Search <span class="badge badge-pill badge-dark">ReplacewithNum</span>
</button>
</h5>
</div>
<div id="collapseTwo" class="collapse show" aria-labelledby="headingTwo" data-parent="#accordion">
<div class="card-body table-responsive">
<div>
<div class="input-group">
<input class="form-control" onchange="searchnull()" id="searchs" style="" type="text" placeholder="Search Netsuite Records" aria-label="Recipient's username" aria-describedby="search-in-netsuite">
<div class="input-group-append">
<button class="btn btn-outline-secondary" onclick="searchRecord();" type="button">Search</button>
</div>
</div>
</div>
<table class="table table-striped table-hover" id="customerrecord" style="width: 100%;">
<thead>
<th colspan="3">
<div>
<div class="searchresults" style=" color: grey; font-weight: 400; font-size: small; ">
SEARCH RESULTS <span class="badge badge-pill badge-dark">ReplacewithNum</span>
</div>
</div>
</th>
</thead>
<tbody>
ReplacewithSearchResults
</tbody>
</table>
<table class="table table-striped table-hover" id="transrecord" style="width: 100%;display: none">
</table>
</div>
</div>
</div>
</div>