The script will strictly transform sales orders to invoices, by making it a Terms order, and all the generated invoices will have a pay-by-link URL generated in it( Generated by Adyen Bundle).
Script lookup the saved searches stored in custom record entries and thus the resultant orders are enlisted and they are transformed into invoices in each reduce.
/**
*@NApiVersion 2.1
*@NScriptType MapReduceScript
*/
define(['N/search', 'N/record', 'N/runtime', 'N/file', 'N/email'], function (search, record, runtime, file, email) {
const SCRIPT_PARAM = "custscript_grw_005_search_id";
const SCRIPT_PARAM_FOLDER_ID = "custscript_grw_005_csv_folder_id"
const CUST_TO_BE_AUTOINV = "custrecord_grw_005_cust_auto_inv";
const CUST_ORDER_SEARCH = "custrecord_grw_005_order_queue";
const CUST_ADYEN_PBL_CONFIG = "custrecord_grw_005_3_adn_pbl_config"
const CUST_INV_BATCH_MAP_REC = "customrecord_grw_005_inv_batch_mapping"
const CUST_ADYEN_AMT_OVERRIDE = "custrecord_grw_005_3_amt_override"
const CUST_ADYEN_SUPPRESS_BILL_ADDRESS = "custrecord_grw_005_3_suppres_billaddress"
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 ADYEN_PAY_BY_LINK_URL_COPY = "custbody_ota_paybylinkurlcopied"
const ADYEN_PAY_BY_LINK_URL = "custbody_es_adyen_pbl_url"
const ADYEN_PAY_BY_LINK_EXPIRES_ON = "custbody_es_adyen_pbl_expiry"
const SO_EXTERNALID = "custbody_ota_salesforcerecordid"
const INV_INVOICE_BATCH = "custbody_ota_invoicebatchcustomer"
const OTA_SF_SYNC_STATUS = "custbody_ota_salesforcesynchstatus"
const SF_SYNC_STATUS_VAL = "1"
const SCRIPT_PARAM_SENDER_ID = "custscript_grw_005_sender_id"
const SCRIPT_PARAM_RECIPIENT_ID = "custscript_grw_005_recipient_id"
function getInputData() {
try {
let savedSeachId = runtime.getCurrentScript().getParameter({ name: SCRIPT_PARAM });
log.debug("savedSeachId", savedSeachId);
let batchMapSeachObj = search.load({ id: savedSeachId });
let batchMapSearchResults = [];
batchMapSeachObj.run().each(function (result) {
let otherField = result.getValue(CUST_TO_BE_AUTOINV);
let orderSearchId;
if (otherField) {
// Get the sales order search ID and any other data you need from the search result
orderSearchId = result.getValue(CUST_ORDER_SEARCH);
}
batchMapSearchResults.push({ orderSearchId });
return true;
});
log.debug("batchMapSearchResults", batchMapSearchResults)
let soDetails = []
for (let index = 0; index < batchMapSearchResults.length; index++) {
let soSearch = batchMapSearchResults[index].orderSearchId
let orderList = getOrderList(soSearch);
if (!isEmpty(orderList)) {
soDetails = soDetails.concat(orderList)
}
}
log.debug("soDetails", soDetails)
return soDetails;
} catch (Err) {
log.debug("Error @getInputData on record save", Err)
log.error("Error @getInputData on record save", Err)
}
}
function reduce(context) {
let successCount = 0
let errCount = 0
let errorMessage = ""
let soId
try {
log.debug("ENTERED REDUCE", "-----------ENTERED REDUCE---------------")
let soKey = context.key;
let orderList = JSON.parse(context.values);
log.debug("orderList", orderList)
soId = orderList.internalId
log.debug("soId", soId)
let entityId = orderList.customerId
let soDocNum = orderList.tranId
let soDocDate = orderList.docDate
let extId = (orderList.externalId != "- None -") ? orderList.externalId : ""
let sopblUrl = (orderList.pblUrl != "- None -") ? orderList.pblUrl : ""
let termsId = (orderList.customerTerms == "- None -" || isEmpty(orderList.customerTerms)) ? "" : Number(orderList.customerTerms)
let customerInvBatch = orderList.invBatch
if (soId) {
try {
let id = record.submitFields({
type: record.Type.SALES_ORDER,
id: soId,
values: {
terms: termsId,
paymentoption: ""
},
options: {
enableSourcing: false,
ignoreMandatoryFields: true
}
});
log.debug("Entered to Transformation of so:", id)
let soRecObj = record.load({
type: record.Type.SALES_ORDER,
id: soId,
isDynamic: true,
});
let soLineCount = soRecObj.getLineCount({
sublistId: 'item'
})
let soArray = []
for (let i = 0; i < soLineCount; i++) {
let solineIndex = i
let soOrderLine = soRecObj.getSublistValue({
sublistId: 'item',
fieldId: 'line',
line: i
});
let soTaxAmt = soRecObj.getSublistValue({
sublistId: 'item',
fieldId: 'tax1amt',
line: i
});
soArray.push({ soOrderLine, soTaxAmt, solineIndex })
}
let invRec = record.transform({
fromType: record.Type.SALES_ORDER,
fromId: id,
toType: record.Type.INVOICE
});
if (soDocNum) {
invRec.setValue({
fieldId: 'tranid',
value: soDocNum
})
}
if (extId) {
invRec.setValue({
fieldId: 'externalid',
value: extId
})
}
if (soDocDate) {
invRec.setText({
fieldId: 'trandate',
text: soDocDate
})
}
if (sopblUrl) {
invRec.setValue({
fieldId: ADYEN_PAY_BY_LINK_URL_COPY,
value: sopblUrl
})
}
if (customerInvBatch) {
invRec.setValue({
fieldId: INV_INVOICE_BATCH,
value: customerInvBatch
})
let InvMapfieldSearchdata = getMappingRecordData(customerInvBatch);
if (!isEmpty(InvMapfieldSearchdata)) {
let adyenAmtOverride = InvMapfieldSearchdata[0].amtOverride
if (!isEmpty(adyenAmtOverride)) {
invRec.setValue({
fieldId: ADYEN_AMT_OVERRIDE,
value: adyenAmtOverride
})
}
let invSupBillAddress = InvMapfieldSearchdata[0].supBillAddress
if (!isEmpty(invSupBillAddress)) {
invRec.setValue({
fieldId: ADYEN_SUPPRESS_BILL_ADDRESS,
value: invSupBillAddress
})
}
//To autogenerate Adyen PBL in invoices except in Zero amount invoices.
if (!isEmpty(sopblUrl)) {
let invPblConfig = InvMapfieldSearchdata[0].pblConfig
log.debug("invPblConfig", invPblConfig)
if (!isEmpty(invPblConfig)) {
invRec.setValue({
fieldId: ADYEN_PAY_BY_LINK_CONFIG,
value: invPblConfig
})
}
}
}
}
invRec.setValue({
fieldId: OTA_SF_SYNC_STATUS,
value: SF_SYNC_STATUS_VAL
})
let invLineCount = invRec.getLineCount({
sublistId: 'item'
})
if (!isEmpty(soArray)) {
for (let index = 0; index < invLineCount; index++) {
let invOrderLine = invRec.getSublistValue({
sublistId: 'item',
fieldId: 'orderline',
line: index
});
let obj = soArray.find(obj => obj.soOrderLine == invOrderLine);
log.debug("obj", obj)
//set taxamount if orderline matches or null
if (!isEmpty(obj)) {
invRec.setSublistValue({
sublistId: 'item',
fieldId: 'tax1amt',
line: index,
value: obj.soTaxAmt
});
}
}
}
let invoiceRecId = invRec.save({
enableSourcing: true,
ignoreMandatoryFields: true
});
log.debug("invoiceRecId", invoiceRecId)
if (invoiceRecId) {
successCount++;
errorMessage = true
}
} catch (Errin) {
errCount++;
errorMessage = Errin.message
log.debug("errorCount in Errin", errCount)
log.debug("Errin", errorMessage)
}
}
log.debug("successCount", successCount)
context.write({
key: soId,
value: errorMessage
});
} catch (Err) {
errCount++;
log.error("Error @reduce on record save", Err)
}
}
function summarize(summary) {
log.debug("summary", summary)
let errorDetails = []
let flag = 0
let errCount = 0;
let successCount = 0;
let errorMsg1 = "Failed Transformation for these Orders,Reason\n";
summary.output.iterator().each(function (key, value) {
if (value != true && value != 'true') {
flag = 1
errCount++;
errorDetails.push({ key: key, value: value });
errorMsg1 += (key + ' , ' + value + '\n');
}
else {
successCount++
}
return true;
});
log.debug("errorMsg1", errorMsg1)
log.debug("successCount", successCount)
log.debug("errCount", errCount)
let csvContent = `Success Count,Error Count\n${successCount},${errCount}\n`;
if (flag == 1 || errCount > 0) {
csvContent += errorMsg1;
}
log.debug("csvContent", csvContent)
let fileName = "bill_run_summary" + "_" + Math.floor(Date.now() / 1000) + ".csv";
// Create a file object and save the CSV content
let csvResponseFolder = runtime.getCurrentScript().getParameter({ name: SCRIPT_PARAM_FOLDER_ID });
log.debug("csvResponseFolder", csvResponseFolder)
let fileObj = file.create({
name: fileName,
fileType: file.Type.CSV,
contents: csvContent,
folder: csvResponseFolder,
});
let fileId = fileObj.save();
log.debug('CSV File ID', fileId);
let senderId = runtime.getCurrentScript().getParameter({ name: SCRIPT_PARAM_SENDER_ID });
let recipientId = runtime.getCurrentScript().getParameter({ name: SCRIPT_PARAM_RECIPIENT_ID });
email.send({
author: senderId,
recipients: recipientId,
subject: 'Auto-Invoicing Status Update',
body: "<BR />Hi, <BR/> <BR/>This email is to notify you that Invoice creation has completed. <BR/> Please find the status in the attached document.",
attachments: [fileObj],
relatedRecords: {
entityId: recipientId
}
});
}
function getOrderList(soSearch) {
try {
if (soSearch) {
let orderSeachObj = search.load({ id: soSearch });
let salesOrdersResults = [];
orderSeachObj.run().each(function (result) {
let internalId = result.getValue({
name: "internalid",
summary: "GROUP"
});
let customerId = result.getValue({
name: "entity",
summary: "GROUP"
});
let tranId = result.getValue({
name: "tranid",
summary: "GROUP"
});
let externalId = result.getValue({
name: SO_EXTERNALID,
summary: "GROUP"
});
let pblUrl = result.getValue({
name: ADYEN_PAY_BY_LINK_URL,
summary: "GROUP"
});
let pblUrlExp = result.getValue({
name: ADYEN_PAY_BY_LINK_EXPIRES_ON,
summary: "GROUP"
});
let customerTerms = result.getValue({
name: "terms",
join: "customer",
summary: "GROUP"
});
let invBatch = result.getValue({
name: INV_INVOICE_BATCH,
summary: "GROUP"
});
let docDate = result.getValue({
name: "trandate",
summary: "GROUP"
});
salesOrdersResults.push({ internalId, customerId, tranId, externalId, pblUrl, pblUrlExp, customerTerms, invBatch, docDate })
return true;
});
return salesOrdersResults;
}
else {
return
}
} catch (Err) {
log.debug("Err@getOrderList", Err)
log.error("Err@getOrderList", Err)
}
}
/**
* Find value of Adyen Amount override
* @param {string} invBatchNumber
* @returns
*/
function getMappingRecordData(invBatchNumber) {
try {
let InvMapfieldLookUp = [];
let InvMapfieldSearchObj = search.create({
type: CUST_INV_BATCH_MAP_REC,
filters:
[
["name", "is", invBatchNumber]
],
columns:
[
search.createColumn({ name: CUST_ADYEN_AMT_OVERRIDE }),
search.createColumn({ name: CUST_ADYEN_SUPPRESS_BILL_ADDRESS }),
search.createColumn({ name: CUST_ADYEN_PBL_CONFIG })
]
});
let searchResultCount = InvMapfieldSearchObj.runPaged().count;
log.debug("InvMapfieldSearchObj result count", searchResultCount);
InvMapfieldSearchObj.run().each(function (result) {
let amtOverride = result.getValue(InvMapfieldSearchObj.columns[0])
let supBillAddress = result.getValue(InvMapfieldSearchObj.columns[1])
let pblConfig = result.getValue(InvMapfieldSearchObj.columns[2])
InvMapfieldLookUp.push({ amtOverride, supBillAddress, pblConfig })
return true;
});
return InvMapfieldLookUp;
} catch (Err) {
log.error("Err@getMappingRecordData", Err)
return [];
}
}
function isEmpty(value) {
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))
);
}
return {
getInputData: getInputData,
reduce: reduce,
summarize: summarize
}
});