The script will create an invoice from the Item Fulfillemrnts for the same items & quantity shipped for a specific user and role. We are using the Sales Order transformation method for achieving this.
/**
* @NApiVersion 2.1
* @NScriptType UserEventScript
*/
/*******************************************************************************
* AMVOP- Consolidate the QR Code Logic with Auto-Invoicing
* *************************************************************************
*
* Created Date: 24-12-2021
*
* Author: Jobin & Jismi IT Services LLP
*
* Revision History:
* Revision 1.0- Consolidated the script with QR code encoder code 07-01-2022
* Revision 1.1- Updated with changes in QR code encoder code 12-01-2022
* Revision 1.2- Script rewritten to correct the remove logic 28/01/2022
*****************************************************************************
**/
//global variables are initialized below
const invAutomationUserRole = 1068; //Set as "AMVOP Warehouseman" role internal id
let approvalStatus = 1; //1 stands for 'pending approval' and 2 for 'approved'
//This is an updated version "JJ UE Autoinvoice AMVOP-259" with code added to update the custom field for QR code
define( [ 'N/encode', 'N/record', 'N/runtime', 'N/search' ],
/**
* @param{encode} encode
* @param{record} record
* @param{runtime} runtime
* @param{search} search
*/
( encode, record, runtime, search ) =>
{
/**
* Defines the function definition that is executed before record is loaded.
* @param {Object} scriptContext
* @param {Record} scriptContext.newRecord - New record
* @param {string} scriptContext.type - Trigger type; use values from the context.UserEventType enum
* @param {Form} scriptContext.form - Current form
* @param {ServletRequest} scriptContext.request - HTTP request information sent from the browser for a client action only.
* @since 2015.2
*/
const afterSubmit = ( scriptContext ) =>
{
try
{
let tagHex, formattedTime;
if ( scriptContext.type == scriptContext.UserEventType.DELETE )
return true;//terminates code execution returning true value if context is delete
let orderTypeIs = scriptContext.newRecord.getValue( { fieldId: "ordertype" } );
if ( orderTypeIs != 'SalesOrd' )
return true;//terminates the script execution if order type is not SO
let temp1, newIFRecord, oldIFRecord, salesOrderId, oldItemFulfillmentStatus, newItemFulfillmentStatus,
temp = {};
var itemList = [];
temp1 = runtime.getCurrentUser();//get current user
if ( checkUser( temp1 ) )
{
oldIFRecord = scriptContext.oldRecord;
newIFRecord = scriptContext.newRecord;
newItemFulfillmentStatus = newIFRecord.getValue( { fieldId: "shipstatus" } );
if ( oldIFRecord != null )
oldItemFulfillmentStatus = oldIFRecord.getValue( { fieldId: "shipstatus" } );
if ( ( newItemFulfillmentStatus != oldItemFulfillmentStatus && newItemFulfillmentStatus == 'C' ) || scriptContext.type == scriptContext.UserEventType.SHIP )
{
salesOrderId = newIFRecord.getValue( {
fieldId: "createdfrom"
} );
log.debug( "Related sales order id", salesOrderId );
//Finding the reference number of item fulfillment
let itemFulfillId = newIFRecord.id;
log.debug( "Item fulfillment id", itemFulfillId );
//creating a invoice record using record.transform() function
var newInvoiceRecord = record.transform( {
fromType: record.Type.SALES_ORDER,
fromId: salesOrderId,
toType: record.Type.INVOICE
} );
log.debug( "Invoice generated", newInvoiceRecord );
//Finding the line items count from invoice record
let lineCountItemSublist = newInvoiceRecord.getLineCount( { sublistId: 'item' } );
log.debug( "lineCountItemSublist", lineCountItemSublist );
//creating itemList object with item id and quantity using for loop from item fulfillment
for ( let i = 0; i < lineCountItemSublist; i++ )
{
try
{
if ( checkItemType( newIFRecord, i ) )
{
itemList.push( itemListElement( newIFRecord, i ) );//creating an array of objects to store IF contents
}
} catch ( err )
{
log.debug( 'Error at lineCountItemSublist', err );
log.error( 'Error at lineCountItemSublist', err );
}
}
log.debug( "Item list object array", itemList );
//Adding quantity values to sublist in invoice using itemList object
addQtyRemoveLinesOnInvoice( newInvoiceRecord, itemList );
// deleteInvoiceLine( newInvoiceRecord, itemList );
//Setting value for item fulfillment# in invoice record. IF Reference number
newInvoiceRecord.setValue( {
fieldId: 'custbody_jj_if_no_new',
value: itemFulfillId,
ignoreFieldChange: true
} );
newInvoiceRecord.setValue( {
fieldId: 'approvalstatus',
value: approvalStatus, //1 stands for 'pending approval' and 2 for 'approved'
ignoreFieldChange: true
} );
//---------------Adding code for qr code encoding in template------------//
let sellerNameId = newInvoiceRecord.getValue( {
fieldId: 'subsidiary'
} );
let sellerText = newInvoiceRecord.getText( {
fieldId: 'subsidiary'
} );
//log.debug( "sellerText", sellerText )
let invoiceID = newInvoiceRecord.getValue( { fieldId: 'id' } );
log.debug( " invoiceID", invoiceID )
//search for subsidiary record from invoice
let resultSet = subsidiarySearch( sellerNameId );
//fetching the seller/customer name
let customerName = resultSet[ 0 ].sellerName;
//search to encode customer name
let encodedCustName = encodingString( customerName )
//log.debug(" encodedCustName", encodedCustName)
//get the length of the customer name
let custNameLength = getLength( encodedCustName )
//setting the tag value
tagHex = '01'
//concatinating the tag value hex value of length and encoded customer name
let sellerNameNew = tagHex.concat( custNameLength, encodedCustName )
//log.debug("sellerNameNew", sellerNameNew)
let vatNum = resultSet[ 0 ].vatNumber;
log.debug( "vatNum", vatNum )
// if (vatNum != '' || vatNum != null || vatNum != undefined) {
let encodedVatNum = encodingString( vatNum )
log.debug( "encodedVatNum", encodedVatNum )
let vatNumLength = getLength( encodedVatNum )
//log.debug("vatNumLength", vatNumLength)
tagHex = '02'
let vatNumNew = tagHex.concat( vatNumLength, encodedVatNum )
// } else {
// let vatNumLength = 0
// let encodedVatNum = ''
// let vatNumNew = tagHex.concat(vatNumLength, encodedVatNum)
// }
if ( vatNum == '' || vatNum == null || vatNum == undefined )
{
let vatNumLength = 0
let encodedVatNum = ''
let vatNumNew = tagHex.concat( vatNumLength, encodedVatNum )
}
//log.debug( "vatNumNew", vatNumNew )
let invoiceTotal = newInvoiceRecord.getValue( { fieldId: 'total' } );
let invoiceTotal1 = invoiceTotal.toFixed( 2 )
let encodedStringInvoiceTotal = encodingString( invoiceTotal1 )
let invoiceTotalLength = getLength( encodedStringInvoiceTotal )
// log.debug(" encodedStringInvoiceTotal", encodedStringInvoiceTotal)
tagHex = '04'
let invoiceTotalNew = tagHex.concat( invoiceTotalLength, encodedStringInvoiceTotal )
log.debug( " invoiceTotalNew", invoiceTotalNew )
let vatTotal = newInvoiceRecord.getValue( { fieldId: 'taxtotal' } );
let vatTotal1 = vatTotal.toFixed( 2 )
let encodedStringVatTotal = encodingString( vatTotal1 )
let vatTotalLength = getLength( encodedStringVatTotal )
//log.debug("base64EncodedStringVatTotal",base64EncodedStringVatTotal)
tagHex = '05'
let vatTotalNew = tagHex.concat( vatTotalLength, encodedStringVatTotal )
log.debug( "vatTotalNew", vatTotalNew )
// SAVE THE TRANSFORMED INVOICE
let invSaveObjID = newInvoiceRecord.save( {
enableSourcing: true,
ignoreMandatoryFields: true
} );
log.debug( "Saved invoice is", invSaveObjID );
//Logic to get time stamp - Invoice record create date and time
let timeNew = transactionSearch( invSaveObjID )
log.debug( "timeNew1", timeNew )
let timeStampUTC = timeNew + " UTC"
log.debug( "timeStampUTC", timeStampUTC )
let today = new Date( timeStampUTC )
log.debug( "today", today )
let timeNew1 = today.toISOString()
log.debug( "ISO", timeNew1 )
let encodededTimeStamp = encodingString( timeNew1 )
let timStampLength = getLength( encodededTimeStamp )
// log.debug("encodededTimeStamp",encodededTimeStamp )
tagHex = '03'
let timeStampNew = tagHex.concat( timStampLength, encodededTimeStamp )
log.debug( "timeStampNew ", timeStampNew )
// concatinating the encoded seller name, vat number,time stamp,tax total and vat total
let encodedQrCode = sellerNameNew + vatNumNew + timeStampNew + invoiceTotalNew + vatTotalNew
log.debug( "encodedQrCode", encodedQrCode )
//encoding the result to base 64 format
let base64EncodedString = encode.convert( {
string: encodedQrCode,
inputEncoding: encode.Encoding.HEX,
outputEncoding: encode.Encoding.BASE_64
} );
log.debug( "base64EncodedString", base64EncodedString );
record.submitFields( {
type: record.Type.INVOICE,
id: invSaveObjID,
values: {
'custbody_jj_qr_code': base64EncodedString
}
} );
//----------------QR code encoding code ends in previous line------------//
//setting value for Invoice# field in item fulfillment
record.submitFields( {
type: record.Type.ITEM_FULFILLMENT,
id: itemFulfillId,
values: {
'custbody_jj_pdf_inv_amvop_121': invSaveObjID
}
} );
} else
{
log.debug( "Fulfillment status is not shipped, no invoice created." )
}
}
} catch ( err )
{
log.debug( { title: "Error at afterSubmit function body", details: err } );
log.error( { title: "Error at afterSubmit function body", details: err } );
}
}
return { afterSubmit }
/**
* Function to check if the current user has access to the invoice automation script
* @param {Object} currentUser - Object referring current user
* @returns {boolean} - returns true if user has access else returns false
*/
function checkUser ( currentUser )
{
try
{
let userId = currentUser.id, userRole = currentUser.role;
log.debug( { title: "Current user", details: userId } );
log.debug( { title: "Current user role", details: userRole } );
let invAutomationUser = runtime.getCurrentScript().getParameter( { name: "custscript_jj_inv_auto_user" } );
log.debug( { title: "User obtained from parameters", details: invAutomationUser } );//this value is returned as an id
log.debug( { title: "User role specified in code", details: invAutomationUserRole } );//this value is returned as an id
if ( userId == invAutomationUser && userRole == invAutomationUserRole )
{
log.debug( "User has access to automation script" );
return true;
}
else
{
log.debug( "User is not authorized for invoice automation" );
return false;
}
} catch ( err )
{
log.debug( "Error found at checkUser() function body" );
log.error( "Error found at checkUser() function body" );
}
}
/**
* Function to check if the item type is assembly or inventory and if yes return true
* @param {Object} itemFulfillRecObject - Object referring to the item fulfillment record created to trigger the script
* @param {Number} line - Number value representing the current line value in IF record referring the item
* @returns {boolean} - true for assembly and inventory types else returns false
*/
function checkItemType ( itemFulfillRecObject, line )
{
try
{
let itemTypeValue = itemFulfillRecObject.getSublistValue( {
sublistId: 'item',
fieldId: 'itemtype',
line: line
} );
if ( itemTypeValue === "Assembly" || itemTypeValue === "InvtPart" )
{//checking if item is assembly or inventory item. Add other types if required
return true;
}
else
{
log.debug( "Invoice automation not available for item type" );
return false;
}
} catch ( err )
{
log.debug( "Error at checkItemType() function", err );
log.error( "Error at checkItemType() function", err );
}
}
/**
* Function to return an object with item id, order line value and quantity
* @param {Object} itemFulfillRecObject - Object referring to the item fulfillment record created to trigger the script
* @param {Number} line - Number value representing the current line value in IF record
* @returns {{itemQuantityValue: *, itemIdValue: *, itemOrderLine: *}}
*/
function itemListElement ( itemFulfillRecObject, line )
{
try
{
//Returns null value for quantity if not present in item fulfillment
let itemId = itemFulfillRecObject.getSublistValue( {
sublistId: 'item',
fieldId: 'item',
line: line
} );
let itemQuantity = itemFulfillRecObject.getSublistValue( {
sublistId: 'item',
fieldId: 'quantity',
line: line
} );
let orderLineValue = itemFulfillRecObject.getSublistValue( {
sublistId: 'item',
fieldId: 'orderline',
line: line
} );
let itemObject1 = { itemIdValue: itemId, itemOrderLine: orderLineValue, itemQuantityValue: itemQuantity };
return itemObject1;
} catch ( err )
{
log.debug( "Error at itemListElement() function", err );
log.error( "Error at itemListElement() function", err );
}
}
/**
* Function to add quantity value to each line item in invoice record if it is present in fulfillment
* @param invoiceRecObject - object representing the new invoice generated by the script
* @param itemList - Array with item id, orderline value, quantity represented as objects
*/
function addQtyRemoveLinesOnInvoice ( invoiceRecObject, itemList )
{
try
{
//let addedQtyArray = []
let invoiceLine = invoiceRecObject.getLineCount( { sublistId: 'item' } );//since invoice created from sales order it have all items in sales order
//loop through the invoice object
for ( let i = 0; i < invoiceLine; i++ )
{
let orderLineValue = invoiceRecObject.getSublistValue( {
sublistId: 'item',
fieldId: 'orderline',
line: i
} );
log.debug( "orderLineValue", orderLineValue )
//Find matching value from the itemlist (IF) based of off the orderline
let obj = itemList.find( obj => obj.itemOrderLine == orderLineValue );
log.debug( "obj", obj )
//set qty if orderline matches or null
if ( obj )
{
invoiceRecObject.setSublistValue( {//adding quantity obtained from itemList object
sublistId: 'item',
fieldId: 'quantity',
line: i,
value: obj.itemQuantityValue
} );
}
else
{
invoiceRecObject.setSublistValue( {//adding quantity obtained from itemList object
sublistId: 'item',
fieldId: 'quantity',
line: i,
value: ""
} );
}
//addedQtyArray.push( itemListElementofInvoice( invoiceRecObject, i ) )
}
//log.debug( "XYYZ addedQtyArray", addedQtyArray )
//loops to remove item lines in invoce
for ( var i = invoiceLine - 1; i >= 0; i-- )
{
if ( invoiceRecObject.getSublistValue( { sublistId: 'item', fieldId: 'quantity', line: i } ) == "" )
{
let orderLineValue = invoiceRecObject.getSublistValue( {
sublistId: 'item',
fieldId: 'orderline',
line: i
} );
log.debug( "removed lines", orderLineValue )
invoiceRecObject.removeLine( {// Removing unwanted lines from item sublist
sublistId: 'item',
line: i
} );
}
}
} catch ( err )
{
log.debug( "Error in addQtyRemoveLinesOnInvoice() function", err );
log.error( "Error in addQtyRemoveLinesOnInvoice() function", err );
}
}
/**
* @param function to get the length of the parameters and convert the string to hex value
* @param returns hexadecimal value of string
*/
function getLength ( lengthparam )
{
try
{
let length = lengthparam.length
let orgLength = Number( length ) / 2
log.debug( "length", length )
let hexString = orgLength.toString( 16 )
if ( hexString.length < 2 )
{
hexString = "0" + hexString;
}
//log.debug("hexString", hexString)
return hexString
} catch ( e )
{
log.debug( "Error@getLength", e )
log.error( "Error@getLength", e );
}
}
/**
* @param function accepts UTF_8 format and returns hexadecimal value of input
* @param returns encoded string
*/
function encodingString ( params )
{
try
{
let hexEncodedString = encode.convert( {
string: params,
inputEncoding: encode.Encoding.UTF_8,
outputEncoding: encode.Encoding.HEX
} );
//log.debug("hexEncodedString",hexEncodedString)
return ( hexEncodedString )
} catch ( e )
{
log.debug( "Error@encodingString", e );
log.error( "Error@encodingString", e );
}
}
/**
* @param function to search subsidiary to get the seller name(arabic) if absent it returns name
* @param returns name
*/
function subsidiarySearch ( sellerNameId )
{
try
{
let subsidiaryArray = [];
var subsidiarySearchObj = search.create( {
type: "subsidiary",
filters:
[
[ "internalid", "anyof", sellerNameId ]
],
columns:
[
search.createColumn( { name: "taxidnum", label: "Tax ID" } ),
search.createColumn( {
name: "formulatext",
formula: "NVL2({custrecord_az_mn_sub_namearabic}, {custrecord_az_mn_sub_namearabic}, {name})",
label: "seller name"
} )
]
} );
subsidiarySearchObj.run().each( function ( result )
{
let vatNumber = result.getValue( result.columns[ 0 ] );
let sellerName = result.getValue( result.columns[ 1 ] );
let customRecordObj = {};
customRecordObj.vatNumber = vatNumber
customRecordObj.sellerName = sellerName
subsidiaryArray.push( customRecordObj );
return true;
} );
return subsidiaryArray;
} catch ( e )
{
log.debug( "Error@subsidiarySearch", e );
log.error( "Error@subsidiarySearch", e )
}
}
/**
*
* @param {number} invSaveObjID - internal id of invoice
* @returns formattedtime
*/
function transactionSearch ( invSaveObjID )
{
try
{
var transactionSearchObj = search.create( {
type: "transaction",
filters:
[
[ "internalidnumber", "equalto", invSaveObjID ],
"AND",
[ "mainline", "is", "T" ],
"AND",
[ "systemnotes.type", "is", "T" ],
"AND",
[ "formulanumeric: case when({systemnotes.field}='Record') then 1 else 0 end", "equalto", "1" ]
],
columns:
[
search.createColumn( {
name: "date",
join: "systemNotes",
label: "Date"
} ),
search.createColumn( {
name: "formulatext",
formula: "TO_CHAR({datecreated},'MM/DD/YYYY HH:MI:SS AM')",
label: "Formula (Text)"
} ),
search.createColumn( {
name: "formulatext",
formula: "TO_CHAR({datecreated},'DD/MM/YYYY HH:MI:SS AM')",
label: "Formula (Text)"
} )
]
} );
transactionSearchObj.run().each( function ( result )
{
formattedTime = result.getValue( transactionSearchObj.columns[ 1 ] )
return true;
} );
return formattedTime;
}
catch ( e )
{
log.debug( "error@transactionSearch", e );
log.error( "error@transactionSearch", e );
}
}
} )