Thursday, September 1, 2011

New Home of Knowledge Share Care: www.codeboxllc.com/ksc

Hey Guys. Knowledge Share Care is MOVING!!!!!

www.codeboxllc.com/ksc will be our new home.

I'll keep this site open for a bit.

Come Visit me at my new house!!!!!

Wednesday, August 31, 2011

NetSuite Fact 1: nlapiSubmitField() doesn't work on ALL fields

We've MOVED!!!! www.codeboxllc.com/ksc


Five days ago, I was inspired by the growing list of cases currently assigned to me. Did I say inspired? I meant to say annoyed. There was one case in particular that was marked urgent and needed to be done by the end of the month. (That was five days ago)

It was a simple task yet so important. Update Department and Class field on Transaction records; specifically Sales Order, Cash Sales and Invoices.

Obvious weapon of choice was Scheduled Job and this was the task that helped me realize the power of Script Parameter. My choice for SuiteScript API function to update these two fields was nlapiSubmitField(). Why? It costs less (About 20 Governance to load and submit vs 10 to update fields).

I finished my coding in approximately 45 mins. Felt proud and clever so I gave myself a pad on the back. I began testing my code and something odd occurred. Even after nlapiSubmitField was called, my fields weren't updated!

I thought it was a defect so I tried using the function against other fields such as custom fields and native fields. It worked just fine. It JUST didn't work for Department and Class fields on Transaction record types.

This was my code:
var flds=['department','class'];
var vals=[19,51]; //internal ID of department and class
var rectype='salesorder';
var recid=555; //internal ID of sales order record
nlapiSubmitField(rectype, recid, flds,vals);
After testing out the function against other native records such as Customer record type, I called NetSuite Support and asked about it.  The support rep. told me that there is an existing defect out there for nlapiSubmitField API call in v2011.2. The problem was, I WASN'T!

I get a response from Support Rep the next day saying this:
Upon further investigation, it appears that the Department and Class fields in the Sales order record are both "non-direct list editable", the nlapiSubmitField in this case behaves as designed. The nlapiSubmitField function will only work for fields which can be Direct List Edited. You can see this on the help guide. Here is the path: SuiteFlex (Customization, Scripting, and WebServices) > SuiteScript > Scripting Records, Fields, Forms, and Sublists > Direct List Editing and SuiteScript > Direct List Editing Using nlapiSubmitField
My problem with this response from the support rep. is that no where on that Help section does it say that Department and Class fields are categorized as None-Direct list editable.

However, if you go to SuiteFlex (Customization, Scripting, and Web Services) : SuiteScript : Scripting Records, Fields, Forms, and Sublists : Direct List Editing and SuiteScript : Direct List Editing and SuiteScript Overview it DOES state this:
In SuiteScript, you cannot direct list edit select fields. In other words, you cannot call nlapiSubmitField on a select field.
If you look at Department and Class field in SuiteScript Recrods Browser both are indeed labeled as select fields.  What's MORE interesting is that I have used nlapiSubmitField function to set select fields!!!!!!!!!

If you are having this problem, you are not the only one. If you want to set Department and Class fields on a transaction record such as Sales Order, you will need to do it the old fashion way:
var soid='123';
var deptid='19';
var clsid='51';
var so=nlapiLoadRecord(rectype, trid);
so.setFieldValue('department',deptid);
so.setFieldValue('class',clsid);
nlapiSubmitRecord(so);
My thought is NetSuite should update their documentation to provide Non-Direct editable fields list.

Saturday, August 27, 2011

Script Parameters: Where have you been all my life??

We've MOVED!!!! www.codeboxllc.com/ksc

Ok. perhaps script parameters been around for a while. I just failed to see the usefulness of it. You can read all about script parameters by going to NetSuite Help > SuiteFlex > SuiteScript > Creating Script Parameters (Custom Fields)

While back, I had one of NetSuite Custom Script guy review my codes. I asked him, "Please, imagine you are paying me to do your work and you are doing a code review of my work. Just rip it part and be very critical".
And he did just that. One of the suggestion he gave me was to utilize Script Parameters to turn my scripts into "Configurable" script.

What does configuration give you? Re-usability. As you see your script library grow, re-usability plays very important role in keeping your NetSuite account clean and organized.

Think of following situations where you can use power of configuration
1. Script that applies to multiple entity record types that sets same fields depending on the situation:
- You could hard code multiple entity  record types in to the script but that's not scalable. If you end up wanting to add new record, you need update the script and deploy.

- An alternative would be to create one script that takes record types as a parameter and DEPLOY it multiple times. It can easily be extended out to other entity record types by creating another deployment of the script with value of parameter as records internal id.

2. Script that takes different saved searches but ultimately does same thing for each records:
- You could hard code different types of saved searches but again, how scalable is that?

