Skip to main content

SuiteScript Tests - Client & User Event

This page contains QuickMart Client Script and User Event test cases.


Test Case #10: Client Script - Loyalty Discount Calculator

Objective: Calculate and apply customer loyalty discount in real-time on Sales Order.

Scenario

Mike (Sales Rep) needs to see the customer's loyalty tier and applicable discount immediately when selecting a customer on a Sales Order.

Script Configuration

/**
* @NApiVersion 2.1
* @NScriptType ClientScript
* @NModuleScope SameAccount
*
* QuickMart - Loyalty Discount Calculator
* Applies discount based on customer loyalty tier
*/
define(['N/search', 'N/currentRecord'], function(search, currentRecord) {

// Discount percentages by tier
const TIER_DISCOUNTS = {
1: 0, // Bronze - 0%
2: 5, // Silver - 5%
3: 10, // Gold - 10%
4: 15 // Platinum - 15%
};

function pageInit(context) {
if (context.mode === 'create') {
console.log('QuickMart Loyalty Script loaded');
}
}

function fieldChanged(context) {
var rec = context.currentRecord;

// When customer changes, lookup loyalty and apply discount
if (context.fieldId === 'entity') {
var customerId = rec.getValue('entity');
if (!customerId) return;

var loyaltyData = getLoyaltyData(customerId);

if (loyaltyData) {
// Show loyalty info to user
alert('Customer Tier: ' + loyaltyData.tierName +
'\nPoints: ' + loyaltyData.points +
'\nDiscount: ' + loyaltyData.discount + '%');

// Set discount on order (if discount field exists)
try {
rec.setValue({
fieldId: 'discountrate',
value: loyaltyData.discount
});
} catch (e) {
console.log('Discount field not available: ' + e.message);
}
}
}
}

function getLoyaltyData(customerId) {
var result = null;

search.create({
type: 'customrecord_cust_loyalty',
filters: [
['custrecord_cl_customer', 'is', customerId]
],
columns: [
'custrecord_cl_tier',
'custrecord_cl_points'
]
}).run().each(function(row) {
var tier = row.getValue('custrecord_cl_tier');
result = {
tier: tier,
tierName: row.getText('custrecord_cl_tier'),
points: row.getValue('custrecord_cl_points'),
discount: TIER_DISCOUNTS[tier] || 0
};
return false; // First result only
});

return result;
}

function saveRecord(context) {
// Optional: Confirm before saving with discount
return true;
}

return {
pageInit: pageInit,
fieldChanged: fieldChanged,
saveRecord: saveRecord
};
});

Deployment

Script Record:
Name: QuickMart Loyalty Discount CS
ID: customscript_qm_loyalty_cs
Script File: SuiteScripts/QuickMart/qm_loyalty_discount_cs.js

Deployment:
Name: QuickMart Loyalty - Sales Order
ID: customdeploy_qm_loyalty_so
Applies To: Sales Order
Status: Released

Expected Result

  1. User creates new Sales Order
  2. Selects customer "ABC Corp"
  3. Script looks up loyalty record
  4. Alert shows: "Customer Tier: Gold, Points: 1500, Discount: 10%"
  5. Discount rate field auto-populated

Test Case #11: Client Script - Credit Limit Warning

Objective: Warn user when order exceeds customer credit limit.

Script Configuration

/**
* @NApiVersion 2.1
* @NScriptType ClientScript
*
* QuickMart - Credit Limit Warning
* Warns when order total exceeds customer available credit
*/
define(['N/search', 'N/record'], function(search, record) {

function saveRecord(context) {
var rec = context.currentRecord;
var customerId = rec.getValue('entity');
var orderTotal = rec.getValue('total');

if (!customerId || !orderTotal) return true;

// Get customer credit info
var custRecord = record.load({
type: record.Type.CUSTOMER,
id: customerId,
isDynamic: false
});

var creditLimit = custRecord.getValue('creditlimit') || 0;
var balance = custRecord.getValue('balance') || 0;
var availableCredit = creditLimit - balance;

// Check if order exceeds available credit
if (orderTotal > availableCredit && creditLimit > 0) {
var proceed = confirm(
'WARNING: Credit Limit Exceeded!\n\n' +
'Credit Limit: $' + creditLimit.toFixed(2) + '\n' +
'Current Balance: $' + balance.toFixed(2) + '\n' +
'Available Credit: $' + availableCredit.toFixed(2) + '\n' +
'Order Total: $' + orderTotal.toFixed(2) + '\n\n' +
'Do you want to proceed anyway?'
);

if (!proceed) {
return false; // Cancel save
}
}

return true;
}

return {
saveRecord: saveRecord
};
});

