Background
A common request is to look at transactional information with a cumulative total in mind. A good example is the inventory on hand balance. In NetSuite, every transaction that affects the inventory quantity serves to either increment or decrement the total on hand for that item. Accordingly, it may be valuable to determine what the inventory balance was at any particular point in time.
NetSuite’s Saved Search offers good capacities to generate detail that can be used to attached to specific records and act as a child list. This is NetSuite’s sublist feature. To continue our example, we can create a saved search that accounts for all the transactions that affect the inventory item quantity on hand. For each of these transactions, we can see the positive and negative affects of the transaction. Wouldn’t it be great to have a column that is the cumulative amount, or sometimes referred to as the running balance or running total, of each transaction?
Here is a sample of the transaction output we are seeking:
Date | Type | Item# | Quantity | On Hand |
---|---|---|---|---|
1/2/2012 | Receipt | A350 | 10 | 10 |
1/3/2012 | Fulfillment | A350 | -1 | 9 |
1/4/2012 | Fulfillment | A350 | -2 | 7 |
1/6/2012 | Receipt | A350 | 5 | 12 |
NetSuite excels as making it easy to get transaction data out of the environment for the first 4 (Date, Type, Item# and Quantity) columns. It is challenging though to get the “On Hand” quantity which represents a running total. You can deem the Saved Search as a Sublist which allows you to attach the results to a master entity (specific record with in a form view). In the above example, we can hook the Saved Search to the Item Form which will then show only records that are of type “A350”. This is exactly what one of our clients wanted: an ability to see the quantity on hand at any time when they performed an item lookup.
Saved Search Summary Lookup Column Reference Limitation
When working with Saved Searches, the general approach is to use the Summarize feature to group and sum information. For those that have SQL query language background, NetSuite is effectively performing a “Group By” type SQL Select statement behind the scenes. In our example, however, we don’t really need to use an aggregate “Group By” function to get our result. That would work well if we wanted to have a single answer up to a particular date in time. Instead, we want to see the detail transactions yet we want to have one column contain summarized information.
The power of the NetSuite Saved Search tool typically lies with the formula functions. It allows you to get closer to NetSuite’s underyling Oracle database by performing PL/SQL function calls. Oracle offers a nice function to produce this running total:
sum({expression1}) OVER(ORDER BY {expression1} ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) "Running Total"
Unfortunately, this special syntax is not supported within NetSuite. And there is another key weakness: the tool does not allow you to name a formula column so you can reference it in another formula column. This is the “Running Total” portion of the SQL syntax above. The ability to create a calculated and named column is easily supported in SQL yet is missing in the Saved Search function. Even though NetSuite supports the capacity to define calculated fields as custom fields, you can’t get close enough to the underlying Oracle database to produce the definition you need.
Addendum — Solved nearly 3 year later!
See updated article on how to solve this with Saved Search.
Basic Running Total Algorithm
The basic running total algorithm logic is as follows assuming we are seeking transaction output as illustrated above:
- Get all the data that fits the criteria you need.
- Define a integer variable, such as “onhand”
- Organize the transactions list sorted by date
- Start at the top of the list and iterate through each row
- For each row, increment the value of “onhand” variable by the value of the quantity column
- Output the “onhand” value as a separate column
Key NetSuite Saved Search Running Total Wish Lists Features
The algorithm above is very common when working with business databases. As such, here is an item to add to my NetSuite Saved Search wish lists:
1. The ability to reference a forumla field by a name
2. The ability to connect SuiteScript to a formula. Combine the power of SuiteScript with Oracle PLSQL.
3. The ability to define when the SuiteScript would execute: before query start, before get row, after get row, after query end.
While I have many other Saved Search wishes, these capacities would allow for a great deal of flexibility and solve our running total problem.
How to Solve the Running Total Challenge
Finally, in order to solve the running total inventory on hand challenge, we alas must use SuiteScript and couple this to the Sublist API to output the results. Below is basic SuiteScript pattern take a manipulated search result and place the values in a sublist to output the result. While we have solved the problem, it demands more of the user and is often outside the reach of a typical NetSuite Administrator.
// assumes you setup the search and results which // include any preprocessing to add running totals if ( searchresults.length > 0 ) { // first, add the columns to the sublist based on the columns in the results var result = searchresults[0]; var column_list = result.getAllColumns(); var col_len = column_list.length; for (i=0; i<col_len; i++) { var col = column_list[i]; var col_name = col.getName(); var col_label= col.getLabel(); var col_form = col.getFormula(); var col_func = col.getFunction(); nlapiLogExecution('DEBUG', 'column name, label, formula, function', col_name + '|'+ col_label + '|'+ col_form + '|'+ col_func); // if there is no label, use the field name if (!col_label) { col_label = col_name; } var fld_name = 'custpage_fld_' + i; } // now, show the results; loop through each result var rlen= searchresults.length; for (ctr=0;ctr<rlen;ctr++) { var result = searchresults[ctr]; // for each result, loop the columns for (i=0; i<col_len; i++) { var col = column_list[i]; var col_name = col.getName(); var col_label= col.getLabel(); // if there is no label, use the field name if (!col_label) { col_label = col_name; } var fld_name = 'custpage_fld_' + i; var value = result.getText(col); if (!value) { value = ""; } if (value.length == 0) { value = result.getValue(col); } var lin = parseInt(ctr) + parseInt(1); if (value) { YOURLIST.setLineItemValue(fld_name, lin, value); } } } }
Can you show how to use the SQL Group By in your example?
In our example, however, we don’t really need to use an aggregate “Group By” function to get our result
Hello Mike,
Are you suggesting that your records, at the detail level, have the data you need and you simply want a running total? We still need to go to SuiteScript to get the job done.
Perhaps you can offer more explanation.
Marty
Hey Marty,
Have you tried an SQL Formula that copies the header details to the rows and the criteria is “Main Line = False’?
Yes, Main Line = False returns only the detail. That’s the level we want. But there is no way to maintain a cumulative total with the SQL functions provided. Do you see a way?
We really need a Javascript Layer or a way to make a second pass on the data.
I was able to get this module working correctly, but was wondering what transaction types did you use as the criteria in your saved search to define on-hand/running total counts. Based on this article, I assumed it was Item Fulfillment and Item Receipts. Can you please clarify. Thanks
Hi David,
It’s been over 18 months since we solved for this so my memory is vague. In case of inventory, yes, you will need to pull over all transaction records that affect inventory. Besides Item Fulfillments and receipts, consider Transfers. Given NetSuite is now supporting new “inventory detail” lists, I might try to approach it from a different angle. I suggest testing all your assumptions to see if you can write queries that can reproduce the inventory balance on hand.
Good luck!
Marty
hey …I have to read an entire line item from one sales order and add that into another sales order. Could you please help me with this???
Hello Ramya,
The work you need to do typically entails working with the Sublist API. There are mutliple ways this API works and it depends on the context for which you are trying to accomplish your goal.
Did you see this article which shows some use of the Sublist API?
https://blog.prolecto.com/2013/05/01/how-to-script-to-automate-netsuite-journal-entries/
Marty
Is it possible to show column totals on top of the search or in the ‘Filters’ area of a dashboard search?
Hello Ruben,
Not really. NetSuite has its own ways of doing things and the column totals will show at the bottom. However, you can program a portlet with Suitescript to get the view you want. Here is a simple portlet code example: https://blog.prolecto.com/2014/07/09/netsuite-portlet-code-example-get-last-invoice-number/
Marty
Hi,
I’d love to use this function on a customer search to show total sales for that customer – I currently have this – which produces an error? I feel like I’m missing something.
case when {transaction.type}=’Tax Invoice’ then (sum/* comment */({transaction.amount})) else 0 end
Appreciate any help at all.
Hello Reuben,
I suspect the Analytics function can’t be used in the case statement that way. Try Decode. See this article: https://community.oracle.com/thread/453335?start=15&tstart=0
Marty
Marty – attempted to build out a saved search based on your terrific guidance, to be able to see point in time inventory. (ie, pick a particular product/SKU, and a specific date or date range, and see what the total inventory on hand was each day). Seeing the movement/transactions – item receipt, inventory adjustment, itself is key as well. Unfortunately my results are showing cumulative counts over time… versus showing what the count was on a particular day/range of days.
Is it even possible to calculate point in time inventory like that ?
This is the formula that I have currently:
TO_CHAR(sum/* comment */( {quantity}) OVER(PARTITION BY {item} ORDER BY TO_CHAR({trandate}, ‘YYYYMMDD’) || to_char({internalid}, ‘00000000000000009’) ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW), ‘9,999,999,999’)
Any guidance would be great.
Hello Jason,
Yes, have a look at this portion: “TO_CHAR({trandate}, ‘YYYYMMDD’) || to_char({internalid}, ‘00000000000000009’)”. It is concatenating the internal ID and the date together. Remove the “|| to_char({internalid}, ‘00000000000000009’)” to see if that helps.
Marty