When Item fulfillment is created and in shipped status, an invoice will be created for that item fulfillment.
When an item fulfillment is deleted, the auto-created item fulfillment will also be deleted from Netsuite.
This auto invoice generation script has considered all the item types like Group, inventory, non-inventory, kit, discount, markup, and subtotal.
This script is specifically designed to work in specific conditions.
- The sales order must be created from Shopify.
- The sales order must be created from 2021.
/**
* @NApiVersion 2.x
* @NScriptType UserEventScript
*/
/****************************************************************************
* Brooklyn Bicycle Co. |BROOK-52 | Order Process
* **************************************************************************
* Date: 13-12-2020
*
* Author: Jobin & Jismi IT Services LLP
*
* Description:
* Automated creation of invoice for the itemfulfilled
* on the sale sorder record.
*
* Revision History:
* Revision 1.0 ${13-12-2020} Gloria : created
* Revision 1.0 ${08-02-2021} Gloria : Updated to resolve the issue with invoice creation.
*
*
******************************************************************************/
define(['N/search', 'N/record', 'N/runtime', 'N/format'],
function (search, record, runtime, format) {
function beforeSubmit(scriptContext) {
try {
//get the current year
var currrentDate = new Date();
var year = currrentDate.getFullYear();
log.debug("scriptContext.type", scriptContext.type)
var newRec = scriptContext.newRecord;
var oldRec = scriptContext.oldRecord;
// when user delete the item fulfillment
if (scriptContext.type == "delete") {
var SoId = oldRec.getValue({ fieldId: "createdfrom" });
// related invoice for this itemfulfillment
var invId = oldRec.getValue({ fieldId: "custbody_jj_invoice_link" });
log.debug("invId", invId)
if (SoId) {
var salesOrderResult = SalesOrderSearch(SoId)
var created = salesOrderResult.created;
log.debug('created', created);
var Createddate = format.parse({ value: created, type: format.Type.DATETIME, timezone: format.Timezone.AMERICA_NEW_YORK })
log.debug('Createddate', Createddate);
log.debug('Createddate year', Createddate.getFullYear());
var createdYear = Createddate.getFullYear();
var type = salesOrderResult.type
if (type == "Sales Order" && createdYear >= 2021) {
var source = salesOrderResult.source
var farApp = salesOrderResult.farApp
var webOrdId = salesOrderResult.webOrdId
log.debug("source farApp webOrdId", source + " " + farApp + " " + webOrdId)
//when the year is greater than or equal to 2021, source is Web Services,FARAPP MARKETPLACE/CART’ field value is ‘Shopify’
// and the ‘FARAPP MARKETPLACE/CART ORDER NUMBER’ field is not empty.
if (source == "webServices" && webOrdId && farApp == "Shopify") {
log.debug("invId del", invId)
if (invId) {
//fetching the internal ids of related deposit applications
var depAppl = depositApplication(invId);
if (depAppl[0].length > 0) {
//delete related deposit application of the invoice
for (var i = 0; i < depAppl[0].length; i++) {
var depApplDel = record.delete({
type: record.Type.DEPOSIT_APPLICATION,
id: depAppl[0][i]
});
// log.debug('depApplDel', depApplDel)
}
}
try {
var custDep = getCustomerDeposits(depAppl[0])
log.debug('custDep', custDep)
if (custDep && custDep.length)
for (var i = 0; i < custDep.length; i++) {
//customer deposit search for finding the total applied amount.
var appliedAmount = getAppliedAmount(custDep[i])
log.debug('appliedAmount', appliedAmount)
if (appliedAmount && appliedAmount != '0') {
//set the total applied amount in the customer deposit
var id = record.submitFields({
type: record.Type.CUSTOMER_DEPOSIT,
id: custDep[i],
values: {
custbody_jj_applied_amount: appliedAmount
},
options: {
enableSourcing: false,
ignoreMandatoryFields: true
}
});
log.debug('id', id)
}
}
} catch (e) {
log.error("error@settting AppliedAmount", e)
}
//delete invoice
var invoiceDel = record.delete({
type: record.Type.INVOICE,
id: invId
});
log.debug('invoiceDel', invoiceDel)
}
}
}
}
}
} catch (e) {
log.error("error@beforeSubmit", e)
}
}
function afterSubmit(scriptContext) {
try {
//get the current year
var currrentDate = new Date();
var year = currrentDate.getFullYear();
log.debug("scriptContext.type", scriptContext.type)
var newRec = scriptContext.newRecord;
var oldRec = scriptContext.oldRecord;
var SoId = newRec.getValue({ fieldId: "createdfrom" });
var invId = newRec.getValue({ fieldId: "custbody_jj_invoice_link" });
log.debug("SoId", SoId)
if (SoId && !invId) {
//status of the item fulfillment
var newStatus = scriptContext.newRecord.getValue({ fieldId: 'shipstatus' });
log.debug("newStatus", newStatus)
var oldStatus = scriptContext.type == "create" ? '' : scriptContext.oldRecord.getValue({ fieldId: 'shipstatus' })
log.debug("oldStatus", oldStatus)
// when user create the item fulfillment
if ((scriptContext.type == "create" && newStatus == "C") || (scriptContext.type == "edit" && (oldStatus != "C" && newStatus == "C")) || scriptContext.type == "ship") {
var salesOrderResult = SalesOrderSearch(SoId)
var created = salesOrderResult.created;
log.debug('created', created);
var Createddate = format.parse({ value: created, type: format.Type.DATETIME, timezone: format.Timezone.AMERICA_NEW_YORK })
log.debug('Createddate', String(Createddate));
log.debug('Createddate year', Createddate.getFullYear());
var createdYear = Createddate.getFullYear();
var type = salesOrderResult.type
if (type == "Sales Order" && createdYear >= 2021) {
var source = salesOrderResult.source
var farApp = salesOrderResult.farApp
var webOrdId = salesOrderResult.webOrdId
log.debug("source farApp webOrdId", source + " " + farApp + " " + webOrdId)
//when the source is Web Services,FARAPP MARKETPLACE/CART’ field value is ‘Shopify’
// and the ‘FARAPP MARKETPLACE/CART ORDER NUMBER’ field is not be empty.
if (source == "webServices" && webOrdId && farApp == "Shopify") {
// when user edit the item fulfillment
createInvoice(SoId, newRec)
}
}
}
}
} catch (e) {
log.error("error@afterSubmit", e)
}
}
function getCustomerDeposits(depoAppl) {
var custDepo = [];
var customerdepositSearchObj = search.create({
type: "customerdeposit",
filters:
[
["type", "anyof", "CustDep"],
"AND",
["applyingtransaction.internalid", "anyof", depoAppl]
],
columns:
[
search.createColumn({ name: "internalid", label: "Internal ID" })
]
});
var searchResultCount = customerdepositSearchObj.runPaged().count;
log.debug("customerdepositSearchObj result count", searchResultCount);
customerdepositSearchObj.run().each(function (result) {
// .run().each has a limit of 4,000 results
custDepo.push(result.getValue({ name: "internalid", label: "Internal ID" }))
return true;
});
return custDepo;
}
/**
* customer deposit search for finding the total applied amount.
* @param {string/number} id - internal id of customer deposit
* @returns {number}
*/
function getAppliedAmount(id) {
var appliedAmount = 0;
var customerdepositSearchObj = search.create({
type: "customerdeposit",
filters:
[
["type", "anyof", "CustDep"],
"AND",
["internalid", "anyof", id]
],
columns:
[
search.createColumn({
name: "amountpaid",
summary: "SUM",
label: "Amount Paid"
})
]
});
var searchResultCount = customerdepositSearchObj.runPaged().count;
log.debug("customerdepositSearchObj result count", searchResultCount);
customerdepositSearchObj.run().each(function (result) {
// .run().each has a limit of 4,000 results
appliedAmount = result.getValue({
name: "amountpaid",
summary: "SUM",
label: "Amount Paid"
});
// return true;
});
return appliedAmount;
}
function getSublist(recordObj, sublistId) {
/**
* Get Sublist fields and their values
*
* @param recordObj {Object} Initialised Object reference to record
* @param sublistId {String} sublistId on the Object
* @return {Array[Object]}
*/
var result = [];
var sublistFields = recordObj.getSublistFields(sublistId);
var lineCount = recordObj.getLineCount({
sublistId: sublistId
});
for (var line = 0; line < lineCount; line++) {
result.push(
sublistFields.reduce(function (a, c) {
try {
a[c] = {
value: recordObj.getSublistValue({
sublistId: sublistId,
fieldId: c,
line: line
}),
text: recordObj.getSublistText({
sublistId: sublistId,
fieldId: c,
line: line
})
};
return a;
} catch (er) { }
return a;
}, {})
);
}
return result;
}
/**
* Check whether the item is a related item
*
* @param dataArray {Array.<Object>} remaing Sales Order line Items
* @param index {String|Number}
* @param ACCUMULATOR
* @returns array
*/
function isRelatedItem(dataArray, i, ACCUMULATOR) {
const RELATED_ITEM_TYPE = {
"Markup": "Markup",
"Discount": "Discount"
};
for (var index = i, len = dataArray.length; index < len; index++) {
if ((!(dataArray[index].isclosed.value)) && RELATED_ITEM_TYPE[dataArray[index].itemtype.value])
ACCUMULATOR.push(dataArray[index])
else //If item is not a releated item
break;
}
return ACCUMULATOR;
}
function lineMap(IF_LINES, SO_LINES) {
/**
* Map Item FulFillment Item Lines To Sales Order Item Lines and attach all the related items such as MarkUp,Discount,.. etc
*
* @param IF_LINES{Array[Object]} Item Fulfillment Line Items
* @param SO_LINES {Array[Object]} Sales Order Line Items
* @return {Array[Object]}
*/
const RELATED_ITEM_TYPE = {
"Markup": "Markup",
"Discount": "Discount"
};
//Map items between Item Fulfillment and Sales Order, contains only Item FulFillment items
var mappedItems = IF_LINES.map(function (currentValue) {
log.debug("currentValue", currentValue)
return Object.assign({}, currentValue,
SO_LINES.find(function (eachValue) {
return currentValue['orderline'].value == eachValue['line'].value;
}) || {}
);
});
//Add all the related items to Mapped Items, contains items in Item Fulfillment and their related items
var groupedItems = mappedItems.reduce(function (ACCUMULATOR, CURRENT_VALUE, idx, src) {
ACCUMULATOR.push(CURRENT_VALUE);
var remainingItems = SO_LINES.filter(function (eachValue) { //Contains all the succeding items in Sales Order from the current item
if (parseInt(eachValue.index) > parseInt(CURRENT_VALUE.index))
return true;
return false;
});
log.debug('remainingItems', remainingItems)
remainingItems.sort(function (a, b) { return a.index - b.index });
log.debug('remainingItems sorted', remainingItems)
//Map only the related item in Sales Order to the current item in Item Fulfillment
for (var index = 0, len = remainingItems.length; index < len; index++) {
if ((!remainingItems[index].isclosed.value) && remainingItems[index].index >= CURRENT_VALUE.index + 1 && RELATED_ITEM_TYPE[remainingItems[index].itemtype.value]) //Process only RELATED_ITEM_TYPE such as Markup,Discount
ACCUMULATOR.push(remainingItems[index]);
else if (RELATED_ITEM_TYPE[remainingItems[index].itemtype.value])
continue;
else if (remainingItems[index].itemtype.value == 'Group')
break;
else if (remainingItems[index].itemtype.value == 'EndGroup') //If the current Item is a EndGroup item
continue;
else if (remainingItems[index].ingroup.value == "T" && remainingItems[index].itemtype.value != 'Group') //If the current Item is a Group Item or EndGroup item and not starting line of group.
continue;
else
break;
}
log.debug("ACCUMULATOR1", ACCUMULATOR)
for (var index = 0, len = remainingItems.length; index < len; index++) {
if ((!remainingItems[index].isclosed.value) && remainingItems[index].itemtype.value == 'Subtotal') { //Process only RELATED_ITEM_TYPE such as Subtotal
ACCUMULATOR.push(remainingItems[index]);
if ((!remainingItems[index].isclosed.value) && remainingItems[index + 1] && RELATED_ITEM_TYPE[remainingItems[index + 1].itemtype.value])
isRelatedItem(remainingItems, index + 1, ACCUMULATOR)
// ACCUMULATOR.push(remainingItems[index + 1]);
}
else
continue;
}
log.debug("ACCUMULATOR2", ACCUMULATOR)
return ACCUMULATOR;
}, []);
log.debug("groupedItems", groupedItems)
return groupedItems;
}
// create invoice for the item fulfillement
function createInvoice(SoId, newRec) {
/**
* Create an invoice when the item fulfillment is shipped.
* @param SoId {Number} sales order id
* @param newRec {Object} Item fulfillment object
* @return no return value
*/
var orderline = []; var quantity = {}
var SO_RECORD = record.load({
type: record.Type.SALES_ORDER,
id: SoId,
isDynamic: true
});
var SOLines = getSublist(SO_RECORD, 'item').reduce(function (a, v, idx) {
a.push({
item: v.item,
itemtype: v.itemtype,
groupsetup: v.groupsetup,
ingroup: v.ingroup,
rate: v.rate,
description: v.description,
line: v.line,
isclosed: v.isclosed,
index: idx
});
return a;
}, [])
var IFLines = [];
var numLines = newRec.getLineCount({
sublistId: 'item'
});
log.debug("numLines", numLines)
for (var i = 0; i < numLines; i++) {
//fetch the order lines that are added in the item fulfillement and the quantity on it.
var fulfill = newRec.getSublistValue({ fieldId: "itemreceive", sublistId: 'item', line: i })
log.debug('fulfill', fulfill)
if (fulfill) {
var ordLine = newRec.getSublistValue({ fieldId: "orderline", sublistId: 'item', line: i });
orderline.push(ordLine);
IFLines.push({
quantity: { value: newRec.getSublistValue({ fieldId: "quantity", sublistId: 'item', line: i }) },
orderline: { value: ordLine },
// index: i
})
quantity[ordLine] = newRec.getSublistValue({ fieldId: "quantity", sublistId: 'item', line: i });
}
}
log.debug('orderline', orderline)
log.debug('quantity[ordLine]', quantity[ordLine])
const SKIP_ITEM_TYPE = {
'Group': 'Group',
'EndGroup': 'EndGroup',
};
var MAP_ARRAY = lineMap(IFLines, SOLines)
if (MAP_ARRAY.length < 1) //If there is no Mapped Items, returns false
return false;
//creating the invoice by transforming the sales order
var objRecord = record.transform({
fromType: 'salesorder',
fromId: SoId,
toType: 'invoice',
isDynamic: true,
});
var numLines1 = objRecord.getLineCount({
sublistId: 'item'
});
log.debug('numLines1', numLines1)
//iterating through the item lines of the new invoice object
for (var i = 0; i < objRecord.getLineCount({
sublistId: 'item'
}); i++) {
if (SKIP_ITEM_TYPE[objRecord.getSublistValue({
sublistId: 'item',
fieldId: 'itemtype',
line: i
})])
continue;
mappedLine = MAP_ARRAY.find(function (eachValue) { //Filtering Out the Items using MAP_ARRAY
return parseInt(eachValue.line.value) == parseInt(objRecord.getSublistValue({
sublistId: 'item',
fieldId: 'orderline',
line: i
}));
}) || false;
log.debug('mappedLine', mappedLine)
if (mappedLine) {
if (mappedLine.quantity)
if (mappedLine.quantity.value) {
objRecord = objRecord.selectLine({
sublistId: 'item',
line: i
});
objRecord.setCurrentSublistValue({
sublistId: 'item',
fieldId: 'quantity',
value: mappedLine.quantity.value,
// ignoreFieldChange: true
});
objRecord.commitLine({
sublistId: 'item'
});
}
} else {
objRecord.removeLine({
sublistId: 'item',
line: i,
ignoreRecalc: false
});
i--;
}
}
//remove the item groups that don't have any items in it.
for (var index = 0; index < objRecord.getLineCount({
sublistId: 'item'
}); index++) { // loop through all the line items
if (objRecord.getSublistValue({
sublistId: 'item',
fieldId: 'itemtype',
line: index
}) === 'Group') {
var ItemType = objRecord.getSublistValue({
sublistId: 'item',
fieldId: 'itemtype',
line: index
});
var itemTypeOnNextLine = objRecord.getSublistValue({
sublistId: 'item',
fieldId: 'itemtype',
line: index + 1
})
if (ItemType === 'Group' && itemTypeOnNextLine === 'EndGroup') {
// remove Item Group
objRecord.removeLine({
sublistId: 'item',
line: index,
ignoreRecalc: false
});
index--;
}
}
}
var recordId = objRecord.save({
enableSourcing: true,
ignoreMandatoryFields: true
});
log.debug('recordId', recordId)
//store the invoice id in the item fulfillment in a custom hidden field
// newRec.setValue({ fieldId: "custbody_jj_invoice_link", value: recordId })
var id = record.submitFields({
type: record.Type.ITEM_FULFILLMENT,
id: newRec.id,
values: {
custbody_jj_invoice_link: recordId
},
options: {
enableSourcing: false,
ignoreMandatoryFields: true
}
});
log.debug('id', id)
}
function SalesOrderSearch(id) {
var SoResult = {};
var transactionSearchObj = search.create({
type: "transaction",
filters:
[
["mainline", "is", "T"],
"AND",
["internalid", "anyof", id]
],
columns:
[
search.createColumn({ name: "type", label: "Type" }),
search.createColumn({ name: "source", label: "Source" }),
search.createColumn({ name: "custbody_fa_channel", label: "FarApp Marketplace/Cart" }),
search.createColumn({ name: "custbody_fa_channel_order", label: "FarApp Marketplace/Cart Order Number" }),
search.createColumn({ name: "datecreated", label: "Date Created" })
]
});
var searchResultCount = transactionSearchObj.runPaged().count;
log.debug("transactionSearchObj result count", searchResultCount);
transactionSearchObj.run().each(function (result) {
// .run().each has a limit of 4,000 results
SoResult.type = result.getText({ name: "type", label: "Type" })
SoResult.source = result.getValue({ name: "source", label: "Source" })
SoResult.farApp = result.getValue({ name: "custbody_fa_channel", label: "FarApp Marketplace/Cart" })
SoResult.webOrdId = result.getValue({ name: "custbody_fa_channel_order", label: "FarApp Marketplace/Cart Order Number" })
SoResult.created = result.getValue({ name: "datecreated", label: "Date Created" })
});
return SoResult;
}
//finding the all deposite applications related to the invoice
function depositApplication(id) {
var depAppl = []; var status;
var invoiceSearchObj = search.create({
type: "invoice",
filters:
[
["type", "anyof", "CustInvc"],
"AND",
["applyingtransaction.type", "anyof", "DepAppl"],
"AND",
["internalid", "anyof", id]
],
columns:
[
search.createColumn({
name: "internalid",
join: "applyingTransaction",
label: "Internal ID"
}),
search.createColumn({ name: "statusref", label: "Status" })
]
});
var searchResultCount = invoiceSearchObj.runPaged().count;
log.debug("invoiceSearchObj result count", searchResultCount);
invoiceSearchObj.run().each(function (result) {
// .run().each has a limit of 4,000 results
depAppl.push(result.getValue({
name: "internalid",
join: "applyingTransaction",
label: "Internal ID"
}))
status = result.getValue({ name: "statusref", label: "Status" })
return true;
});
return [depAppl, status];
}
return {
beforeSubmit: beforeSubmit,
afterSubmit: afterSubmit
}
});
if (typeof Object.assign != 'function') {
Object.defineProperty(Object, "assign", {
value: function assign(target, varArgs) {
'use strict';
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object');
}
var to = Object(target);
for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];
if (nextSource != null) {
for (var nextKey in nextSource) {
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
writable: true,
configurable: true
});
}
if (!Array.prototype.find) {
Object.defineProperty(Array.prototype, 'find', {
value: function (predicate) {
if (this == null) {
throw new TypeError('"this" is null or not defined');
}
var o = Object(this);
var len = o.length >>> 0;
if (typeof predicate !== 'function') {
throw new TypeError('predicate must be a function');
}
var thisArg = arguments[1];
var k = 0;
while (k < len) {
var kValue = o[k];
if (predicate.call(thisArg, kValue, k, o)) {
return kValue;
}
k++;
}
return undefined;
},
configurable: true,
writable: true
});
}