- An alternative would be to create one script that takes internal id of a saved search and DEPLOY it multiple times.

I think you are starting to see the trend here. Whats really cool about script parameter is that it can be text field, drop down list or a check box. Why is this cool? It's easy to set the parameters. Typing in internal ID might not be too user friendly but what if you want to set list of items, departments, or custom record as your parameter? Instead of looking up the internal ID of those records, you simply set it. EASY RIGHT!!!!

Keep in mind, if you want to set custom parameter specific for your script, you leave the Preference as blank.
You have two other options for this preference; Company and User. You can see full details by going to NetSuite Help > SuiteFlex > SuiteScript > Setting Script Parameter Preferences


You create your parameter(s) during your script creation stage. You set values for those parameters when you deploy your script. 

ID of your custom parameter starts with custscript prefix. Once you know what your ID is going to be, you can gain access to it with following code:
nlapiGetContext().getSetting('SCRIPT','YOUR CUSTOM FIELD ID');
On my screen shot above, I named my parameter _testparam. Full sample code will look like this:

 nlapiGetContext().getSetting('SCRIPT','custscript_testparam');
I can't wait to go back and rewrite all my scripts. Happy coding everyone!




Wednesday, August 24, 2011

Do you really need Advanced Billing Module?

We've MOVED!!!! www.codeboxllc.com/ksc

Lately I've been pondering on this question. I have this task that I need to do where I must figure out billing that fits the business requirements.  It's not a simple requirement and there are many different variables on how the monthly or annual invoice is generated.

I think for most subscription based and/or fixed amount based businesses, billing is pretty straight forward. However, what about those businesses who has fairly complex process?

As I read through NetSuite documentation and posts on Forums, NetSuite provides out of the box feature for reoccurring billing process. They call it "Memorize Transaction" feature. It allows you to replicate reoccurring billing by having NetSuite "Memorize" this instance of transaction and direct it to run it on schedule of your choice. Sounds pretty simple right?
This will work as long as the items included in the invoice doesn't change or no adjustment is needed for the duration of the billing AND you don't mind making adjustments manually from time to time.

We also have NetSuites' paid featured called Advanced Billing Module  I have not used it before and my opinion on this module is purely based on documentation provided by NetSuite. It seems to offer great automation and customization features than "Memorize Transaction" OOB feature. I really like the fact that it will customize billing schedule with initial amount, initial payment terms, recurrence frequency, recurrence count, in arrears, and recurrence payment terms. It definately allows businesses to take control of billing schedules.

But, does it solve the issue of varying amount invoicing? Say like cell phone billing which is based on base amount plus additional overage charge or hourly consultants billing where invoice amount is based on hours worked by the consultants? Can Memorize Transaction or ABM fully automate the process of billing for these situations?

My initial thought is no.

Ofcourse, in order to automate this type of invoicing, you need to identify and create variables that will allow custom process to calculate the correct amount.

I am thinking that for consistently varying monthly or annual invoice process, custom solution will fit better.
Sure this costs money. Most likely more than what you'll pay for ABM. However, what I like to suggest is that before you purchase ABM I think it'll be worth while to revisit the billing business requirements.

What are your thoughts?

Sunday, August 14, 2011

Streamlining the Business Process in NetSuite

We've MOVED!!!! www.codeboxllc.com/ksc

Lately, all I hear in the news is "Down grade in credit rating", "Recession" or "High unemployment rate". You most likely know them by heart by now. As a business, all these negative economic news would mean tightening the budget belt and trying to figure out where to cut cost.

If your company is using NetSuite or any other SaaS based CRM/ERP tool, this is a good time to revisit all the business processes you have implemented using the system.

I always like to say that tool itself will not save company money or enhance productivity. It's how you use the tool that will get you there. (Obvious point I am making....)

Types of business process I am referring to:

  • Lead to Customer process.
  • Drip email marketing process.
  • Sales order processing.
  • Billing process.
  • Customer relation process.
List goes on... 


Here are some things to look for when you are revisiting your business processes.

  • Process flow diagram:
    Have you created process diagram for each of your business processes used in the System? If you don't have it, you really need to create one. Directors and Project Managers can tell you everything about what the processes are but it's worth nothing when the system doesn't reflect them.
  • Identify automation points within the process
    Use the tool to do the work for you. There are some steps in the process where human intervention is inevitable but there are overlooked steps that doesn't require it. Making sales calls to customers most likely will not be a good candidate for automation but starting a drip marketing campaign is.
    Here are something that I can think of to automate using NetSuite:
    - Auto generation of Sales Order, Proposals or Invoices
    - Conversion process
    - Record creation process
    - Ongoing billing process
    - Subscription renewal process
    - Daily accounting process
  • Identify unnecessary or potentially new steps
    If you already have process diagrams for each of the business processes, revisiting them will give you a fresh look.

    For some departments or projects, business requirements change frequently. This will give you a chance to compare with current requirements and remove or add in steps you no longer need. This will better streamline the process.
