Suitescript stop order from being approved before approval - netsuite

I am working on a script for net suite. The point of the script is to check a sales order to make sure the credit limit is higher than the order total before actually approving the order. The script runs when the user clicks the "approve" button on the sales order. I successfully check to see if its a sales order and if the action type is approve. What I'm not understanding is how to stop the approval if the order is indeed over the credit limit. I tried setting the order status back to pending approval and submit the order but it doesn't work like that. The status is not getting set. There is also no cancel type of command.
function beforeApproveSalesOrder(type)
{
var recordType = nlapiGetRecordType();
var recordId = nlapiGetRecordId();
if (recordType == 'salesorder')
{
if (type == 'approve')
{
var recordId = nlapiGetRecordId();
nlapiLogExecution("debug", "Check Credit Limit Before Approval", "Found sales order with transaction type of approve.");
var salesOrderTotalCost = nlapiGetFieldValue('total');
var customer = nlapiGetFieldValue('entity');
var customerCreditLimit = queryCustomerCreditLimit(customer);
if (customerCreditLimit < salesOrderTotalCost)
{
nlapiSetFieldText('orderstatus', 'A');
nlapiLogExecution("debug", "Check Credit Limit Before Approval", "order status set");
nlapiSubmitRecord(recordId, true);
}
}
}
}
function queryCustomerCreditLimit( customer )
{
var filters = new Array();
filters[0] = new nlobjSearchFilter( 'internalid', 'null', 'is', customer);
var columns = new Array();
columns[0] = new nlobjSearchColumn( 'creditlimit' );
var searchresults = nlapiSearchRecord( 'customer', null, filters, columns );
var creditLimit = searchresults[ 0 ].getValue( 'creditlimit' );
return creditLimit;
}

You could throw an exception and thereby abort the submit action. In your beforeSubmit() function, you can have something like:
if (customerCreditLimit < salesOrderTotalCost){
throw nlapiCreateError('Error','Cannot Approve. Exceeds credit limit',true);
}

Your code works but you need to change nlapiSetFieldText('orderstatus', 'A'); to nlapiSetFieldValue('orderstatus', 'A');
I used it on a script that compares the total amount of an SO and changes the approval status if it has been increased.

Related

How do you fetch a list of records and then add a checkbox next to corresponding record so to edit and save changes in suiteScript