Expected Result

  1. Order total > customer available credit
  2. Warning dialog appears with credit details
  3. User can confirm to proceed or cancel

Test Case #12: User Event - Update Loyalty Points

Objective: Add loyalty points after invoice is created.

Scenario

After an invoice is saved, the customer should receive 1 point per $10 spent.

Script Configuration

/**
* @NApiVersion 2.1
* @NScriptType UserEventScript
*
* QuickMart - Update Loyalty Points
* Awards points after invoice creation
*/
define(['N/record', 'N/search', 'N/log'], function(record, search, log) {

const POINTS_PER_DOLLAR = 0.1; // 1 point per $10

function afterSubmit(context) {
if (context.type !== context.UserEventType.CREATE) return;

var invoice = context.newRecord;
var customerId = invoice.getValue('entity');
var invoiceTotal = invoice.getValue('total');

if (!customerId || !invoiceTotal) return;

// Calculate points earned
var pointsEarned = Math.floor(invoiceTotal * POINTS_PER_DOLLAR);
if (pointsEarned <= 0) return;

// Find customer's loyalty record
var loyaltyId = findLoyaltyRecord(customerId);

if (loyaltyId) {
// Update existing loyalty record
updateLoyaltyPoints(loyaltyId, pointsEarned, invoiceTotal);
} else {
// Create new loyalty record
createLoyaltyRecord(customerId, pointsEarned, invoiceTotal);
}

log.audit('Loyalty Points', 'Customer ' + customerId +
' earned ' + pointsEarned + ' points');
}

function findLoyaltyRecord(customerId) {
var results = search.create({
type: 'customrecord_cust_loyalty',
filters: [['custrecord_cl_customer', 'is', customerId]],
columns: ['internalid']
}).run().getRange({ start: 0, end: 1 });

return results.length > 0 ? results[0].id : null;
}

function updateLoyaltyPoints(loyaltyId, pointsEarned, invoiceTotal) {
var loyalty = record.load({
type: 'customrecord_cust_loyalty',
id: loyaltyId
});

var currentPoints = loyalty.getValue('custrecord_cl_points') || 0;
var totalSpent = loyalty.getValue('custrecord_cl_totalspent') || 0;

loyalty.setValue('custrecord_cl_points', currentPoints + pointsEarned);
loyalty.setValue('custrecord_cl_totalspent', totalSpent + invoiceTotal);

// Check for tier upgrade
var newTier = calculateTier(currentPoints + pointsEarned);
loyalty.setValue('custrecord_cl_tier', newTier);

loyalty.save();
}

function calculateTier(points) {
if (points >= 5000) return 4; // Platinum
if (points >= 2000) return 3; // Gold
if (points >= 500) return 2; // Silver
return 1; // Bronze
}

function createLoyaltyRecord(customerId, points, spent) {
var loyalty = record.create({
type: 'customrecord_cust_loyalty'
});

loyalty.setValue('custrecord_cl_customer', customerId);
loyalty.setValue('custrecord_cl_points', points);
loyalty.setValue('custrecord_cl_totalspent', spent);
loyalty.setValue('custrecord_cl_tier', calculateTier(points));
loyalty.setValue('custrecord_cl_joindate', new Date());

loyalty.save();
}

return {
afterSubmit: afterSubmit
};
});

Expected Result

  1. Create invoice for $500
  2. After save, customer receives 50 points
  3. Loyalty record updated or created

Test Case #13: User Event - Push Order to Warehouse (Integration)

Objective: Send order details to external warehouse app when order is approved.

Script Configuration

