Showing posts with label Customization. Show all posts
Showing posts with label Customization. Show all posts

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. 

Saturday, July 30, 2011

Webstore Main Menu Customization

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

Who uses out of the box template for website any more? If you are using NetSuite Advanced Site Customization, you have the option to custom build the main navigation bar.

I've seen some posting on NetSuite Forum where fellow NetSuite users were asking for ability to build out dynamic menu system on their Webstore without heavy scripting. I am one of them.
Standard "Tabs" just doesn't do justice for my brilliant site layout. (I'm seriously being sarcastic here...)

So, how do you build this in WITHOUT heavy scripting? Well... unfortunately, you still need to do SOME scripting but method I found seems to work out without too much headache.

For my little project, I wanted to build dynamic horizontal menu layout.

In NetSuite, you can use <NLPAGETABS> to generate he horizontal tab menu layout. If you sue this tag nested between <table> and </table> tags in Logo and Tabs Template section under Body tab of your Theme.
As long as you have your tabs marked as "Show on Website", that one tag will render everything for you.

Here is sample menu tree I wanted to achieve:
Home page Products (Presentation Tab 0) Shopping Cart My Account
  Product Page 1 (Presentation Tab A)    
  Product Page 2 (Presentation Tab B)    
  Product Page 3 (Presentation Tab C)    

When visitor mouse over to Products, I want three sub pages to show up. As noted above, each page is a presentation tab with different items. 

If you were to ONLY use out of the box <NLPAGETABS>, those sub pages will get rendered as main navigation and will NOT look good.

Here is how I implemented dynamic custom main menu:
Step 1: Find the script you like.
I found a very handy, easy to use dynamic menu from Dynamic Drive. The script I found is very simple and not much modification is needed. Details of this script can be found here. Be sure to read their Terms of Use before using it. =)
I made some modifications to the CSS file to match the theme of my website. 

Step 2: Upload script files, CSS files and any images to your NetSuite Document folder.
What ever file you decide to use, you need to first upload it to your documents folder to be referenced on your Webstore. Script that I found from Dynamic Drive is fairly small and simple which I really like.

Step 3: Implement. (This is implementation using the Script from Dynamic Drive)
  • Make sure .js and .css files are referenced at the Header. [Your Theme] > General > Additon to <head> section.
Go to [Your Theme] > Body > Logo and Tabs Template section and add in your navigation.
Code Sample:
Main Navigation Code:
<div class="chromestyle" id="chromemenu">
<ul>
<li><a href="#">Home</a></li>
<li><a href="#" rel="dropmenu1">Products</a></li>
<li><a href="#">Shopping Cart</a></li>
<li><a href="#">My Account</a></li>
</ul>
</div>
** red="dropmenu1" indicates a reference to sub menu items.
Sub Menu Navigation Code:
<div id="dropmenu1" class="dropmenudiv">
<a href="#">Product Page 1</a>
<a href="#">Product Page 2</a>
<a href="#">Product Page 3</a>
</div>
Script Tag:
** Add this after you've placed all your menu item
<script type="text/javascript">
cssdropdown.startchrome("chromemenu")
</script>

You can customize it further to your liking. Please read the details on Dynamic Drive regarding this script.

It's not that bad. I changed the CSS just a bit to match my pages' colors and didn't touch the original script file. 

If you'd like to see this page in action, please contact me by email. mhson1978@gmail.com

Oh yah, one note. If you customize the navigation this way, URL to pages will be a bit hard to manage. I've been looking into using Descriptive URL feature but its not working for me at the moment.
I've done some research on the Forum and some one DID say this:
You must have domain set up in order to use it.
I've actually asked my network admin to put in a CNAME for me. So if it works, I'll let you guys know.

Let me know if you have any questions. NetSuite, do you guys know when you'll add this as Part of NetSuite feature?

Tuesday, June 21, 2011

NetSuite Support Case - Grab Notification

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

Have you ever received an email from NetSuite Support saying something the line of "Hello, My name is [Rep. Name] and I will be working on your case..."

We wanted to do something like this for our support. Thought it was pretty nice way to let folks know that they are not being ignored. After some research, we find out that this isn't an automated solution.

Apparently, NetSuite Support implemented this method by attaching a solution to a case and having it notify customer. When Solution is attached to the case, they manually enter in Case Number and their name before sending it.

I thought this was a bit odd. Apparently, using CRMSDK Tags do not work when added to a solution set. So, I've been thinking about a work around for this. I haven't tried it yet but I'm sure it'll work.

First, create yourself a solution called "Case Assignment". Here is an example:
Hello, My name is [RepName] and I'll be working on your case #[CaseNumber].
Second, create a script file which will be deployed at Case form level or Case Record level.
My thought is that my script will fire ONSAVE or right before the form is submitted.

If the value of the INSERTSOLUTION field is "Case Assignment" AND value of OUTGOINGMESSAGE contains the value [RepName] or [CaseNumber] {
Replace [RepName] with Currently Logged in User's first and last name;
Replace [CaseNumber] with getFieldValue('CASENUMBER');
}

What do you think? I think this might work. I'll let you guys know as soon as I put it in. Working version of the code coming soon.....