I want to create a suitelet that could fetch some sales order then be able to edit those on click on submit button, so far I was I'm able to fetch records but I'm not able to add checkboxes and update records in netsuite - suitescript
var list=form.createList({title:"Sales order"});
list.addColumn({
id : 'column1',
type : serverWidget.FieldType.TEXT,
label : 'tranid',
align : serverWidget.LayoutJustification.LEFT
});list.addColumn({
id : 'column2',
type : serverWidget.FieldType.TEXT,
label : 'shipaddress',
align : serverWidget.LayoutJustification.LEFT
});list.addColumn({
id : 'column3',
type : serverWidget.FieldType.TEXT,
label : 'rate',
align : serverWidget.LayoutJustification.LEFT
});
salesorderSearchObj.run().each(function(result){
tranid= result.getValue({name: 'tranid'})
shipaddress= result.getValue({name: 'shipaddress'})
rate= result.getValue({name: 'rate'})
list.addRows({
rows : [{
column1 : tranid,
column2 : shipaddress,
column3 : rate
}]
});
Suite Answer Id 40768 has sample code in SuiteScript 1.0. An outline in 2.0 format is below. The full 1.0 sample from NetSuite is also below. The key is to for the GET req method create the page and display the information as a Sublist instead of a List. Then for all other req methods, get the parameters and take desired action.
2.0 partial outline
define(['N/ui/serverWidget'], function(serverWidget) {
function onRequest(context){
if(context.request.method === 'GET'){
//create page to display results and allow for user input
var form = serverWidget.createForm({
title : 'Simple Form'
});
var sublist = form.addSublist({
id : 'sublistid',
type : serverWidget.SublistType.INLINEEDITOR,
label : 'Inline Editor Sublist'
});
//Add checkbox and atleast internalid
var printField = sublist.addField({
id: 'custpage_rec_process',
label: 'Process',
type: serverWidget.FieldType.CHECKBOX
});
var idField = sublist.addField({
id: 'custpage_rec_id',
label: 'Internal ID',
type: serverWidget.FieldType.TEXT
});
//empty Array to hold Sales Order data
var TranIds = [];
//run search to get Sales Order data
...salesorderSearchObj.run().each(function(result){
//add search column names as columns in the sublist
//add each column value to the sublist
}
//add buttons to sublist and form
sublist.addMarkAllButtons();
sublist.addRefreshButton();
form.addResetButton({label: 'Reset'});
form.addSubmitButton({label: 'Create File'});
//display page for user input
context.response.writePage(form);
} else { //if the previously created/displayed form has been submitted display the following to the user
var req = context.request;
var params = JSON.stringify(context.request.parameters);//can log this to see exactly how the information comes through
//gather submitted data
//take desired action
//display response to user
context.response.writePage('Done');
}
} return {
onRequest: onRequest
}
});
1.0 full sample
function suitelet(request, response){
//Create the form that will be used by the POST and GET requests
var form = nlapiCreateForm('Delete Transactions');
//GET - Show a list of transactions from the search results so the user can select the ones to be deleted
if (request.getMethod() == 'GET' )
{
// Run an existing transaction search
var results = nlapiSearchRecord('transaction', 'customsearch_mass_deletion_results');
// Create a sublist to show the search results
var sublist = form.addSubList('custpage_transaction_list', 'list','Transactions');
// Create an array to store the transactions from the search results
var transactionArray = new Array();
if (results!=null)
{
// Add a checkbox column to the sublist to select the transactions that will be deleted.
sublist.addField('delete','checkbox', 'Delete');
// Add hidden columns for the Internal ID and for the Record type.
// These fields are necessary for the nlapiDeleteRecord function.
sublist.addField('internalid','text', 'Internal ID').setDisplayType('hidden');
sublist.addField('recordtype','text', 'Record Type').setDisplayType('hidden');
// Add a column for the Internal ID link
sublist.addField('internalidlink','text', 'Internal ID');
// Get the the search result columns
var columns = results[0].getAllColumns();
// Add the search columns to the sublist
for(var i=0; i< columns.length; i++)
{
sublist.addField(columns[i].getName() ,'text', columns[i].getName() );
}
// For each search results row, create a transaction object and attach it to the transactionArray
for(var i=0; i< results.length; i++)
{
var transaction = new Object();
// Set the Delete column to False
transaction['delete'] = 'F';
// Set the hidden internal ID field
transaction['internalid'] = results[i].getId();
// Set the hidden record type field
transaction['recordtype'] = results[i].getRecordType();
// Create a link so users can navigate from the list of transactions to a specific transaction
var url = nlapiResolveURL('RECORD', results[i].getRecordType() ,results[i].getId(), null);
internalIdLink = " " + results[i].getId() +" ";
// Set the link
transaction['internalidlink'] = internalIdLink;
// Copy the row values to the transaction object
for(var j=0; j< columns.length ; j++)
{
transaction[columns[j].getName()] = results[i].getValue(columns[j].getName());
}
// Attach the transaction object to the transaction array
transactionArray[i] = transaction;
}
}
// Initiate the sublist with the transactionArray
sublist.setLineItemValues(transactionArray);
sublist.addMarkAllButtons();
form.addSubmitButton('Submit' );
response.writePage( form );
}
//POST - Delete the selected transactions and show a confirmation message
else
{
// Check how many lines in the sublist
var count = request.getLineItemCount('custpage_transaction_list');
// This variable will keep track of how many records are deleted.
var num = 0;
//for each line in the sublist
for(var i=1; i< count+1; i++)
{
//get the value of the Delete checkbox
var deleteTransaction = request.getLineItemValue('custpage_transaction_list', 'delete', i);
// If it's checked, delete the transaction
if(deleteTransaction == 'T')
{
// Get the transaction internal ID
var internalId = request.getLineItemValue('custpage_transaction_list', 'internalid', i);
// Get the transaction type
var recordType = request.getLineItemValue('custpage_transaction_list', 'recordtype', i);
try
{
// Delete the transaction
nlapiDeleteRecord(recordType, internalId);
num++;
}
// Errors will be logged in the Execution Log
catch(ex)
{
nlapiLogExecution('ERROR', 'Error', 'Transaction ID '+ internalId +': '+ ex);
}
}
}
// Show how many records were deleted.
form.addField("custpage_transaction_total", "text").setDisplayType('inline').setDefaultValue(num + " transactions deleted");
response.writePage( form );
}
}

How to restrict users to put a future date in NetSuite custom Date field?

is it possible to restrict user from entering a future date in a netsuite field?
Usercase:
to restrict user from adding a future date in shipment receiving date. This field should be either today or any past date but not future.
if anyone have an idea please reply.
Both of the following would work in a client script.
This defines the validation function that is executed when a field is changed by a user or client call.
function validateField(context) {
var curRec = context.currentRecord;
var fieldName = context.fieldId;
if (fieldName === 'date') { //replace "date" with your field id
var recDate = curRec.getValue({fieldId: fieldName});
var today = new Date();
if (recDate > today){
alert('You cannot enter a future date');
curRec.setValue({
fieldId: 'date',
value: null
});
}
return true;
}
This defines the function that is executed when a field is changed by a user or client call.
function fieldChanged(context) {
var curRec = context.currentRecord;
var fieldName = context.fieldId;
if (fieldName === 'date'){ //replace "date" with your field id
var recDate = curRec.getValue({fieldId: fieldName});
var today = new Date();
if (recDate > today){
alert('You cannot enter a future date');
curRec.setValue({
fieldId: 'date',
value: null
});
}

SuiteScript 2.0 Transaction Saved Search Filter

I have created a saved search for transaction in netsuite and with suitescript 2.0 I am showing saved search data in my application. In application user can apply filter on any fields (please see the attached screenshot). For example user select "Aug 2011" for posting period, only transactions of Aug 2011 should be loaded. This works fine if I create a filter with internalid of "Aug 2011", but on UI I dont have internal id.
sample code:
/*
here is my required module
*/
function getTransactionData(datain)
{
try
{
var objSearch = search.load
({
id: datain.savedsearchid
});
/***** Work *****/
objSearch.filters.push(search.createFilter({ name: "postingperiod", operator: "ANYOF", values: "1" })); //here 1 is internalid of periodname "Aug 2011"
/***** Not Work (SSS_INVALID_SRCH_FILTER_JOIN) *****/
//objSearch.filters.push(search.createFilter({ name: "postingperiod", join: "accountingperiod", operator: "ANYOF", values: "Aug 2011" }));
objSearch.run();
}
catch(ex)
{
log.error("getTransactionData", ex);
throw ex;
}
}
I tried with join but seeing "SSS_INVALID_SRCH_FILTER_JOIN" error from Netsuite.
Can any one please help me regarding this.
Thanks in advance
I edited your code to a more simplified one to understand better.
If you get the gist of how it works, you can edit/customize the way you want.
I assume there is join 'accountingperiod' option available for 'postingperiod' and this works perfectly in your saved search you created in netsuite without using suitescript.
/*
here is my required module
*/
function getTransactionData(datain) {
try {
var objSearch = search.load({
id: datain.savedsearchid
});
var defaultFilters = objSearch.filters;
var customFilters = [];
//Adding filter
customFilters = ['postingperiod', 'ANYOF', '1'];
defaultFilters.push(customFilters);
customFilters = undefined;
customFilters = [];
//Adding filter
/*
customFilters = ['postingperiod.accountingperiod', 'ANYOF', 'Aug 2011'];
defaultFilters.push(customFilters);
*/
objSearch.filters = defaultFilters;
var objSearch_run = objSearch.run().getRange({
start: 0,
end: 10
});
} catch (ex) {
log.error("getTransactionData", ex);
throw ex;
}
}
If you want to know how filters is stored in saved search you created in netsuite, you can use the script debugger.
The following code is suitescript 1.0
//Load Saved Search
get();
function get() {
var search = nlapiLoadSearch('transaction', ' ENTER SAVED SEARCH ID HERE ');
log.debug('search',search);
var searchFilters = search.getFilterExpression();
log.debug('searchFilters',searchFilters);
return search;
}
I assume your application is a Suitelet? If so, you need to do a select field type of your record. So probably of 'posting periods'. This will show your periods in a drop down.
When the user selects it, have a client side script auto refresh the data and load your saved search.
Alternatively, you can load all the data and do the filtering client side DOM filtering. Really need a bit more information about your "application".

Creating a Customer Refund record for a Customer Deposit associated with a Sales Order

I'm getting close. I'm am getting hung up on adding the Customer Deposit to the Customer Refund. Here is the code I am using to create a Customer Refund.
var ifxCreateCustomerRefund = {
fromSalesOrder: function () {
var salesOrder = nlapiGetNewRecord();
var customerRefund = nlapiCreateRecord('customerrefund');
customerRefund.setFieldValue("customer", salesOrder.getFieldValue("entity"));
customerRefund.setFieldValue("paymentmethod", salesOrder.getFieldValue("custbody_ifx_cc_payment_method"));
var account = ifxFindRecord.find("account", "number", "1099");
customerRefund.setFieldValue("account", account.id);
nlapiLogExecution("debug", "today", nlapiDateToString(nlapiStringToDate(this.today())));
customerRefund.setFieldValue("trandate", nlapiDateToString(nlapiStringToDate(this.today())));
nlapiLogExecution("debug", "order #", salesOrder.getFieldValue("tranid"));
nlapiLogExecution("debug", "deposittransaction", JSON.stringify(salesOrder.getFieldValue("deposittransaction")));
var deposits = nlapiSearchRecord("customerdeposit", null, new nlobjSearchFilter("createdfrom", null, "is", salesOrder.id), null);
nlapiLogExecution("debug", "customer deposits", JSON.stringify(deposits));
customerRefund.insertLineItem("deposit", 1);
customerRefund.setLineItemValue("deposit", "doc", 1, deposits[0].id);
customerRefund.setLineItemValue("deposit", "apply", 1, true);
nlapiSubmitRecord(customerRefund);
},
today: function () {
var dt = new Date();
var str = (dt.getMonth() + 1) + "/" + dt.getDay() + "/" + dt.getFullYear();
return nlapiDateToString(nlapiStringToDate(str));
}
};
It is giving me this error:
You have attempted an invalid sublist or line item operation. You are
either trying to access a field on a non-existent line or you are
trying to add or remove lines from a static sublist.
Can you help me get the Customer Deposit associated with the Sales Order to be applied to the Customer Refund?
The customer refund needs to be initialized with the customer id and then the available deposits will already be present. Adapted from my previous answer:
var cr = nlapiCreateRecord('customerrefund',{entity:salesOrder.getFieldValue("entity")}); // id of customer
cr.setFieldValue('paymentmethod', salesOrder.getFieldValue("custbody_ifx_cc_payment_method"));
... // do your account assignment etc.
var deposits = nlapiSearchRecord("customerdeposit", null, new nlobjSearchFilter("createdfrom", null, "is", salesOrder.getId()), null);
var depositId = deposits[0].getId();
for(var i = cr.getLineItemCount('deposit'); i>0; i--){
if(depositId == cr.getLineItemValue('deposit', 'doc', i)){
cr.setLineItemValue('deposit', 'apply', i, 'T'); // need this for at least one line.
break;
}
nlapiSubmitRecord(cr);

Netsuite: Sort categories by what's in stock?

In Netsuite, on a category page, is there a way to sort by what's in stock? So those things in stock show up at the top of a category?
Yes. There are 3 ways I know of to achieve this.
use a saved search based category instead of a static category. Make sure the layout uses Sort by Sequence. Remember NS caches these for 6 hours so updates are not immediate.
can run a scheduled script that targets certain categories; have the script remove out-of-stock items from the category and then add them back in. This has the effect of moving them to the end of the category list
similar but run the script on stock affecting transactions (SO commitments; Item Receipts; each out-of-stock item would remove itself and re-add itself to all categories. I would actually post the affected item ids to a queue (custom record) and work through that every 15 minutes.
in either of the scheduled script cases you'd probably want to take advantage of {disabletriggers:true,enablesourcing:false} to minimize unnecessary user events firing.
var itemRec = nlapiLoadRecord(ki.getRecordType(), ki.getId());
itemRec.insertLineItem('sitecategory', 1); // at the start
itemRec.setLineItemValue('sitecategory', 'category', 1, targetCat);
nlapiSubmitRecord(itemRec,{disabletriggers:true,enablesourcing:false});
This is what we use to sort items by quantity and then by item name. You'd have to tweak the tags, ids, etc. to match your own info where needed, but this is basically it.
function ReadXMLData(selectLayout) {
var itemName = document.getElementsByTagName('itemdisplayname');
var storeDesc = document.getElementsByTagName('storediscription');
var desc = document.getElementsByTagName('discription');
var itemQuantity = document.getElementsByTagName('itemquantity');
var itemPrice = document.getElementsByTagName('itemprice');
var itemImgUrl = document.getElementsByTagName('itemimgurl');
var itemUrl = document.getElementsByTagName('itemurl');
var displayName2 = document.getElementsByTagName('itemdisplayname2');
var itemSKU = document.getElementsByTagName('itemid');
var internalId = document.getElementsByTagName('iteminternalid');
var itemList = [];
for (var i = 0; i < itemName.length; i++) {
itemList.push({
Name: itemName[i].innerHTML,
Description: storeDesc[i].innerHTML,
Desc: desc[i].innerHTML,
Quantity: parseInt(itemQuantity[i].innerHTML),
Price: itemPrice[i].innerHTML,
ImageURL: itemImgUrl[i].innerHTML,
StoreURL: itemUrl[i].innerHTML,
DisplayName2: displayName2[i].innerHTML,
InternalId: internalId[i].innerHTML,
ItemSKU: itemSKU[i].innerHTML
});
}
//Sort the array by quantity descending order and then by name ascending order
var sortedList = Enumerable.From(itemList)
.OrderByDescending(function(x) {
return x.Quantity
})
.ThenBy(function(x) {
return x.Name
})
.ToArray();
if (document.getElementById('itemDisplayArea')) {
document.getElementById('itemDisplayArea')
.innerHTML = '<br/>' + GetLayoutHTML(selectLayout, sortedList);
}
if (selectLayout == "imgList") {
DisplayDescription(sortedList);
} else {
DisplayStarGrid(sortedList);
}
}

Resources