Perhaps it's not your job to do this type of work. If you are NetSuite Admin or developer, does this SOUND like your job description? Probably not. But wouldn't you want your company to save money and keep providing the things you enjoy???

Monday, August 8, 2011

Scriptable Cart - Full Sample By Jason K

We've MOVED!!!! www.codeboxllc.com/ksc

Hey Guys. I've been meaning to post this up but lost track of time due to extermination work I've been doing.
Couple months ago, I've worked with Jason K from NetSuite to trouble shoot remove/add issues I was having with my particular scriptable cart process. What this code is SUPPOSED to do:
When an item is added with certain country, it pro grammatically adds country specific surcharge item to the cart. In increments existing surcharge item in the cart if it already exists, it adds new country item if it doesn't exists. Removing parent item will also either increments or decrements the surcharge item.
I have not tried out his code personally but he assures me that it worked for him when he was fully testing out the process in his environment.

I'm hoping this will help in your effort to implement Webstore Scriptable Cart/Checkout form:


/*
Summary:  A custom item option is attached to a certificate which allows the shopper to choose a country.
When the shopper does this, a surcharge will be added to the cart based on the country chosen.  The shopper
can also add another certificate to the cart with a different country and there will be a separate line item
for each surcharge for each country.


This script also syncs up quantities for each cert-country combination.  For example, if the shopper increments,
decrements, or adds another Canada certificate to the cart, the quantity of the canadian surcharge will follow
that quantity.  This will NOT affect the quantity of other surcharges that are not the same country.


Finally, when a certificate is removed from the cart, the associated surcharge is also removed.


There are two customizations that were done to the sales order to support this script:


1) a new custom body field "custbody_processing", a text field, was added to ensure that no infinite looping
is seen.


2) a new custom body field "custbody_deletedcountry", a text field, is added and used by the validateDelete
event. The problem with recalc during a deletion is that we don't know what was deleted.  But, in our
custom validateDelete, if a cert was deleted, we save the cert's country in this custom field.  Then, during
recalc, we can just look up what surcharge needs to be removed and remove it.


There are also a couple of utility functions included below, including safeSelectNewLineItem and safeSelectLineItem.
In some cases, I've noticed that selectNewLineItem and selectLineItem throws an error if the script left the current
item row in an uncommitted state.  Using these functions will ensure that a commit will be done if need be before
a new row is selected.  The other functions should be self-explanatory.


*/
var certId = "74"; // ID of the general certificate, will including an item option custcol_certcountry
var certCountry = "custcol_certcountry"; // name of the custom column


// Map to define what surcharge item ID belongs to which country.
// In this case, I've only set up two countries along with two non inventory items.
var surchargeMap = {"US":"76", "CA":"75"};


// In order to make things easier for us, we will track the type of cert
// that is deleted via a validateDelete.  Will get the country
function validateDelete(type)
{
if (type == 'item')
{
var itemId = nlapiGetCurrentLineItemValue('item','item');
if (itemId == certId)
{
// if we get here, we know a certificate is being deleted.  Store the country in our
// custom body field for use in recalc.
var country = nlapiGetCurrentLineItemValue('item', certCountry);
log('Item ID '+itemId+' being deleted for country '+country+' - storing this in custom field');
nlapiSetFieldValue('custbody_deletedcountry', country);
}
}


return true; // Always return either true or false during validation
}


function onRecalc(type, action)
{
if (type != 'item') { return; }


try
{
var processing = nlapiGetFieldValue('custbody_processing');
if (processing != null && processing == 'T')
{
// We are in a secondary recalc, so exit
return;
}


// If we've passed the previous check, we are in the first
// level of recalc, so set processing flag
nlapiSetFieldValue('custbody_processing', 'T');


if (action=='commit')
{
doCommit();
}


if (action=='remove')
{
doRemove();
}
}
catch (err)
{
log("General Error: "+err.message);
}
finally
{
// Now, even with an error, we want to reset the processing flag
// so use a finally-clause to ensure this happens no matter what
nlapiSetFieldValue('custbody_processing', 'F');
}
}




