netsuite

User Event: Before and After Submit events

Syntax#

  • beforeSubmit(type) // Before Submit, 1.0
  • beforeSubmit(scriptContext) // Before Submit, 2.0
  • afterSubmit(type) // After Submit, 1.0
  • afterSubmit(scriptContext) // After Submit, 2.0

Parameters#

Parameter Details
SuiteScript 2.0 -
scriptContext {Object}
scriptContext.newRecord {N/record.Record} A reference to the record that is being read from the database. We can use it to modify the field values on the record
scriptContext.oldRecord {N/record.Record} A read-only reference to the previous state of the record. We can use it to compare to the new values
scriptContext.type {UserEventType} An enumeration of the type of write action being performed
SuiteScript 1.0 -
type {String} The type of write action being performed

Remarks#

beforeSubmit and afterSubmit

These two events are triggered by any database write operation on a record. Any time a user, a script, a CSV import, or a web service request attempts to write a record to the database, the Submit events get fired.

Record actions that trigger both Submit events:

  • Create
  • Edit
  • Delete
  • XEdit (inline edit)
  • Approve
  • Reject
  • Cancel
  • Pack
  • Ship

Record actions that trigger beforeSubmit only:

  • Mark Complete
  • Reassign (support cases)
  • Edit Forecast

Record actions that trigger afterSubmit only:

  • Dropship
  • Special Order
  • Order Items
  • Pay Bills

Typical Use Cases for beforeSubmit

  • Validate record before it is committed to database
  • Permission and restriction checks
  • Last-minute changes before database commit
  • Pull updates from external systems

Typical Use Cases for afterSubmit

  • Email notification of record changes
  • Browser redirection
  • Create/update dependent records
  • Push changes to external systems

User Events do not chain

Code written in User Events will not trigger any User Events on other records. For example, modifying the associated Customer record from the beforeSubmit of a Sales Order record will not trigger the Customer record’s submit events.

NetSuite does this to avoid User Events triggering each other in an infinite loop. If you do need User Events to fire in a chained sequence, other script types (e.g. RESTlets, Suitelets, Scheduled Scripts) will need to be injected in between the events.

Event Handlers return void

The return type of the Submit event handlers is void. Any data returned from our event handler has no effect on the system. We do not need to return anything from our handler function as we cannot actually do anything with its returned value.

!! CAUTION !!

Be very cautious when comparing values between old and new records. Empty fields from the old record are returned as null, while empty fields from the new record are returned as an empty String. This means you cannot simply compare the old with the new, or you will get false positives. Any logic you write must handle the case where one is null and one is an empty String appropriately.

Minimal: Log a message

// 1.0, Revealing Module pattern
var myNamespace = myNamespace || {};

myNamespace.example = (function () {

    /**
     * User Event 1.0 example detailing usage of the Submit events
     *
     * @appliedtorecord employee
     */
    var exports = {};

    function beforeSubmit(type) {
        nlapiLogExecution("DEBUG", "Before Submit", "action=" + type);
    }

    function afterSubmit(type) {
        nlapiLogExecution("DEBUG", "After Submit", "action=" + type);
    }

    exports.beforeSubmit = beforeSubmit;
    exports.afterSubmit = afterSubmit;
    return exports;
})();

// 2.0
define(["N/log"], function (log) {

    /**
     * User Event 2.0 example showing usage of the Submit events
     *
     * @NApiVersion 2.x
     * @NModuleScope SameAccount
     * @NScriptType UserEventScript
     * @appliedtorecord employee
     */
    var exports = {};

    function beforeSubmit(scriptContext) {
        log.debug({
            "title": "Before Submit",
            "details": "action=" + scriptContext.type
        });
    }

    function afterSubmit(scriptContext) {
        log.debug({
            "title": "After Submit",
            "details": "action=" + scriptContext.type
        });
    }

    exports.beforeSubmit = beforeSubmit;
    exports.afterSubmit = afterSubmit;
    return exports;
});

Before Submit: Validate record before it is committed to database

For this example, we want to make sure that any Employee who is marked as a Project Resource also has an appropriate Labor Cost defined.

// 1.0, Revealing Module pattern
var myNamespace = myNamespace || {};
myNamespace.example = (function () {

    /**
     * User Event 1.0 example detailing usage of the Submit events
     *
     * @appliedtorecord employee
     */
    var exports = {};

    function beforeSubmit(type) {
        if (!isEmployeeValid(nlapiGetNewRecord())) {
            throw nlapiCreateError("STOIC_ERR_INVALID_DATA", "Employee data is not valid", true);
        }
    }

    function isEmployeeValid(employee) {
        return (!isProjectResource(employee) || hasValidLaborCost(employee));
    }

    function isProjectResource(employee) {
        return (employee.getFieldValue("isjobresource") === "T");
    }

    function hasValidLaborCost(employee) {
        var laborCost = parseFloat(employee.getFieldValue("laborcost"));

        return (Boolean(laborCost) && (laborCost > 0));
    }

    exports.beforeSubmit = beforeSubmit;
    return exports;
})();

