Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Automatically bill NetSuite sales orders when the deposit total is enough to pay the order.
Author: <>
Description: Automatically bills sales orders (creates invoices) when the deposit
total is enough to pay the order. This ensures the sales order is only
billed when the resulting invoice would be paid in full.
2. User Event
3. Name: Copy Order Fields to Transaction
4. ID: _suitesync_copy_invoice_fields
5. After Submit: afterSubmit
6. Deployments: CustomerDeposit on Create, or CustomerPayment on Create
// Start Utilities
function isZero(obj) {
return parseFloat(obj) == 0.0
function log(msg) {
nlapiLogExecution('DEBUG', msg);
function error(msg) {
nlapiLogExecution('ERROR', msg);
function isEmpty(obj) {
return obj === undefined || obj === null || obj === "";
function absoluteDifference(a, b) {
return Math.abs(parseFloat(a) - parseFloat(b))
function isScriptUsageRemaining() {
var estimatedUsagePerIteration = 100;
var scriptContext = nlapiGetContext();
if(scriptContext.getExecutionContext() != 'scheduledScript') {
log("execution context is not scheduled script, assuming true");
return true;
var hasUsageRemaining = scriptContext.getRemainingUsage() > estimatedUsagePerIteration;
if(!hasUsageRemaining) {
log("script terminating, not enough usage remaining");
return false;
return true;
function rescheduleScript() {
var scriptContext = nlapiGetContext();
// TODO look into nlapiYieldScript, be aware of serialization limitations before implementing!
var schStatus = nlapiScheduleScript(scriptContext.getScriptId(), scriptContext.getDeploymentId());
// End Utilities
function scheduled(type) {
if(type != 'scheduled') return;
function processOpenSalesOrders() {
var filters = [
// NOTE that only "Pending Billing" and "Sales Order:Pending Billing/Partially Fulfilled" are processed
// you may need to change
// F = Pending Billing; E = Pending Billing/Partially Fulfilled
new nlobjSearchFilter( 'status', null, 'anyOf', ['SalesOrd:F', 'SalesOrd:E']),
new nlobjSearchFilter( 'mainline', null, 'is', 'T'),
// NOTE limit search to records modified in the last 10 ten days to avoid picking up "bad" records
// i.e. records that can never be billed for one reason or another
new nlobjSearchFilter( 'lastmodifieddate', null, 'after', 'tenDaysAgo')
var search = nlapiSearchRecord('salesorder', null, filters, []);
if(isEmpty(search)) {
log("no search results");
for(var i = 0; i < search.length; i++) {
if(!isScriptUsageRemaining()) {
var salesOrderId = search[i].getId();
function processSalesOrder(salesOrderInternalId) {
log("processing order: " + salesOrderInternalId);
var salesOrderTotal = nlapiLookupField('salesorder', salesOrderInternalId, 'total');
var depositTotal = getCustomerDepositTotalForSalesOrder(salesOrderInternalId, null);
// NOTE because of JS FPA arithemtic errors we treat less than 0.02 differences as equal
if(parseFloat(depositTotal) >= parseFloat(salesOrderTotal) || absoluteDifference(depositTotal, salesOrderTotal) < 0.02) {
} else {
log("sales order does not have enough deposits: " + salesOrderInternalId + " deposit: " + depositTotal
+ " salesOrderTotal: " + salesOrderTotal)
// NOTE should be used after the caller is sure the SO has not been billed
function getCustomerDepositTotalForSalesOrder(salesOrderId, entityId) {
// NOTE if a CustomerDeposit is created from a SalesOrder it is not possible to apply it against
// an invoice until it's been billed. This means that all deposit associated with a SalesOrder
// are an accurate representation of the total amount pre-paid
// NOTE if a SalesOrder is billed it cannot be re-billed: if the amount is edited and the price increases
// a invoice or other record must be created to account for the additional billed amount
if(isEmpty(entityId)) {
entityId = nlapiLookupField('salesorder', salesOrderId, 'entity');
// if deposit balance is zero then there are no deposits on the user
var depositBalance = nlapiLookupField('customer', entityId, 'depositbalance');
if(isEmpty(depositBalance) || isZero(depositBalance)) {
return 0;
var filters = [
new nlobjSearchFilter('salesorder', null, 'anyof', salesOrderId)
var columns = [
new nlobjSearchColumn('total', null, 'sum')
var results = nlapiSearchRecord(
if(!results) {
log("no deposits found for record: " + salesOrderId)
return 0;
if(results.length > 1) {
error("summary result should only have a single result")
return results[0].getValue('total', null, 'sum');
function billSalesOrder(salesOrderInternalId) {
log("billing sales order: " + salesOrderInternalId);
try {
var billRecord = nlapiTransformRecord('salesorder', salesOrderInternalId, 'invoice');
} catch(e) {
error("error converting sales order to invoice: " + salesOrderInternalId);
try {
} catch (e) {
error("error creating sales order: " + salesOrderInternalId)
if(nlapiGetContext().getExecutionContext() == 'debugger') {
// allows you set a breakpoint at the end of the script

This comment has been minimized.

Copy link

commented Jun 6, 2018

Hi Iloveitaly,

Thank you for sharing your hardwork. I'm a Netsuite user who is trying to streamline a daily repetitive task. I'm trying to understand your script so that I can apply to my situation. It seem that this script is one step ahead of what I'm trying to do. Basically, I'm trying to create invoices base on sales orders that have "pending bill" status before any customer deposits. Will you please give me some directions/advises?

Thank you for your time.


This comment has been minimized.

Copy link
Owner Author

commented Nov 6, 2018

@n0n0n0GH if you are a SuiteSync ( user, reach out to support at Otherwise, I'd recommend you checkout the OpenSuite community for answers to this sort of question:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.