Automatic invoice creation from Item fulfillment

When the fulfilment is generated and designated as shipped, a shipping label is produced, and a tracking number is appended to the item fulfilment record. This action signifies the item has shipped or released. The requirement involves the automatic generation of invoices as soon as order fulfilment is created.  

In cases of partial fulfilment, the company faces a scenario where they invoice the customer in advance based on a percentage. The remaining amount is billed when the actual shipment takes place. The company’s requirement is to generate invoices for both partial and complete order fulfilment instances. An automatic invoice needs to be created in accordance with the item fulfilment we create. Since the standard NetSuite functionality cannot fulfil this requirement, customization is necessary. 

/**
 * @NApiVersion 2.1
 * @NScriptType UserEventScript
 */
/*************************************************************************************
 ***********
 * MynTahl Corporation DBA East Electronics-US-NS
 *
 * MYNE-19 : Automated creation of invoices
 *
 *
 *************************************************************************************
 ***********
 *
 * Author: Jobin and Jismi IT Services LLP
 *
 * Date Created : 19-October-2023
 *
 * Description: To automate the creation of an invoice when an Item Fulfillment (IF) is generated with a "Shipped" status.
 *
 * REVISION HISTORY
 *
 * @version 1.0 MYNE-19 : 19-October-2023 : Created the initial build by JJ0125
 *
 *
 *************************************************************************************
 **********/