/**
* @NApiVersion 2.1
* @NScriptType UserEventScript
*
* QuickMart - Push Order to Warehouse
* Integration Pattern: PUSH OUT
*/
define(['N/https', 'N/record', 'N/log', 'N/runtime'], function(https, record, log, runtime) {

const WAREHOUSE_API_URL = 'https://quickmart-warehouse-api.appsvein.workers.dev/api/orders';

function afterSubmit(context) {
// Only on create or status change to Pending Fulfillment
if (context.type !== context.UserEventType.EDIT &&
context.type !== context.UserEventType.CREATE) return;

var order = context.newRecord;
var status = order.getValue('orderstatus');

// B = Pending Fulfillment
if (status !== 'B') return;

var payload = buildPayload(order);

try {
var response = https.post({
url: WAREHOUSE_API_URL,
headers: {
'Content-Type': 'application/json',
'X-API-Key': runtime.getCurrentScript().getParameter('custscript_warehouse_api_key')
},
body: JSON.stringify(payload)
});

log.audit('Warehouse Push', 'Order ' + order.id + ' sent. Response: ' + response.code);

// Log to Integration Log custom record
logIntegration(order.id, 'PUSH_OUT', 'SUCCESS', response.body);

} catch (e) {
log.error('Warehouse Push Error', e.message);
logIntegration(order.id, 'PUSH_OUT', 'ERROR', e.message);
}
}

function buildPayload(order) {
var lines = [];
var lineCount = order.getLineCount('item');

for (var i = 0; i < lineCount; i++) {
lines.push({
item: order.getSublistText('item', 'item', i),
quantity: order.getSublistValue('item', 'quantity', i),
location: order.getSublistText('item', 'location', i)
});
}

return {
orderId: order.getValue('tranid'),
nsInternalId: order.id,
customer: order.getText('entity'),
shipAddress: order.getValue('shipaddress'),
priority: order.getValue('custbody_priority_order'),
lines: lines
};
}

function logIntegration(recordId, pattern, status, message) {
var integLog = record.create({ type: 'customrecord_integration_log' });
integLog.setValue('name', 'Order Push: ' + recordId);
integLog.setValue('custrecord_il_pattern', pattern);
integLog.setValue('custrecord_il_status', status);
integLog.setValue('custrecord_il_message', message.substring(0, 4000));
integLog.save();
}

return {
afterSubmit: afterSubmit
};
});

Test Case #14: User Event - Calculate PR Line Total

Objective: Calculate line totals and header total on Purchase Request.

Script Configuration

/**
* @NApiVersion 2.1
* @NScriptType UserEventScript
*
* QuickMart - Purchase Request Line Calculator
* Calculates line total and updates header total
*/
define(['N/record', 'N/search', 'N/log'], function(record, search, log) {

// On Purchase Request LINE record
function afterSubmit(context) {
var line = context.newRecord;
var parentId = line.getValue('custrecord_prl_parent');

if (!parentId) return;

// Recalculate header total
updateHeaderTotal(parentId);
}

function updateHeaderTotal(headerId) {
// Sum all lines for this header
var total = 0;

search.create({
type: 'customrecord_purch_req_line',
filters: [
['custrecord_prl_parent', 'is', headerId]
],
columns: ['custrecord_prl_total']
}).run().each(function(result) {
total += parseFloat(result.getValue('custrecord_prl_total')) || 0;
return true;
});

// Update header
record.submitFields({
type: 'customrecord_purch_req',
id: headerId,
values: {
'custrecord_pr_total': total
}
});

log.audit('PR Total Updated', 'PR ' + headerId + ' = $' + total);
}

return {
afterSubmit: afterSubmit
};
});

Test Case #27: User Event - Auto-Create PO from Approved PR

Objective: When Purchase Request status changes to "Approved", automatically create a Purchase Order.

Scenario

After Sarah approves a Purchase Request, the PO should be created automatically without Lisa (Admin) having to manually copy data.

Script Configuration

