Why Your Payment Strategy Matters (and BlueSnap Can Help)
For businesses strategically focused on expansion and sustained growth, a seamless and efficient payment infrastructure is essential. It directly...
Sign up to hear about Snapshot's latest news and projects!
4 min read
Steve Springer
:
Apr 7, 2026 3:31:41 PM
Pricing in distribution is rarely simple. A single SKU might have a different price depending on the customer relationship, the product category, and the warehouse fulfilling the order. When these variables are managed manually — through spreadsheets, memorized rates, or fragile saved search workarounds — errors creep in. Margins erode quietly. Reps lose confidence in the system and start bypassing it.
For mid-market distributors running NetSuite, the challenge is that the platform's native pricing tools — price levels, quantity schedules, and item-specific pricing — don't natively support a location dimension. When your pricing varies by ship-from warehouse or branch, you need a different approach.
This post walks through how we built a SuiteScript-based solution that automates customer/product/location pricing lookups on transactions, ensuring every line item gets the right price without manual intervention.
The solution has two components: a custom record that stores pricing data and a client script that queries it in real time during order entry.
We created a custom record type — let's call it "Customer Location Pricing" — with the following key fields:
This structure gives the pricing team a flat, maintainable table. They can filter by customer, bulk-update by item, and audit changes through NetSuite's standard system notes. No developer involvement required for day-to-day rate changes.
The Client Script
The client script deploys on Sales Orders (and optionally Quotes and Invoices) and hooks into two key events:
postSourcing — fires after a field's dependent values have been set. When a rep selects an item on a line, postSourcing lets us read the item value after NetSuite has finished populating its defaults, then query our custom record for the correct price.
fieldChanged — fires when the customer or location changes at the header level. This triggers a full reprice loop across all existing lines.
Here's a simplified version of the pattern:
/**
* @NApiVersion 2.1
* @NScriptType ClientScript
* @NModuleScope SameAccount
*/
define(['N/search', 'N/currentRecord'], (search, currentRecord) => {
function getCustomerPrice(customerId, itemId, locationId) {
let price = null;
search.create({
type: 'customrecord_cust_loc_pricing',
filters: [
['custrecord_clp_customer', 'anyof', customerId],
'AND',
['custrecord_clp_item', 'anyof', itemId],
'AND',
['custrecord_clp_location', 'anyof', locationId],
'AND',
['custrecord_clp_effective', 'onorbefore', 'today'],
'AND',
['custrecord_clp_expiration', 'onorafter', 'today']
],
columns: ['custrecord_clp_price']
}).run().each(result => {
price = result.getValue('custrecord_clp_price');
return false; // first match wins
});
return price;
}
function postSourcing(context) {
if (context.sublistId === 'item'
&& context.fieldId === 'item') {
const rec = context.currentRecord;
const customerId = rec.getValue('entity');
const locationId = rec.getValue('location');
const itemId = rec.getCurrentSublistValue({
sublistId: 'item', fieldId: 'item'
});
if (customerId && itemId && locationId) {
const price = getCustomerPrice(
customerId, itemId, locationId
);
if (price) {
rec.setCurrentSublistValue({
sublistId: 'item',
fieldId: 'rate',
value: price
});
}
}
}
}
function fieldChanged(context) {
const rec = context.currentRecord;
if (['entity', 'location'].includes(context.fieldId)) {
const customerId = rec.getValue('entity');
const locationId = rec.getValue('location');
const lineCount = rec.getLineCount({ sublistId: 'item' });
for (let i = 0; i < lineCount; i++) {
rec.selectLine({ sublistId: 'item', line: i });
const itemId = rec.getCurrentSublistValue({
sublistId: 'item', fieldId: 'item'
});
const price = getCustomerPrice(
customerId, itemId, locationId
);
if (price) {
rec.setCurrentSublistValue({
sublistId: 'item',
fieldId: 'rate',
value: price
});
rec.commitLine({ sublistId: 'item' });
}
}
}
}
return { postSourcing, fieldChanged };
});
Common Pitfalls
1. Using fieldChanged Instead of postSourcing for Line Items
A frequent mistake is using the fieldChanged event to detect item selection on a line. The problem is that fieldChanged fires before NetSuite finishes sourcing dependent fields, so you may be reading stale data. postSourcing is the correct event for reacting to item selection because it fires after all sourcing is complete.
2. Not Handling the "No Match" Case Gracefully
If there's no pricing record for a given combination, the script should fall back to the item's standard rate rather than blanking out the price or throwing an error. Build a clear fallback hierarchy: customer/item/location price → customer/item price → standard rate.
3. Performance on Large Orders
The reprice loop that fires on customer or location change runs a search for every line. On orders with 50+ lines, this can feel slow. Two mitigation strategies: batch the lookups into a single search with an OR filter across all items on the order, or load all relevant pricing into a local object on pageInit and reference it in memory rather than hitting the server per line.
4. Ignoring Date-Based Pricing
Pricing agreements expire. If your custom record doesn't include effective and expiration date fields — or if your script doesn't filter on them — you'll eventually serve stale pricing. Always filter on date range in the search, and consider adding a scheduled script that flags expired records for the pricing team to review.
Why This Approach vs. Alternatives
NetSuite offers several native pricing mechanisms, and it's worth understanding where they fall short for this use case:
The custom record approach wins on maintainability. Business users can manage pricing data directly. Developers maintain the lookup logic. And the two concerns stay cleanly separated.
Getting Started
If your team is dealing with pricing complexity that NetSuite's native tools can't handle cleanly, a custom record plus client script is often the most practical path. It keeps the data accessible to business users, the logic transparent to developers, and the maintenance burden low.
At Snapshot Design, we build these kinds of targeted NetSuite customizations for distributors and manufacturers every week. If you'd like to talk through whether this approach fits your pricing model, we're happy to have that conversation.
For businesses strategically focused on expansion and sustained growth, a seamless and efficient payment infrastructure is essential. It directly...
Integrating Configurable Products: What Your Team Needs to Know Before Launch Ecommerce integrations for simple products are straightforward. SKU...
1 min read
In today's rapidly evolving ecommerce landscape, where the pace of change is relentless and the competition is more intense than ever before,...