define(['N/record', 'N/search'],

    (record, search) => {
        /**
        * 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
        */
        function createInvoice(SoId, newRec) {
            try {
                let orderline = []; let quantity = {}
                let SO_RECORD = record.load({
                    type: record.Type.SALES_ORDER,
                    id: SoId,
                    isDynamic: true
                });
                let SOLines = getSublist(SO_RECORD, 'item').reduce(function (a, v, idx) {
                    a.push({
                        item: v.item,
                        itemtype: v.itemtype,
                        quantityfulfilled: v.quantityfulfilled,
                        quantitybilled: v.quantitybilled,
                        rate: v.rate,
                        line: v.line,
                        isclosed: v.isclosed,
                        index: idx
                    });
                    return a;
                }, [])
                let IFLines = [];
                let numLines = newRec.getLineCount({
                    sublistId: 'item'
                });
                for (let i = 0; i < numLines; i++) {
                    let fulfill = newRec.getSublistValue({ fieldId: "itemreceive", sublistId: 'item', line: i })
                    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 },
                        })
                        quantity[ordLine] = newRec.getSublistValue({ fieldId: "quantity", sublistId: 'item', line: i });
                    }

                }
                const SKIP_ITEM_TYPE = {
                    'Group': 'Group',
                    'EndGroup': 'EndGroup',
                };

                let MAP_ARRAY = lineMap(IFLines, SOLines);
                //If there is no Mapped Items, returns false
                if (MAP_ARRAY.length < 1)
                    return false;

                //creating the invoice by transforming the sales order
                let objRecord = record.transform({
                    fromType: 'salesorder',
                    fromId: SoId,
                    toType: 'invoice',
                    isDynamic: true,
                });

                let numLines1 = objRecord.getLineCount({
                    sublistId: 'item'
                });
                log.debug('numLines1', numLines1)

                //iterating through the item lines of the new invoice object
                for (let i = 0; i < numLines1; i++) {
                    if (SKIP_ITEM_TYPE[objRecord.getSublistValue({
                        sublistId: 'item',
                        fieldId: 'itemtype',
                        line: i
                    })])
                        continue;
                    //Filtering Out the Items using MAP_ARRAY
                    mappedLine = MAP_ARRAY.find(function (eachValue) {
                        return parseInt(eachValue.line.value) == parseInt(objRecord.getSublistValue({
                            sublistId: 'item',
                            fieldId: 'orderline',
                            line: i
                        }));
                    }) || false;
                    if (mappedLine) {
                        objRecord = objRecord.selectLine({
                            sublistId: 'item',
                            line: i
                        });
                        objRecord.setCurrentSublistValue({
                            sublistId: 'item',
                            fieldId: 'quantity',
                            value: mappedLine.quantityfulfilled.value - mappedLine.quantitybilled.value,
                        });
                        objRecord.commitLine({
                            sublistId: 'item'
                        });
                    } else {
                        objRecord.removeLine({
                            sublistId: 'item',
                            line: i,
                            ignoreRecalc: false
                        });
                        i--;

                    }
                }

                let recordId = objRecord.save({
                    enableSourcing: true,
                    ignoreMandatoryFields: true
                });
                log.debug('recordId', recordId)

                //store the invoice id in the item fulfillment in a custom field
                let id = record.submitFields({
                    type: record.Type.ITEM_FULFILLMENT,
                    id: newRec.id,
                    values: {
                        custbody_jj_invoice_reference: recordId
                    },
                    options: {
                        enableSourcing: false,
                        ignoreMandatoryFields: true
                    }
                });
            }
            catch (e) {
                log.error("error @createInvoicefunction", e);
                record.submitFields({
                    type: record.Type.ITEM_FULFILLMENT,
                    id: newRec.id,
                    values: {
                        'custbody_jj_auto_invoice_error': e.message,
                    },
                    options: {
                        enableSourcing: false,
                        ignoreMandatoryFields: true
                    }
                });
                return {};
            }
        }

        /**
        * Map Item FulFillment Item Lines To Sales Order Item Lines and attach all the related items 
        * 
        * @param IF_LINES{Array[Object]} Item Fulfillment Line Items
        * @param SO_LINES {Array[Object]} Sales Order Line Items
        * @return {Array[Object]}
        */
        function lineMap(IF_LINES, SO_LINES) {
            try {
                //Map items between Item Fulfillment and Sales Order, contains only Item FulFillment items
                var mappedItems = IF_LINES.map(function (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);
                    return ACCUMULATOR;
                }, []);
                return groupedItems;
            }
            catch (e) {
                log.error("error @lineMap", e);
            }
        }
        /**
         * Get Sublist fields and their values
         * 
         * @param recordObj {Object} Initialised Object reference to record
         * @param sublistId {String} sublistId on the Object
         * @return {Array[Object]} 
         */
        function getSublist(recordObj, sublistId) {
            try {
                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;
            }
            catch (e) {
                log.error("error @getSublist", e);
            }
        }


        /**
         * Defines the function definition that is executed after record is submitted.
         * @param {Object} scriptContext
         * @param {Record} scriptContext.newRecord - New record
         * @param {Record} scriptContext.oldRecord - Old record
         * @param {string} scriptContext.type - Trigger type; use values from the context.UserEventType enum
         * @since 2015.2
         */
        const afterSubmit = (scriptContext) => {
            try {
                let oldIFRecord = scriptContext.oldRecord;
                let newIFRecord = scriptContext.newRecord;
                let newItemFulfillmentStatus = newIFRecord.getValue({ fieldId: "shipstatus" });
                let oldItemFulfillmentStatus = scriptContext.type == scriptContext.UserEventType.CREATE ? '' : oldIFRecord.getValue({ fieldId: 'shipstatus' })
                if ((scriptContext.type === scriptContext.UserEventType.CREATE && newItemFulfillmentStatus === "C") || (scriptContext.type === scriptContext.UserEventType.EDIT && (oldItemFulfillmentStatus != "C" && newItemFulfillmentStatus == "C")) || scriptContext.type === scriptContext.UserEventType.SHIP) {
                    let salesOrderId = newIFRecord.getValue({ fieldId: "createdfrom" });
                    let subsidiary = newIFRecord.getValue({
                        fieldId: 'subsidiary'
                    })
                    let invRefId = newIFRecord.getValue({
                        fieldId: "custbody_jj_invoice_reference"
                    });
                    let soForm = search.lookupFields({
                        type: search.Type.TRANSACTION,
                        id: salesOrderId,
                        columns: ["customform"]
                    });
                    let soCustomForm = soForm["customform"][0].value;
                    if (!invRefId && subsidiary == 3 && soCustomForm == 306) {
                        createInvoice(salesOrderId, newIFRecord)
                    }
                }
            }
            catch (e) {
                log.error("error @aftersubmit", e);
            }
        }
        return { 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
    });
}

Leave a comment

Your email address will not be published. Required fields are marked *