/**
* @NApiVersion 2.1
* @NScriptType UserEventScript
*
* QuickMart - Auto-Create PO from Approved PR
* Triggers when PR status changes to "Approved"
*/
define(['N/record', 'N/search', 'N/log'], function(record, search, log) {

function afterSubmit(context) {
if (context.type !== context.UserEventType.EDIT) return;

var pr = context.newRecord;
var oldStatus = context.oldRecord.getValue('custrecord_pr_status');
var newStatus = pr.getValue('custrecord_pr_status');

// Only trigger when status changes TO "Approved" (value = 3)
if (oldStatus === newStatus || newStatus !== '3') return;

try {
var poId = createPurchaseOrder(pr);

// Link PO back to PR
record.submitFields({
type: 'customrecord_purch_req',
id: pr.id,
values: {
'custrecord_pr_linked_po': poId
}
});

log.audit('PO Created', 'PR ' + pr.id + ' -> PO ' + poId);

} catch (e) {
log.error('PO Creation Failed', e.message);
}
}

function createPurchaseOrder(pr) {
var po = record.create({
type: record.Type.PURCHASE_ORDER,
isDynamic: true
});

// Set vendor (from first line item's preferred vendor, or default)
var vendor = getVendorFromPRLines(pr.id);
po.setValue('entity', vendor);

// Reference original PR
po.setValue('memo', 'Auto-created from PR: ' + pr.getValue('name'));
po.setValue('custbody_source_pr', pr.id);

// Add lines from PR
var lines = getPRLines(pr.id);
lines.forEach(function(line) {
po.selectNewLine('item');
po.setCurrentSublistValue('item', 'item', line.item);
po.setCurrentSublistValue('item', 'quantity', line.qty);
po.setCurrentSublistValue('item', 'rate', line.rate);
po.commitLine('item');
});

return po.save();
}

function getPRLines(prId) {
var lines = [];

search.create({
type: 'customrecord_purch_req_line',
filters: [['custrecord_prl_parent', 'is', prId]],
columns: ['custrecord_prl_item', 'custrecord_prl_qty', 'custrecord_prl_rate']
}).run().each(function(result) {
lines.push({
item: result.getValue('custrecord_prl_item'),
qty: result.getValue('custrecord_prl_qty'),
rate: result.getValue('custrecord_prl_rate')
});
return true;
});

return lines;
}

function getVendorFromPRLines(prId) {
// Get preferred vendor from first item
var lines = getPRLines(prId);
if (lines.length === 0) return null;

var itemLookup = search.lookupFields({
type: search.Type.ITEM,
id: lines[0].item,
columns: ['preferredvendor']
});

return itemLookup.preferredvendor ? itemLookup.preferredvendor[0].value : null;
}

return {
afterSubmit: afterSubmit
};
});

Required Custom Field

Add to Purchase Request record:

FieldTypeIDPurpose
Linked POList/Recordcustrecord_pr_linked_poPoints to created PO

Test Steps

  1. Create Purchase Request with lines
  2. Submit for approval (status = "Pending Approval")
  3. Manager approves (status = "Approved")
  4. Verify: Purchase Order automatically created
  5. Verify: PR has link to new PO

Test Case #29: Integration - Push Approved PR to Procurement System

Objective: Push approved Purchase Requests to external procurement/ERP system.

Scenario

QuickMart uses an external procurement system for supplier management. When PRs are approved, they need to sync to that system.

Architecture

PUSH OUT - PR TO PROCUREMENT
-------------------------------------------------------------------------------

NetSuite External Procurement
+------------------+ +------------------+
| | HTTP POST | |
| Purchase Request | -----------------> | /api/requests |
| Status: Approved | JSON Payload | |
| | | - Queue for RFQ |
| User Event: | Response: 200 OK | - Supplier Match |
| afterSubmit | <----------------- | |
| | | |
+------------------+ +------------------+

Script Configuration

/**
* @NApiVersion 2.1
* @NScriptType UserEventScript
*
* QuickMart - Push PR to Procurement System
* Integration Pattern: PUSH OUT
*/
define(['N/https', 'N/record', 'N/search', 'N/log', 'N/runtime'], function(https, record, search, log, runtime) {

const PROCUREMENT_API = 'https://quickmart-warehouse-api.appsvein.workers.dev/api/requests';
const API_KEY_PARAM = 'custscript_external_api_key';

function afterSubmit(context) {
if (context.type !== context.UserEventType.EDIT) return;

var pr = context.newRecord;
var oldStatus = context.oldRecord.getValue('custrecord_pr_status');
var newStatus = pr.getValue('custrecord_pr_status');

// Only push when approved
if (oldStatus === newStatus || newStatus !== '3') return;

try {
var payload = buildPayload(pr);
var response = pushToProcurement(payload);

// Log integration
logIntegration(pr.id, payload, response);

// Store external ID
if (response.success) {
record.submitFields({
type: 'customrecord_purch_req',
id: pr.id,
values: {
'custrecord_pr_external_id': response.request_id
}
});
}

} catch (e) {
log.error('Procurement Push Failed', e.message);
}
}

function buildPayload(pr) {
var lines = getPRLines(pr.id);

return {
source: 'NETSUITE',
source_id: pr.id,
pr_number: pr.getValue('custrecord_pr_number'),
requestor: pr.getText('custrecord_pr_requestor'),
department: pr.getText('custrecord_pr_dept'),
justification: pr.getValue('custrecord_pr_justify'),
total_amount: pr.getValue('custrecord_pr_total'),
approved_by: pr.getText('custrecord_pr_approver'),
approved_date: pr.getValue('custrecord_pr_approval_date'),
items: lines.map(function(line) {
return {
item_id: line.item,
item_name: line.itemName,
quantity: line.qty,
unit_price: line.rate,
total: line.amount
};
})
};
}

function pushToProcurement(payload) {
var response = https.post({
url: PROCUREMENT_API,
headers: {
'Content-Type': 'application/json',
'X-API-Key': runtime.getCurrentScript().getParameter(API_KEY_PARAM)
},
body: JSON.stringify(payload)
});

return JSON.parse(response.body);
}

function getPRLines(prId) {
var lines = [];

search.create({
type: 'customrecord_purch_req_line',
filters: [['custrecord_prl_parent', 'is', prId]],
columns: [
'custrecord_prl_item',
'custrecord_prl_qty',
'custrecord_prl_rate',
'custrecord_prl_amount'
]
}).run().each(function(result) {
var itemLookup = search.lookupFields({
type: search.Type.ITEM,
id: result.getValue('custrecord_prl_item'),
columns: ['displayname', 'itemid']
});

lines.push({
item: result.getValue('custrecord_prl_item'),
itemName: itemLookup.displayname || itemLookup.itemid,
qty: result.getValue('custrecord_prl_qty'),
rate: result.getValue('custrecord_prl_rate'),
amount: result.getValue('custrecord_prl_amount')
});
return true;
});

return lines;
}

function logIntegration(prId, payload, response) {
record.create({
type: 'customrecord_integration_log',
values: {
'custrecord_log_direction': 'OUTBOUND',
'custrecord_log_pattern': 'PUSH_OUT',
'custrecord_log_endpoint': PROCUREMENT_API,
'custrecord_log_payload': JSON.stringify(payload).substring(0, 3000),
'custrecord_log_response': JSON.stringify(response).substring(0, 3000),
'custrecord_log_status': response.success ? 'SUCCESS' : 'ERROR',
'custrecord_log_related': prId
}
}).save();
}

return {
afterSubmit: afterSubmit
};
});