// 2.0
define(["N/error"], function (err) {

    var exports = {};

    /**
     * User Event 2.0 example detailing usage of the Submit events
     *
     * @NApiVersion 2.x
     * @NModuleScope SameAccount
     * @NScriptType UserEventScript
     * @appliedtorecord employee
     */
    function beforeSubmit(scriptContext) {
        if (!isEmployeeValid(scriptContext)) {
            throw err.create({
                "name": "STOIC_ERR_INVALID_DATA",
                "message": "Employee data is not valid",
                "notifyOff": true
            });
        }
    }

    function isEmployeeValid(scriptContext) {
        return (!isProjectResource(scriptContext.newRecord) || hasValidLaborCost(scriptContext.newRecord));
    }

    function isProjectResource(employee) {
        return (employee.getValue({"fieldId" : "isjobresource"}));
    }

    function hasValidLaborCost(employee) {
        var laborCost = employee.getValue({"fieldId" : "laborcost"});

        return (Boolean(laborCost) && (laborCost > 0));
    }

    exports.beforeSubmit = beforeSubmit;
    return exports;
});

Note that we pass references to the new record into our validation because we do not care what the values used to be; we are only concerned with the values that are about to be written to the database. In 2.0, we do that via the scriptContext.newRecord reference, and in 1.0 we call the global function nlapiGetNewRecord.

When the data being submitted is not valid, we create and throw an error. In a beforeSubmit event, in order to prevent the changes from being written to the database, your function must throw an Exception. Often developers try to return false from their function, expecting that to be enough, but that is not sufficient. Error objects are created in 2.0 using the N/error module, and in 1.0 using the global nlapiCreateError function; we then raise an Exception using our created error object with the throw keyword.

After Submit: Determine whether a field was changed

After the record gets stored in the database, we want to inspect what was changed on the record. We’ll do this inspection by comparing values between the old and new record instances.

// 1.0, Revealing Module pattern
var myNamespace = myNamespace || {};
myNamespace.example = (function () {

    /**
     * User Event 1.0 example detailing usage of the Submit events
     *
     * @appliedtorecord employee
     */
    var exports = {};

    function afterSubmit(type) {
        notifySupervisor();
    }

    function notifySupervisor() {
        // Old and New record instances are retrieved from global functions
        var employee = nlapiGetNewRecord();
        var prevEmployee = nlapiGetOldRecord();

        // If Employee Status didn't change, there's nothing to do
        if (!didStatusChange(employee, prevEmployee)) {
            return;
        }

        // Otherwise, continue with business logic...
    }

    function didStatusChange(employee, prevEmployee) {
        var status = employee.getFieldValue("employeestatus");
        var prevStatus = prevEmployee.getFieldValue("employeestatus");

        /* !! Caution !!
         * Empty fields from the Old record come back as `null`
         * Empty fields from the New record come back as an empty String
         * This means  you cannot simply compare the old and new
         */
        return ((prevStatus || status) && (status !== prevStatus));
    }

    exports.afterSubmit = afterSubmit;
    return exports;
})();

// 2.0
define(["N/runtime"], function (runtime) {

    /**
     * User Event 2.0 example detailing usage of the Submit events
     *
     * @NApiVersion 2.x
     * @NModuleScope SameAccount
     * @NScriptType UserEventScript
     * @appliedtorecord employee
     */
    var exports = {};

    function afterSubmit(scriptContext) {
        notifySupervisor(scriptContext);
    }

    function notifySupervisor(scriptContext) {
        // Old and New records are simply properties on scriptContext
        var employee = scriptContext.newRecord;
        var prevEmployee = scriptContext.oldRecord;

        // If Employee Status didn't change, there's nothing to do
        if (!didStatusChange(employee, prevEmployee)) {
            return;
        }

        // Otherwise, continue with business logic...
    }

    function didStatusChange(employee, prevEmployee) {
        var status = employee.getValue({"fieldId" : "employeestatus"});
        var prevStatus = prevEmployee.getValue({"fieldId" : "employeestatus"});

        /* !! Caution !!
         * Empty fields from the Old record come back as `null`
         * Empty fields from the New record come back as an empty String
         * This means  you cannot simply compare the old and new
         */
        return ((prevStatus || status) && (status !== prevStatus));
    }

    exports.afterSubmit = afterSubmit;
    return exports;
});

Be very cautious when comparing values between old and new records. Empty fields from the old record are returned as null, while empty fields from the new record are returned as an empty String. This means you cannot simply compare the old with the new, or you will get false positives. Any logic you write must handle the case where one is null and one is an empty String appropriately.


This modified text is an extract of the original Stack Overflow Documentation created by the contributors and released under CC BY-SA 3.0 This website is not affiliated with Stack Overflow