- This gem will act as a wrapper around the NetSuite SuiteTalk WebServices API.
- The gem does not cover the entire API, only the subset contributors have used so far.
- NetSuite is a huge complex system. There's a lot to learn and sparse resources available to learn from. Here's a list of NetSuite Development Resources that might make things a bit less painful.
Join the slack channel for help with any NetSuite issues.
Please do not post usage questions as issues in GitHub.
Before contributing a patch make sure all existing tests pass.
git clone git://github.com/NetSweet/netsuite.git
cd netsuite
bundle
bundle exec rspec
Add this line to your application's Gemfile:
gem 'netsuite'
This gem is built for ruby 1.9.x+, checkout the 1-8-stable branch for ruby 1.8.x support.
Not sure how to find your account id? Search for "web service preferences" in the NetSuite global search.
NetSuite.configure do
reset!
# optional, defaults to 2011_2
api_version '2012_1'
# optionally specify full wsdl URL (to switch to sandbox, for example)
wsdl "https://webservices.sandbox.netsuite.com/wsdl/v#{api_version}_0/netsuite.wsdl"
# if your datacenter is being switched, you'll have to manually set your wsdl location
wsdl "https://webservices.na2.netsuite.com/wsdl/v#{api_version}_0/netsuite.wsdl"
# or specify the sandbox flag if you don't want to deal with specifying a full URL
sandbox true
# often the netsuite servers will hang which would cause a timeout exception to be raised
# if you don't mind waiting (e.g. processing NS via DJ), increasing the timeout should fix the issue
read_timeout 100000
# you can specify a file or file descriptor to send the log output to (defaults to STDOUT)
log File.join(Rails.root, 'log/netsuite.log')
# login information
email '[email protected]'
password 'password'
account '12345'
role 1111
end
If you'd like to use a API endpoints greater than 2015_1 you'll need to specify an application ID:
NetSuite::Configuration.soap_header = {
'platformMsgs:ApplicationInfo' => {
'platformMsgs:applicationId' => 'your-netsuite-app-id'
}
}
OAuth credentials are also supported. Learn more about how to set up token based authentication here.
NetSuite.configure do
reset!
account ENV['NETSUITE_ACCOUNT']
consumer_key ENV['NETSUITE_CONSUMER_KEY']
consumer_secret ENV['NETSUITE_CONSUMER_SECRET']
token_id ENV['NETSUITE_TOKEN_ID']
token_secret ENV['NETSUITE_TOKEN_SECRET']
# oauth does not work with API versions less than 2015_2
api_version '2016_2'
end
# get a customer
customer = NetSuite::Records::Customer.get(:internal_id => 4)
customer.is_person
# or
NetSuite::Records::Customer.get(4)
# get a list of customers
customers = NetSuite::Records::Customer.get_list(:list => [4, 5, 6])
# randomly assign a task
customer_support_reps = [12345, 12346]
task = NetSuite::Records::Task.new(
:title => 'Take Care of a Customer',
:assigned => NetSuite::Records::RecordRef.new(internal_id: customer_support_reps.sample),
:due_date => DateTime.now + 1,
:message => "Take care of this"
)
task.add
# this will only work on OS X, open a browser to the record that was just created
`open https://system.sandbox.netsuite.com/app/crm/calendar/task.nl?id=#{invoice.internal_id}`
# update a field on a record
task.update(message: 'New Message')
# delete a record
task.delete
# refresh/reload a record (helpful after adding the record for the first time)
task.reload
# using get_select_value with a standard record
NetSuite::Records::BaseRefList.get_select_value(
recordType: 'serviceSaleItem',
field: 'taxSchedule'
)
# get options for a custom sublist field (i.e. transaction column fields)
NetSuite::Records::BaseRefList.get_select_value(
field: 'custcol69_2',
sublist: 'itemList',
recordType: 'salesOrder'
)
# output names of options available for a custom field
options = NetSuite::Records::BaseRefList.get_select_value(
field: 'custbody_order_source',
recordType: 'invoice'
)
options.base_refs.map(&:name)
# updating a custom field list on a record
# you need to push ALL the values of ALL of the custom fields that you want set on the record
# you can't just push the values of the fields that you want to update: all of the values of
# other fields will then fall back to their default values
contact = NetSuite::Records::Contact.get(12345)
contact.custom_field_list.custentity_alistfield = { internal_id: 1 }
contact.custom_field_list.custentity_abooleanfield = true
contact.update(custom_field_list: contact.custom_field_list)
# getting a custom record
record = NetSuite::Records::CustomRecord.get(
# custom record type
type_id: 10,
# reference to instance of the custom record type
internal_id: 100
)
# getting a list of custom records
records = NetSuite::Records::CustomRecord.get_list(
# netsuite internalIDs
list: [1,2,3],
# only needed for a custom record
type_id: 1234
)
# adding a custom record
record = NetSuite::Records::CustomRecord.new
record.rec_type = NetSuite::Records::CustomRecord.new(internal_id: 10)
record.custom_field_list.custrecord_locationstate = "New Jersey"
record.add
# updating a custom record
record = NetSuite::Records::CustomRecord.new(internal_id: 100)
record.custom_field_list.custrecord_locationstate = "New Jersey"
record.update(custom_field_list: record.custom_field_list, rec_type: NetSuite::Records::CustomRecord.new(internal_id: 10))
# using get_select_value with a custom record
NetSuite::Records::BaseRefList.get_select_value(
field: 'custrecord_something',
customRecordType: {
'@internalId' => 10,
'@xsi:type' => 'customRecord'
}
)
# basic search
search = NetSuite::Records::Customer.search({
basic: [
{
field: 'companyName',
operator: 'contains',
value: company_name
}
]
})
`open https://system.netsuite.com/app/common/entity/custjob.nl?id=#{search.results.first.internal_id}`
# find the avalara tax item. Some records don't support search.
all_sales_taxes = NetSuite::Utilities.backoff { NetSuite::Records::SalesTaxItem.get_all }
ns_tax_code = all_sales_taxes.detect { |st| st.item_id == 'AVATAX' }
# searching for custom records
NetSuite::Records::CustomRecord.search(
basic: [
{
field: 'recType',
operator: 'is',
# custom record type
value: NetSuite::Records::CustomRecordRef.new(:internal_id => 10),
}
]
).results
# advanced search building on saved search
NetSuite::Records::Customer.search({
saved: 500, # your saved search internalId
basic: [
{
field: 'entityId',
operator: 'hasKeywords',
value: 'Assumption',
},
{
field: 'stage',
operator: 'anyOf',
type: 'SearchMultiSelectCustomField',
value: [
'_lead', '_customer'
]
},
{
field: 'customFieldList',
value: [
{
field: 'custentity_acustomfield',
operator: 'anyOf',
# type is needed for all search fields
type: 'SearchMultiSelectCustomField',
value: [
NetSuite::Records::CustomRecordRef.new(:internal_id => 1),
NetSuite::Records::CustomRecordRef.new(:internal_id => 2),
]
},
{
field: 'custbody_internetorder',
type: 'SearchBooleanCustomField',
value: true
}
]
}
]
}).results
NetSuite::Records::SalesOrder.search({
criteria: {
basic: [
# NOTE do not search for more than one transaction type at a time!
{
field: 'type',
operator: 'anyOf',
value: [ "_invoice"]
},
{
field: 'tranDate',
operator: 'within',
# this is needed for date range search requests, for date requests with a single param type is not needed
type: 'SearchDateField',
value: [
# NetSuite requires iso8601 time format
# need to require the time library for this to work
Time.parse("01/01/2012").iso8601,
Time.parse("30/07/2013").iso8601,
# or you can use a string. Note that the format below is different from the format of the above code
# but it matches exactly what NS returns
# "2012-01-01T22:00:00.000-07:00",
# "2013-07-30T22:00:00.000-07:00"
]
}
],
# equivilent to the 'Account' label in the GUI
accountJoin: [
{
field: 'internalId',
operator: 'noneOf',
value: [ NetSuite::Records::Account.new(:internal_id => 215) ]
}
],
itemJoin: [
{
field: 'customFieldList',
value: [
{
field: 'custitem_apcategoryforsales',
operator: 'anyOf',
type: 'SearchMultiSelectCustomField',
value: [
NetSuite::Records::Customer.new(:internal_id => 1),
NetSuite::Records::Customer.new(:internal_id => 2),
]
}
]
}
]
},
# the column syntax is a WIP. This will change in the future
columns: {
'tranSales:basic' => [
'platformCommon:internalId/' => {},
'platformCommon:email/' => {},
'platformCommon:tranDate/' => {}
],
'tranSales:accountJoin' => [
'platformCommon:internalId/' => {}
],
'tranSales:contactPrimaryJoin' => [
'platformCommon:internalId/' => {}
],
'tranSales:customerJoin' => [
'platformCommon:internalId/' => {}
],
'tranSales:itemJoin' => [
'platformCommon:customFieldList' => [
'platformCore:customField/' => {
'@internalId' => 'custitem_apcategoryforsales',
'@xsi:type' => "platformCore:SearchColumnSelectCustomField"
}
]
]
},
preferences: {
page_size: 10,
# only returning body fields increases performance!
body_fields_only: true
}
}).results
NetSuite::Records::ItemFulfillment.search({
criteria: {
basic: [
{
field: 'type',
operator: 'anyOf',
type: 'SearchEnumMultiSelectField',
value: ["_itemFulfillment"]
},
{
field: 'lastModifiedDate',
type: 'SearchDateField',
operator: 'within',
value: [
DateTime.now - 2.hours,
DateTime.now
]
}
],
createdFromJoin: [
{
field: 'type',
operator: 'anyOf',
value: [ '_salesOrder' ]
},
{
field: 'internalIdNumber',
operator: 'notEmpty'
}
]
},
preferences: {
pageSize: 1000,
bodyFieldsOnly: false
}
}).results
# basic search with pagination / SearchMorewithId
search = NetSuite::Records::Customer.search(
criteria: {
basic: [
{
# no operator for booleans
field: 'isInactive',
value: false,
},
]
},
preferences: {
page_size: 10,
}
)
search.results_in_batches do |batch|
puts batch.map(&:internal_id)
end
# item search
NetSuite::Records::InventoryItem.search({
criteria: {
basic: [
{
field: 'type',
operator: 'anyOf',
type: 'SearchEnumMultiSelectField',
value: [
'_inventoryItem',
# note that the naming conventions aren't consistent: AssemblyItem != _assemblyItem
# https://system.netsuite.com/help/helpcenter/en_US/srbrowser/Browser2014_1/schema/enum/itemtype.html
'_assembly'
]
},
{
field: 'isInactive',
value: false
}
]
}
}).results.first
# set body_fields_only = false to include the majority of lists associated with records in the XML response
# Some lists you just can't include with searches (a customer's AddressBookList, for example)
# In order to get the full record data for those records whose lists aren't included when body_fields_only = false
# you will have to run a get_list call on the resulting internalIds returned from the search you've executed
search = NetSuite::Records::File.search({
preferences: {
body_fields_only: false,
page_size: 20
}
})
search.results_in_batches do |batch|
batch.each do |file|
next unless file.file_type == "_JAVASCRIPT"
puts Base64.decode64(file.content)
end
end
# the getList operation
NetSuite::Records::CustomRecord.get_list(
# netsuite internalIDs
list: [1,2,3],
# only needed for a custom record
type_id: 1234,
# allow inclomplete results (defaults to false)
allow_incomplete: true
).each do |record|
# do your thing...
end
# Adding a Customer Deposit example. The customer associated with the
# sales order would be linked to the deposit.
deposit = CustomerDeposit.new
deposit.sales_order = RecordRef.new(internal_id: 7279)
deposit.payment = 20
deposit.add
# making a call that hasn't been implemented yet
NetSuite::Configuration.connection.call :get_customization_id, message: {
'platformMsgs:customizationType' => { '@getCustomizationType' => 'customRecordType'},
'platformMsgs:includeInactives' => 'false'
}
server_time_response = NetSuite::Configuration.connection.call :get_server_time
server_time_response.body[:get_server_time_response][:get_server_time_result][:server_time]
# getting a list of states
states = NetSuite::Configuration.connection.call(:get_all, message: {
'platformCore:record' => {
'@recordType' => 'state'
}
})
states.to_array.first[:get_all_response][:get_all_result][:record_list][:record].map { |r| { country: r[:country], abbr: r[:shortname], name: r[:full_name] } }