Sample Payload

{
"source": "NETSUITE",
"source_id": 123,
"pr_number": "PR-00047",
"requestor": "David Lee",
"department": "Operations",
"justification": "Running low on packaging supplies",
"total_amount": 1250.00,
"approved_by": "Sarah Chen",
"approved_date": "2024-03-15",
"items": [
{
"item_id": 456,
"item_name": "Cardboard Boxes (Med)",
"quantity": 200,
"unit_price": 2.50,
"total": 500.00
},
{
"item_id": 457,
"item_name": "Packing Tape (6-pack)",
"quantity": 50,
"unit_price": 8.00,
"total": 400.00
}
]
}

Test Steps

  1. Create and approve a Purchase Request
  2. Verify: Integration log created
  3. Verify: External system receives the request
  4. Verify: PR has external ID stored

Test Case #31: User Event - Push Fulfillment Status to Shopify

Objective: When an Item Fulfillment is created, update the Shopify order status.

Scenario

Lisa (Admin) wants Shopify customers to see their order as "Fulfilled" immediately after NetSuite ships it.

Script Configuration

/**
* @NApiVersion 2.1
* @NScriptType UserEventScript
*
* QuickMart - Push Fulfillment Status to Shopify
*/
define(['N/https', 'N/search', 'N/log', 'N/runtime'], function(https, search, log, runtime) {

const SHOPIFY_API = 'https://quickmart-warehouse-api.appsvein.workers.dev/api/shopify/orders/';
const API_KEY_PARAM = 'custscript_external_api_key';

function afterSubmit(context) {
if (context.type !== context.UserEventType.CREATE) return;

var fulfillment = context.newRecord;
var salesOrderId = fulfillment.getValue('createdfrom');
if (!salesOrderId) return;

var lookup = search.lookupFields({
type: search.Type.SALES_ORDER,
id: salesOrderId,
columns: ['custbody_external_order_id']
});

var externalId = lookup.custbody_external_order_id;
if (!externalId) return;

var payload = {
order: { fulfillment_status: 'fulfilled' }
};

var response = https.put({
url: SHOPIFY_API + externalId + '.json',
headers: {
'Content-Type': 'application/json',
'X-API-Key': runtime.getCurrentScript().getParameter(API_KEY_PARAM)
},
body: JSON.stringify(payload)
});

log.audit('Shopify Update', 'Order ' + externalId + ' updated. Code: ' + response.code);
}

return { afterSubmit: afterSubmit };
});

Expected Result

  1. Create Sales Order with custbody_external_order_id set
  2. Fulfill the order in NetSuite
  3. Verify: Shopify order status updates to Fulfilled