// Called when a commit is done for recalc
function doCommit()
{
var thisItem = nlapiGetCurrentLineItemValue('item','item');


// Ignore any add-to-carts that are not certifications
if (thisItem != certId) { return; }


var thisQty = nlapiGetCurrentLineItemValue('item','quantity');
log("Current qty of cert = "+thisQty);


var thisCountry = nlapiGetCurrentLineItemValue('item', certCountry);
log("Current country of cert: "+thisCountry);


var surchargeId = surchargeMap[thisCountry];


if (isEmpty(surchargeId))
{
log("No surcharge ID found for country "+thisCountry+" - skipping processing");
return;
}


var surchargeLine = findItem(surchargeId);


if (surchargeLine == -1)
{
// We need to add a surcharge here
log("surcharge not found - adding surcharge");
addItem(surchargeId, thisQty);
}
else
{
surchargeQty = nlapiGetLineItemValue('item', 'quantity', surchargeLine);
log("Surcharge found - current Surcharge qty "+surchargeQty);


if (surchargeQty != thisQty)
{
log("updating quantity of surcharge");
safeSelectLineItem(surchargeLine);
nlapiSetCurrentLineItemValue('item', 'quantity', thisQty);
nlapiCommitLineItem('item');
}
}
}


// Called when a remove is done for recalc.  We have tracked the country that was
// deleted in the custom field, so this should be straightforward.  If there's no
// value in the custom field, don't do anything.
function doRemove()
{
// First, we get the deleted country from custbody_deleteditem
var deletedCountry= nlapiGetFieldValue('custbody_deletedcountry');


// If it's empty, this means that the item that was deleted was not a cert, so ignore.
if (isEmpty(deletedCountry))
{
return;
}


// Start a try-block here so after the remove we are sure that custbody_deletedcountry
// is cleared out even during an error
try
{
log('Country '+deletedCountry+' has been deleted, finding related surcharge');


var surchargeId = surchargeMap[deletedCountry];
var surchargeLine = findItem(surchargeId);


// only delete if appropriate surcharge is found
if (surchargeLine > 0)
{
log('Surcharge item for country '+deletedCountry+' found on line '+surchargeLine+' - removing');
safeSelectLineItem(surchargeLine);
nlapiRemoveLineItem('item', surchargeLine);
}
}
finally
{
nlapiSetFieldValue('custbody_deletedcountry', '');
}
}


// This function adds the given item to the order
function addItem(itemID, qty)
{
safeSelectNewLineItem();
nlapiSetCurrentLineItemValue('item', 'item', itemID);
nlapiSetCurrentLineItemValue('item', 'quantity', qty);
nlapiCommitLineItem('item');
}




// Sometimes, a selectNewLineItem results in an error if there's uncommitted
// values in the item list.  This makes sure this does not happen.
function safeSelectNewLineItem()
{
if (notEmpty(nlapiGetCurrentLineItemValue('item','item')))
{
nlapiCommitLineItem('item');
}


nlapiSelectNewLineItem('item');
}


// Sometimes, a selectLineItem results in an error if there's uncommitted
// values in the item list.  This makes sure this does not happen.
function safeSelectLineItem(itemLine)
{
if (notEmpty(nlapiGetCurrentLineItemValue('item','item')))
{
nlapiCommitLineItem('item');
}


nlapiSelectLineItem('item', itemLine);
}


// General find function for the item list
function findItem(itemID)
{
var cnt = nlapiGetLineItemCount('item');
for (var i=1; i <= cnt; i++)
{
if (nlapiGetLineItemValue('item','item',i) == itemID)
{
return i;
}
}


return -1; // -1 means not found
}




function isEmpty(tmp)
{
return tmp == null || tmp == '';
}


function notEmpty(tmp)
{
return !isEmpty(tmp);
}




function log(msg)
{
alert(msg);
}


Tuesday, August 2, 2011

Couple things NetSuite Documentation Doesn't Tell You

We've MOVED!!!! www.codeboxllc.com/ksc

NetSuite most likely doesn't care what I complain to them about. I'm no body. I'm just a guy trying to make in this world.

However, I think these two items they may listen and PUT it on the documentation or make it more clearer.

Point 1: Check out customization URLs
There is ONLY ONE usable checkout URL in Sandbox Environment. That is https://checkout.sandbox.netsuite.com. All other selections are used ONLY for production.
I came across this issue while making our second webstore in Sandbox. When ever we refresh our Sandbox from Prod, this error occured. I initially thought it was just a flook. However, when second brand new Webstore I started on began to throw "Page Not Found" or "Internal Server Error" with other customizable check out URL, that's when it got me concerned.

Point 2: Domain IS required to be able to use Descriptive URLs
I'm sure some of you already knew this. I couldn't find anything on documentation about this specific statement. However, If you are having issues with Descriptive URL you set up for Items and Presentation Tabs, you need to set up your own domain.

CORRECTION:
Apparently, Point 2 is wrong on my part. You can find it under this Path of documentation:
Web Site : Searching & Search Engine Optimization : Search Engine Optimization (SEO) : Setting Up Descriptive URLs


It says:
To use descriptive URLs in NetSuite, first, set up a domain at Setup > Web Site > Set Up Domains. Next, turn on the Advanced Site Customization feature, and the Descriptive URLs feature at Setup > Company > Enable Features, on the Web Presence subtab.