Build Status Coverage Status

NSConnector intro

This library provides an interface to NetSuite via RESTlets, i.e. SuiteScript . This appears to be a quicker and more reliable way of interfacing with NetSuite records than the SOAP API.

How is this different to the other netsuite connector gems out there?

  • Basically, it uses a javascript RESTlet to communicate as opposed to the SOAP API, so it's completely different and exposes a separate API.

  • It's not built around auto generated WSDL which can make it easier to design a usable API.

  • It can be concurrent, so it at least has the potential to scale. You can only make one request at a time per user using the SOAP API.

  • It's quicker. Not sure why, but the SOAP API is horrendously slow sometimes, RESTlets seem to be quicker, for now.

Documentation

Avaliable at rubydoc.info

Features

  • No dependencies

  • Read write for supported types

  • Flexible searching

  • Multithreaded chunked searching for retrieving large datasets

  • Read only sublist support.

  • SubList item creation on creating new records only. (>= v0.0.7)

  • Attaching and detaching records

  • Invoice PDF generation.

  • Record transformations.

Supported NetSuite types

It's pretty trivial to add a new one yourself. These have been tested to work:

  • Contact

  • Customer

  • CustomerPayment

  • Invoice

Installation

Install the RESTlet:

function post(request) {
  func = eval(request.code);
  return func(request);
}

That's the whole RESTlet. Deploy it to NetSuite, ensuring that the POST function is set to 'post'. Note the 'External URL' when deploying it, that is what you will use in the configuration below.

Configuration

The configuration is stored 'globally' via NSConnector::Config#set_config!

An example config:

NSConnector::Config.set_config!({
  :account_id  => '123',
  :email       => 'email@site',
  :password    => "password",
  :role        => '456',
  :restlet_url => 'https://netsuite/restlet',
})

Options

:account_id (mandatory)

TODO: Document how to find an account ID.

:email (mandatory)

The email address you log into the NetSuite web site with that matches the account_id.

:password (mandatory)

Hopefully a secret.

:role (mandatory)

Can be found in your cookie when logged in, or deep within the jungle of the user interface. TODO: Document the perilous journey through the jungle.

:use_threads (optional)

A bool, set to false to turn off threading when retrieving large result sets. By default we try to split these result sets up into manageable chunks. If you turn this off, they will still be split up, but only one chunk will be retrieved at a time.

:no_threads (optional)

An integer, the number of threads to use. By default, 4.

CRUD usage

Every supported type supports full CRUD via the same standard interface Check the SuiteScript Records Browser [1] for avaliable fields.

Creating

To create a record, simply instantiate a new class of that kind and call .save! on it.

Calling .save! will re-load the saved Record from NetSuite with any other changes that NetSuite's internal logic may have made.

Example:

include NSConnector

c = Contact.new(:firstname => 'name')
=> <#NSConnector::Contact:nil>

c.fields
=> [fields...]

c.lastname = 'abc'
=> 'abc'

c.save!
=> true

c.lastname
=> 'Abc'

SubLists can be added to a record on creation. Here is how that works:

include NSConnector
c = Contact.new(:firstname => 'name')
c.sublists
=> {:addressbook=>
  [:addr1,
  ...

c.addressbook << c.new_addressbook_item({
  :addr1 => 'address line 1',
  :city => 'unicorn valley'
})

c.save!
=> true

Reading

You can find by any field or by internalId. Example:

include NSConnector

# Search by any internal field ID, returns an array of Contacts
Contact.search_by('entityId', 42)
=> [<#NSConnector::Contact:12>]

# Fetch one Contact by internalId
Contact.find(12)
=> <#NSConnector::Contact:12>

# Fetch one Customer by entityid
Customer.find_by('entityid', 1234)
=> <#NSConnector::Contact:12>

# Fetch all Contacts, this will take a while. (Request will be broken
# up into smaller ones and spread across multiple threads).
Contact.all
=> [rather large array of NSConnector::Contact]

You can also perform more complex searces. Example:

Contact.advanced_search([
  ['email', 'contains', '@'],
  ['entityId', 'lessthanorequalto', '1000']
])
=> [<#NSConnector::Contact:12>, <#NS...]

At any time you can check which fields are avaliable in both the class and instances:

Contact.fields
Contact.new.fields

You can also access the raw data store to more easily see what the object contains with:

Contact.find(1234).store

SubLists

SubLists are exposed differently by the backend API, but that's pretty transparent to us, simply access them as a read only array:

Contact.addressbook.first.city

You can see which sublists are avaliable to an object via the sublists accessor:

Contact.sublists
Customer.new.sublists

Updating

Updating records is much like creating new ones. Simply:

  1. Grab the record (see Reading).

c = Contact.find(12)
  1. Update the record to your liking.

c.lastname = 'newname'
  1. Save the record (see Creating)

c.save!
=> true

Deleting

Deletion can be done by ID or Record. Example:

  1. By ID

Contact.delete!(42)
=> true
  1. By Record

c = Contact.find(42)
c.delete!

Record transformations

You can transform records into other records as per the supported transformation types in NetSuite [2]

e.g.

Invoice.find(2106).transform!(CustomerPayment) do |payment|
  payment.ccnumber = '4222222222222'
end
=> {"id"=>"2107",
"account"=>"",
"allowemptycards"=>nil,
"applied"=>"99.99" ...

Attaching records

You can attach/detach records with #attach! and #detach!, it's pretty simple.

# Attach customer 123 to contacts 1, 2 and 3 with the role of 1
Customer.find(123).attach!(Contact, [1,2,3], :role => 1)

# Or anonymously like:
Customer.attach!(Contact, 123, [1,2,3], :role => 1)

Troubleshooting note:

Should you run into an error something like:

NSConnector::Restlet::RuntimeError: Failed to parse response from Restlet as JSON (): A JSON text must at least contain two octets!

It may just be that your user is being denied access, in which case netsuite seems to believes it is a good idea to simply send a blank response.

Development notes

See HACKING in the repo

License

MIT

References

1

SuiteScript Records Browser

2

Supported Transformation Types