The following script creates invoice in Netuite based on the request values provided from Salesforce.
Also these invoice will have a pay-by-link URL autogenerated( the script trigger the Adyen Bundle to generate the link)
/**
* @NApiVersion 2.1
* @NScriptType RestLet
* @NModuleScope SameAccount
*/
define(['N/record', 'N/search', 'N/runtime', './moment.js', 'N/format', 'N/email', 'N/error', 'N/url'],
function (record, search, runtime, moment, format, email, error, url) {
const DOCUMENT_DATE = "custbody_document_date"
const DOCUMENT_PDF_LINK = "custbody_ota_documentpdflink"
const SALESFORCE_RECORD_ID = "custbody_ota_salesforcerecordid"
const SALESFORCE_UPDATED_DATE = "custbody_ota_salesforceupdateddate"
const SALESFORCEURL = "custbody_ota_salesforceurl"
const SALESFORCE_SYNCHED_DATE = "custbody_ota_sfsyncheddate"
const SALESFORCE_TRANSACTION_EMAIL_SENT_DATETIME = "custbody_ota_transactionemailsentdate"
const OTA_SERVICE_PERIOD_START_DATE = "custcol_ota_serviceperiodstartdate"
const OTA_SERVICE_PERIOD_END_DATE = "custcol_ota_serviceperiodenddate"
const OTA_INV_BATCH_CUSTOMER_NO = "custbody_ota_invoicebatchcustomer"
const STATUS_REC_TYPE = "customrecord_grw_003_rec_intgrn_status"
const STATUS_FLD_TRAN_TYPE = "custrecord_grw_003_tran_type"
const STATUS_FLD_ERROR_MSSG = "custrecord_grw_003_json_response"
const STATUS_FLD_RESQUEST_JSON = "custrecord_grw_003_req_json"
const STATUS_STAT_CODE = "custrecord_grw_003_statuscode"
const ADYEN_AMT_OVERRIDE = "custbody_es_adyen_auth_amount_override"
const ADYEN_SUPPRESS_BILL_ADDRESS = "custbody_es_adyen_suppress_billaddress"
const ADYEN_PAY_BY_LINK_CONFIG = "custbody_es_adyen_pbl_config"
const ENTITY_INV_BATCH = "custentity_ota_invoicebatch"
const CUST_ADYEN_SUPPRESS_BILL_ADDRESS = "custentity_ota_invoicebatch.custrecord_grw_005_3_suppres_billaddress"
const CUST_ADYEN_AMT_OVERRIDE = "custentity_ota_invoicebatch.custrecord_grw_005_3_amt_override"
const CUST_ADYEN_PBL_CONFIG = "custentity_ota_invoicebatch.custrecord_grw_005_3_adn_pbl_config"
/**
* Post action data handling and triggers Invoice creation
* @param {*} context - json object request from api call
* @returns
*/
function doPost(context) {
let responseObj = {};
let requestBody = context
log.debug('requestBody', requestBody);
if (!isEmpty(requestBody)) {
let invObjJson = requestBody
let user = runtime.getCurrentUser();
let datePref = user.getPreference({
name: 'DATEFORMAT'
});
if (!isEmpty(invObjJson)) {
// Get Currency
let invCurrency = invObjJson.currency
// Get Document Date
let invDocDate = invObjJson.custbody_document_date
if (!isEmpty(invDocDate)) {
invDocDate = moment(invDocDate).format(datePref)
}
// Get Transaction Date
let invTranDate = invObjJson.tranDate
if (!isEmpty(invTranDate)) {
invTranDate = moment(invTranDate).format(datePref)
}
// Get Due Date
let invDueDate = invObjJson.dueDate
if (!isEmpty(invDueDate)) {
invDueDate = moment(invDueDate).format(datePref)
}
let invDocPdfLink = invObjJson.custbody_ota_documentpdflink
let invSfRecId = invObjJson.custbody_ota_salesforcerecordid
// Get value for Salesforce Updated Date
let invSfUpdateDate = invObjJson.custbody_ota_salesforceupdateddate
if (!isEmpty(invSfUpdateDate)) {
invSfUpdateDate = format.format({ value: new Date(invSfUpdateDate), type: format.Type.DATETIME })
}
let invSfUrl = invObjJson.custbody_ota_salesforceurl
// Get value for Salesforce Synched Date field
let invSfSyncDate = invObjJson.custbody_ota_sfsyncheddate
if (!isEmpty(invSfSyncDate)) {
invSfSyncDate = format.format({ value: new Date(invSfSyncDate), type: format.Type.DATETIME })
}
// Get Data for Salesforce Transaction Email Sent DateTime field
let invEmailSentDate = invObjJson.custbody_ota_transactionemailsentdate
if (!isEmpty(invEmailSentDate)) {
invEmailSentDate = format.format({ value: new Date(invEmailSentDate), type: format.Type.DATETIME })
}
//Set customer
let invEntity = invObjJson.entity
//Set External ID (unique identifier for records)
let invExternalId = invSfRecId
//Get item object
let invItem = invObjJson.item
//Get Memo
let invMemo = invObjJson.memo
let invSubsidiary = invObjJson.subsidiary
//Get Document number
let invTranId = invObjJson.tranId
// Get Location value (Mandatory Field in inv record, so if it null system will throw error)
let invLocation = invObjJson.location
let invCostCenter = invObjJson.department
if (!isEmpty(invExternalId)) {
// For duplicate detection using External Id
let extId = invAlreadyExits(invExternalId)
if (!extId) {
let invId = createNSInvoice(invCurrency, invDocDate, invDocPdfLink, invSfRecId, invSfUpdateDate, invSfUrl, invSfSyncDate, invEmailSentDate, invDueDate, invEntity, invExternalId, invItem, invMemo, invSubsidiary, invTranDate, datePref, invTranId, invLocation, invCostCenter)
log.debug("invId", invId)
if (Number.isInteger(invId)) {
responseObj.statusCode = "SUCCESS";
responseObj.recordId = invId;
return JSON.stringify(responseObj);
}
else {
responseObj.statusCode = "FAILURE";
responseObj.error = invId;
let statusRecId = createStatusRecord(responseObj, requestBody)
}
}
else {
responseObj.statusCode = "FAILURE";
responseObj.error = "This record already exists";
let statusRecId = createStatusRecord(responseObj, requestBody)
}
}
else {
responseObj.statusCode = "FAILURE";
responseObj.error = "External Id is missing";
let statusRecId = createStatusRecord(responseObj, requestBody)
}
}
}
}
/**
* Creation of Invoice
*/
function createNSInvoice(invCurrency, invDocDate, invDocPdfLink, invSfRecId, invSfUpdateDate, invSfUrl, invSfSyncDate, invEmailSentDate, invDueDate, invEntity, invExternalId, invItem, invMemo, invSubsidiary, invTranDate, datePref, invTranId, invLocation, invCostCenter) {
let invResponse;
try {
let invRecord = record.create({
type: record.Type.INVOICE,
isDynamic: true
});
invRecord.setValue({
fieldId: 'entity',
value: invEntity
});
if (!isEmpty(invSubsidiary)) {
invRecord.setValue({
fieldId: 'subsidiary',
value: invSubsidiary
});
}
if (!isEmpty(invCurrency)) {
invRecord.setValue({
fieldId: 'currency',
value: invCurrency
});
}
invRecord.setValue({
fieldId: 'externalid',
value: invExternalId
})
if (!isEmpty(invTranDate)) {
invRecord.setText({
fieldId: 'trandate',
text: invTranDate
})
}
else {
invRecord.setValue({
fieldId: 'trandate',
value: new Date()
})
}
if (!isEmpty(invTranId)) {
invRecord.setValue({
fieldId: 'tranid',
value: invTranId
});
}
invRecord.setValue({
fieldId: 'location',
value: invLocation
});
if (!isEmpty(invCostCenter)) {
invRecord.setValue({
fieldId: 'department',
value: invCostCenter
});
}
try {
if (!isEmpty(invDocDate)) {
invRecord.setText({
fieldId: DOCUMENT_DATE,
text: invDocDate
});
}
if (!isEmpty(invDocPdfLink)) {
invRecord.setValue({
fieldId: DOCUMENT_PDF_LINK,
value: invDocPdfLink
});
}
if (!isEmpty(invSfRecId)) {
invRecord.setValue({
fieldId: SALESFORCE_RECORD_ID,
value: invSfRecId
});
}
if (!isEmpty(invSfUpdateDate)) {
invRecord.setText({
fieldId: SALESFORCE_UPDATED_DATE,
text: invSfUpdateDate
});
}
if (!isEmpty(invSfUrl)) {
invRecord.setValue({
fieldId: SALESFORCEURL,
value: invSfUrl
});
}
if (!isEmpty(invSfSyncDate)) {
invRecord.setText({
fieldId: SALESFORCE_SYNCHED_DATE,
text: invSfSyncDate
});
}
if (!isEmpty(invEmailSentDate)) {
invRecord.setText({
fieldId: SALESFORCE_TRANSACTION_EMAIL_SENT_DATETIME,
text: invEmailSentDate
});
}
if (!isEmpty(invDueDate)) {
invRecord.setText({
fieldId: 'duedate',
text: invDueDate
})
}
if (!isEmpty(invMemo)) {
invRecord.setValue({
fieldId: 'memo',
value: invMemo
})
}
} catch (e) {
log.error("Err@skipcustomFields", e)
}
if (!isEmpty(invItem)) {
let invItems = invItem.items
if (!isEmpty(invItems)) {
let itemLineCount = invItems.length
for (let index = 0; index < itemLineCount; index++) {
let lineItem = invItems[index].item
if (!isEmpty(lineItem)) {
invRecord.selectNewLine({
sublistId: 'item'
});
invRecord.setCurrentSublistValue({
sublistId: 'item',
fieldId: 'item',
value: lineItem,
ignoreFieldChange: false,
forceSyncSourcing: true
});
invRecord.setCurrentSublistValue({
sublistId: 'item',
fieldId: 'quantity',
value: invItems[index].quantity
});
invRecord.setCurrentSublistValue({
sublistId: 'item',
fieldId: 'rate',
value: invItems[index].rate
});
invRecord.setCurrentSublistValue({
sublistId: 'item',
fieldId: 'tax1amt',
value: invItems[index].taxamount
});
try {
let servicePeriodStartdate = invItems[index].custcol_ota_serviceperiodstartdate
if (!isEmpty(servicePeriodStartdate)) {
servicePeriodStartdate = moment(servicePeriodStartdate).format(datePref)
invRecord.setCurrentSublistText({
sublistId: 'item',
fieldId: OTA_SERVICE_PERIOD_START_DATE,
text: servicePeriodStartdate
});
}
let servicePeriodEnddate = invItems[index].custcol_ota_serviceperiodenddate
if (!isEmpty(servicePeriodEnddate)) {
servicePeriodEnddate = moment(servicePeriodEnddate).format(datePref)
invRecord.setCurrentSublistText({
sublistId: 'item',
fieldId: OTA_SERVICE_PERIOD_END_DATE,
text: servicePeriodEnddate
});
}
if (!isEmpty(invItems[index].department)) {
invRecord.setCurrentSublistValue({
sublistId: 'item',
fieldId: 'department',
value: invItems[index].department
});
}
} catch (Err) {
log.error("Errskipcustomlinefields", Err)
}
invRecord.commitLine({
sublistId: 'item'
});
}
}
}
}
let InvMapfieldLookUp = search.lookupFields({
type: record.Type.CUSTOMER,
id: invEntity,
columns: [CUST_ADYEN_SUPPRESS_BILL_ADDRESS, CUST_ADYEN_AMT_OVERRIDE, ENTITY_INV_BATCH, CUST_ADYEN_PBL_CONFIG]
});
log.debug("InvMapfieldLookUp", InvMapfieldLookUp)
if (!isEmpty(InvMapfieldLookUp)) {
let amtOverride = InvMapfieldLookUp[CUST_ADYEN_AMT_OVERRIDE]
if (!isEmpty(amtOverride)) {
invRecord.setValue({
fieldId: ADYEN_AMT_OVERRIDE,
value: amtOverride
});
}
let supBillAddress = InvMapfieldLookUp[CUST_ADYEN_SUPPRESS_BILL_ADDRESS]
if (!isEmpty(supBillAddress)) {
invRecord.setValue({
fieldId: ADYEN_SUPPRESS_BILL_ADDRESS,
value: supBillAddress
});
}
let hasEntityInvBatch = InvMapfieldLookUp[ENTITY_INV_BATCH]
if (!isEmpty(hasEntityInvBatch)) {
let entityInvBatch = hasEntityInvBatch[0].text;
if (!isEmpty(entityInvBatch)) {
invRecord.setValue({
fieldId: OTA_INV_BATCH_CUSTOMER_NO,
value: entityInvBatch
});
}
}
let hasAdyenPblConfig = InvMapfieldLookUp[CUST_ADYEN_PBL_CONFIG]
if (!isEmpty(hasAdyenPblConfig)) {
let adyenPblConfig = hasAdyenPblConfig[0].value;
if (!isEmpty(adyenPblConfig)) {
invRecord.setValue({
fieldId: ADYEN_PAY_BY_LINK_CONFIG,
value: adyenPblConfig
});
}
}
}
let recordID = invRecord.save({
enableSourcing: true,
ignoreMandatoryFields: true
});
log.debug("recordID", recordID);
invResponse = recordID
}
catch (Err) {
invResponse = Err.message;
log.error("Err@Sales Order Creation", Err)
}
return invResponse;
}
/**
* Duplicate validation of transaction using external id
* @param {*} invExtId
* @returns
*/
function invAlreadyExits(invExtId) {
let resultval;
try {
let invoiceSearchObj = search.create({
type: "invoice",
filters:
[
["type", "anyof", "CustInvc"],
"AND",
["externalid", "anyof", invExtId]
],
columns:
[
search.createColumn({
name: "internalid",
summary: "GROUP",
label: "Internal ID"
})
]
});
let searchResultCount = invoiceSearchObj.runPaged().count;
log.debug("invoiceSearchObj result count", searchResultCount);
invoiceSearchObj.run().each(function (result) {
resultval = result.getValue({
name: "internalid",
summary: "GROUP",
label: "Internal ID"
});
log.debug("resultval", resultval)
return false;
});
} catch (e) {
log.error("Err@invAlreadyExits", e)
}
return resultval ? resultval : false;
}
/**
* Custom record creation for error handling
* @param {*} reponseJson
* @param {*} requestBody
* @returns
*/
function createStatusRecord(reponseJson, requestBody) {
let statusRecObj = record.create({
type: STATUS_REC_TYPE,
isDynamic: true
});
statusRecObj.setValue({
fieldId: STATUS_FLD_TRAN_TYPE,
value: "Invoice Generation API"
});
statusRecObj.setValue({
fieldId: STATUS_FLD_ERROR_MSSG,
value: reponseJson.error
});
statusRecObj.setValue({
fieldId: STATUS_STAT_CODE,
value: reponseJson.statusCode
});
statusRecObj.setValue({
fieldId: STATUS_FLD_RESQUEST_JSON,
value: requestBody
});
let statusRecId = statusRecObj.save({
enableSourcing: true,
ignoreMandatoryFields: true
});
sendEmailNotification(statusRecId)
var missingIdError = error.create({
name: reponseJson.statusCode,
message: reponseJson.error,
notifyOff: true
});
missingIdError.toString = function () { return missingIdError.message };
throw missingIdError;
}
/**
* Send Email if errors blocks transaction creation
* @param {*} statusRecId
*/
function sendEmailNotification(statusRecId) {
try {
let senderId = runtime.getCurrentUser().id;
let recipientId = 242 //113; // For now we set the receipient Test Employee, will be updated as -5 later;
let output = url.resolveRecord({
recordType: STATUS_REC_TYPE,
recordId: statusRecId,
isEditMode: true
});
if (output) {
let extractedInfo = extractRecordInfoFromUrl(output);
log.debug('Record Type:', extractedInfo.recordType);
log.debug('Record ID:', extractedInfo.recordId);
log.debug('account Id:', extractedInfo.accntId);
let nsBaseUrl = 'https://' + extractedInfo.accntId + '.app.netsuite.com/app/common/custom/custrecordentry.nl?rectype=';
let recLink = '<a href="' + nsBaseUrl + extractedInfo.recordType + '&id=' + extractedInfo.recordId + '"> View Record </a>';
log.debug("recLink1", recLink)
let emailSubject = "Alert: Invoice Creation API Failure"
let emailBody = "<BR />Hi, <BR/> <BR/>This email is to notify you that the Invoice Generation triggered from SalesForce has failed exceution. <BR/> Please find the failure reason from the below record" + " " + recLink + "."
email.send({
author: senderId,
recipients: recipientId,
subject: emailSubject,
body: emailBody,
relatedRecords: {
entityId: recipientId
}
});
}
} catch (e) {
log.error("Err@sendEmailNotification", e)
}
}
/**
* validating variable values has value or not
* @param {*} value
* @returns
*/
function isEmpty(value) {
try {
let isEmptyObject = function (a) {
if (typeof a.length === 'undefined') { // it's an Object, not an Array
let hasNonempty = Object.keys(a).some(function nonEmpty(element) {
return !isEmpty(a[element]);
});
return hasNonempty ? false : isEmptyObject(Object.keys(a));
}
return !a.some(function nonEmpty(element) { // check if array is really not empty as JS thinks
return !isEmpty(element); // at least one element should be non-empty
});
};
return (
value == false
|| typeof value === 'undefined'
|| value == null
|| (typeof value === 'object' && isEmptyObject(value))
);
} catch (Err) {
log.error("Err@extractRecordInfoFromUrl", Err)
}
}
/**
* To fetch Url Components of the Custom Record
* @param {*} url
* @returns
*/
function extractRecordInfoFromUrl(url) {
try {
// Define regular expressions to match the parameters
let rectypeRegex = /rectype=(\d+)/;
let idRegex = /id=(\d+)/;
let idAccntRegex = /compid=(\d+)/;
let matchRectype = url.match(rectypeRegex);
let matchId = url.match(idRegex);
let matchAccId = url.match(idAccntRegex);
return {
recordType: matchRectype[1],
recordId: matchId[1],
accntId: matchAccId[1]
};
} catch (Err) {
log.error("Err@extractRecordInfoFromUrl", Err)
}
}
return {
'post': doPost
}
});