netsuite_client 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ === 0.0.1 2009-12-01
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
@@ -0,0 +1,8 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.rdoc
4
+ Rakefile
5
+ lib/netsuite_client.rb
6
+ lib/netsuite_client/client.rb
7
+ test/test_helper.rb
8
+ test/test_netsuite_client.rb
@@ -0,0 +1,54 @@
1
+ = netsuite_client
2
+
3
+ * http://github.com/vjebelev/netsuite_client
4
+
5
+ == DESCRIPTION:
6
+
7
+ Ruby soap4r-based Netsuite client.
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ Provides a simple way to access Netsuite data, such as sales orders, inventory items, invoices etc. A valid Netsuite account and a certain familiarity with Netsuite record data types are required.
12
+
13
+ Best to be used together with Rails Netsuite plugin which offers tighter integration with Active Record models and easier and more transparent access to netsuite data.
14
+
15
+
16
+ == SYNOPSIS:
17
+
18
+ client = NetsuiteClient.new(:account_id => 1, :email => 'xxx@xxx.com', :password => 'password')
19
+ sales_order = client.find_by_internal_id('TransactionSearchBasic', 1)
20
+
21
+
22
+ == REQUIREMENTS:
23
+
24
+ * a working Netsuite account
25
+ * ruby 1.8.6/1.8.7
26
+ * soap4r
27
+
28
+ == INSTALL:
29
+
30
+
31
+ == LICENSE:
32
+
33
+ (The MIT License)
34
+
35
+ Copyright (c) 2010 Vlad Jebelev
36
+
37
+ Permission is hereby granted, free of charge, to any person obtaining
38
+ a copy of this software and associated documentation files (the
39
+ 'Software'), to deal in the Software without restriction, including
40
+ without limitation the rights to use, copy, modify, merge, publish,
41
+ distribute, sublicense, and/or sell copies of the Software, and to
42
+ permit persons to whom the Software is furnished to do so, subject to
43
+ the following conditions:
44
+
45
+ The above copyright notice and this permission notice shall be
46
+ included in all copies or substantial portions of the Software.
47
+
48
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
49
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
50
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
51
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
52
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
53
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
54
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ gem 'hoe', '>= 2.1.0'
3
+ require 'hoe'
4
+ require 'fileutils'
5
+ require './lib/netsuite_client'
6
+
7
+ Hoe.plugin :newgem
8
+ # Hoe.plugin :website
9
+ # Hoe.plugin :cucumberfeatures
10
+
11
+ # Generate all the Rake tasks
12
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
13
+ $hoe = Hoe.spec 'netsuite_client' do
14
+ self.developer 'Vlad Jebelev', 'vlad@jebelev.com'
15
+ self.post_install_message = 'PostInstall.txt' # TODO remove if post-install message not required
16
+ self.rubyforge_name = self.name # TODO this is default value
17
+ # self.extra_deps = [['activesupport','>= 2.0.2']]
18
+
19
+ end
20
+
21
+ require 'newgem/tasks'
22
+ Dir['tasks/**/*.rake'].each { |t| load t }
23
+
24
+ # TODO - want other tests/tasks run by default? Add them to the list
25
+ # remove_task :default
26
+ # task :default => [:spec, :features]
@@ -0,0 +1,15 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'rubygems'
5
+ gem 'soap4r'
6
+
7
+ require 'netsuite_client/soap_netsuite'
8
+ require 'netsuite_client/string'
9
+ require 'netsuite_client/netsuite_exception'
10
+ require 'netsuite_client/netsuite_result'
11
+ require 'netsuite_client/client'
12
+
13
+ class NetsuiteClient
14
+ VERSION = '0.0.1'
15
+ end
@@ -0,0 +1,191 @@
1
+ require 'logger'
2
+ require 'net/http'
3
+ require 'net/https'
4
+
5
+ class NetsuiteClient
6
+ include NetSuite::SOAP
7
+
8
+ class NetsuiteHeader < SOAP::Header::SimpleHandler
9
+ def initialize(prefs = {})
10
+ @prefs = self.class::DefaultPrefs.merge(prefs)
11
+ super(XSD::QName.new(nil, self.class::Name))
12
+ end
13
+
14
+ def on_simple_outbound
15
+ @prefs
16
+ end
17
+ end
18
+
19
+ class SearchPreferencesHeaderHandler < NetsuiteHeader
20
+ Name = 'searchPreferences'
21
+ DefaultPrefs = {:bodyFieldsOnly => false, :pageSize => 25}
22
+ end
23
+
24
+ class PreferencesHeaderHandler < NetsuiteHeader
25
+ Name = 'preferences'
26
+ DefaultPrefs = {:warningAsError => false, :ignoreReadOnlyFields => true}
27
+ end
28
+
29
+ class PassportHeaderHandler < NetsuiteHeader
30
+ Name = 'passport'
31
+ DefaultPrefs = {:account => '', :email => '', :password => ''}
32
+ end
33
+
34
+ attr_accessor :logger
35
+
36
+ def initialize(config = {})
37
+ @config = config
38
+
39
+ @driver = NetSuitePortType.new(@config[:endpoint_url] || NetSuitePortType::DefaultEndpointUrl)
40
+ @driver.headerhandler.add(PassportHeaderHandler.new(:email => @config[:email], :password => @config[:password], :account => @config[:account_id]))
41
+ @driver.headerhandler.add(PreferencesHeaderHandler.new)
42
+ @driver.headerhandler.add(SearchPreferencesHeaderHandler.new)
43
+ end
44
+
45
+ def debug=(value)
46
+ @driver.wiredump_dev = value == true ? $stderr : nil
47
+ end
48
+
49
+ def find_by_internal_id(klass, id)
50
+ find_by_internal_ids(klass, [id])[0]
51
+ end
52
+
53
+ def find_by_internal_ids(klass, ids)
54
+ basic = constantize(klass).new
55
+ basic.internalId = SearchMultiSelectField.new
56
+ basic.internalId.xmlattr_operator = SearchMultiSelectFieldOperator::AnyOf
57
+
58
+ records = []
59
+ ids.each do |id|
60
+ record = RecordRef.new
61
+ record.xmlattr_internalId = id
62
+ records << record
63
+ end
64
+
65
+ basic.internalId.searchValue = records
66
+
67
+ full_basic_search(basic)
68
+ end
69
+
70
+ # Only supports equality for integers and strings for now.
71
+ def find_by(klass, name, value)
72
+ basic = constantize(klass).new
73
+
74
+ ref = nil
75
+ case value.class.to_s
76
+ when 'Fixnum'
77
+ ref = basic.send("#{name}=".to_sym, SearchLongField.new)
78
+ ref.xmlattr_operator = SearchLongFieldOperator::EqualTo
79
+
80
+ else
81
+ ref = basic.send("#{name}=".to_sym, SearchStringField.new)
82
+ ref.xmlattr_operator = SearchStringFieldOperator::Is
83
+ end
84
+
85
+ ref.searchValue = value
86
+
87
+ full_basic_search(basic)
88
+ end
89
+
90
+ def get(klass, id)
91
+ ref = RecordRef.new
92
+ ref.xmlattr_type = constantize(klass)
93
+ ref.xmlattr_internalId = id
94
+
95
+ res = @driver.get(GetRequest.new(ref))
96
+ res && res.readResponse.status.xmlattr_isSuccess ? res.readResponse.record : nil
97
+ end
98
+
99
+ def get_all(klass)
100
+ ref = GetAllRecord.new
101
+ ref.xmlattr_recordType = constantize(klass)
102
+
103
+ res = @driver.getAll(GetAllRequest.new(ref))
104
+ res && res.getAllResult.status.xmlattr_isSuccess ? res.getAllResult.recordList : []
105
+ end
106
+
107
+ def add(ref)
108
+ res = @driver.add(AddRequest.new(ref))
109
+ NetsuiteResult.new(res.writeResponse)
110
+ end
111
+
112
+ def update(ref)
113
+ res = @driver.update(UpdateRequest.new(ref))
114
+ NetsuiteResult.new(res.writeResponse)
115
+ end
116
+
117
+ def delete(ref)
118
+ r = RecordRef.new
119
+ r.xmlattr_type = ref.class.to_s.split('::').last.sub(/^(\w)/) {|s|$1.downcase}
120
+ r.xmlattr_internalId = ref.xmlattr_internalId
121
+
122
+ res = @driver.delete(DeleteRequest.new(r))
123
+ NetsuiteResult.new(res.writeResponse)
124
+ end
125
+
126
+ private
127
+
128
+ # Get the full result set (possibly across multiple pages).
129
+ def full_basic_search(basic)
130
+ records, res = exec_basic_search(basic)
131
+ unless res && res.status.xmlattr_isSuccess
132
+ return []
133
+ end
134
+
135
+ if res.totalPages > 1
136
+ while res.pageIndex < res.totalPages
137
+ next_records, res = exec_next_search(res.searchId, res.pageIndex+1)
138
+ records += next_records
139
+ end
140
+ end
141
+
142
+ records
143
+ end
144
+
145
+ # Get the first page of search results for basic search.
146
+ def exec_basic_search(basic)
147
+ exec_with_retry do
148
+ search = constantize(basic.class.to_s.sub(/Basic/, '')).new
149
+ search.basic = basic
150
+
151
+ res = @driver.search(search)
152
+ return res.searchResult.recordList, res.searchResult
153
+ end
154
+ end
155
+
156
+ # Get the next page of results.
157
+ def exec_next_search(search_id, page)
158
+ exec_with_retry do
159
+ res = @driver.searchMoreWithId("searchId" => search_id, "pageIndex" => page)
160
+ return res.searchResult.recordList, res.searchResult
161
+ end
162
+ end
163
+
164
+ def exec_with_retry(&block)
165
+ tries = 5
166
+
167
+ begin
168
+ yield
169
+
170
+ rescue => e
171
+
172
+ logger.warn "Exception: #{e.message}"
173
+ sleep 0.1
174
+
175
+ if tries > 0
176
+ tries -= 1
177
+ logger.debug "#{$$} retrying, tries left: #{tries}"
178
+ retry
179
+ end
180
+
181
+ raise NetsuiteException.new(e)
182
+ end
183
+ end
184
+
185
+ def constantize(klass)
186
+ klass.constantize
187
+
188
+ rescue NameError
189
+ "NetSuite::SOAP::#{klass}".constantize
190
+ end
191
+ end
@@ -0,0 +1,3 @@
1
+ require 'stringio'
2
+ require 'test/unit'
3
+ require File.dirname(__FILE__) + '/../lib/netsuite_client'
@@ -0,0 +1,88 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class TestNetsuiteClient < Test::Unit::TestCase
4
+ include NetSuite::SOAP
5
+
6
+ def setup
7
+ ENV['NS_ENDPOINT_URL'] ||= 'https://webservices.sandbox.netsuite.com/services/NetSuitePort_2009_1'
8
+
9
+ unless ENV['NS_ACCOUNT_ID'] && ENV['NS_EMAIL'] && ENV['NS_PASSWORD']
10
+ puts "Ensure that your environment variables are set: NS_ACCOUNT_ID, NS_EMAIL, NS_PASSWORD"
11
+ exit(-1)
12
+ end
13
+
14
+ @client = NetsuiteClient.new(:account_id => ENV['NS_ACCOUNT_ID'], :email => ENV['NS_EMAIL'], :password => ENV['NS_PASSWORD'], :endpoint_url => ENV['NS_ENDPOINT_URL'])
15
+ #@client.debug = true
16
+ end
17
+
18
+ def test_init
19
+ assert_not_nil @client
20
+ end
21
+
22
+ def test_find_by_internal_id
23
+ records = @client.find_by_internal_ids('TransactionSearchBasic', [1])
24
+ assert_equal [], records
25
+ end
26
+
27
+ def test_get
28
+ record = @client.get('RecordType::PaymentMethod', 1)
29
+ assert_not_nil record
30
+ assert_equal 1, record.xmlattr_internalId.to_i
31
+ assert_equal 'NetSuite::SOAP::PaymentMethod', record.class.to_s
32
+ end
33
+
34
+ def test_get_all
35
+ records = @client.get_all('RecordType::PaymentMethod')
36
+ assert records.any?
37
+ assert records.all? {|r| r.class.to_s == 'NetSuite::SOAP::PaymentMethod'}
38
+ end
39
+
40
+ def test_add_inventory_item
41
+ ref = InventoryItem.new
42
+ ref.itemId = 'test inventory item'
43
+ res = @client.add(ref)
44
+ assert_not_nil res
45
+ assert res.success? || res.error_code == 'DUP_ITEM'
46
+ end
47
+
48
+ def test_find_by_item_id
49
+ test_add_inventory_item
50
+ item = @client.find_by('ItemSearchBasic', 'itemId', 'test inventory item')
51
+
52
+ assert_not_nil item
53
+ assert_equal 'NetSuite::SOAP::RecordList', item.class.to_s
54
+ assert_equal 1, item.size
55
+ assert_equal 'NetSuite::SOAP::InventoryItem', item[0].class.to_s
56
+ end
57
+
58
+ def test_update_inventory_item
59
+ test_add_inventory_item
60
+ new_name = String.random_string
61
+
62
+ item = @client.find_by('ItemSearchBasic', 'itemId', 'test inventory item')[0]
63
+ assert item.displayName != new_name
64
+
65
+ ref = InventoryItem.new
66
+ ref.xmlattr_internalId = item.xmlattr_internalId
67
+ ref.displayName = new_name
68
+ res = @client.update(ref)
69
+ assert_not_nil res
70
+ assert res.success?
71
+
72
+ item = @client.find_by('ItemSearchBasic', 'itemId', 'test inventory item')[0]
73
+ assert item.displayName == new_name
74
+ end
75
+
76
+ def test_delete_inventory_item
77
+ test_add_inventory_item
78
+ item = @client.find_by('ItemSearchBasic', 'itemId', 'test inventory item')[0]
79
+ assert_not_nil item
80
+
81
+ ref = InventoryItem.new
82
+ ref.xmlattr_internalId = item.xmlattr_internalId
83
+ res = @client.delete(ref)
84
+ assert_not_nil res
85
+ assert res.success?
86
+ assert_nil @client.find_by('ItemSearchBasic', 'itemId', 'test inventory item')[0]
87
+ end
88
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: netsuite_client
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Vlad Jebelev
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-07-06 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: hoe
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 5
30
+ segments:
31
+ - 2
32
+ - 3
33
+ - 3
34
+ version: 2.3.3
35
+ type: :development
36
+ version_requirements: *id001
37
+ description: Ruby soap4r-based Netsuite client.
38
+ email:
39
+ - vlad@jebelev.com
40
+ executables: []
41
+
42
+ extensions: []
43
+
44
+ extra_rdoc_files:
45
+ - History.txt
46
+ - Manifest.txt
47
+ files:
48
+ - History.txt
49
+ - Manifest.txt
50
+ - README.rdoc
51
+ - Rakefile
52
+ - lib/netsuite_client.rb
53
+ - lib/netsuite_client/client.rb
54
+ - test/test_helper.rb
55
+ - test/test_netsuite_client.rb
56
+ has_rdoc: true
57
+ homepage: http://github.com/vjebelev/netsuite_client
58
+ licenses: []
59
+
60
+ post_install_message: PostInstall.txt
61
+ rdoc_options:
62
+ - --main
63
+ - README.rdoc
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ hash: 3
81
+ segments:
82
+ - 0
83
+ version: "0"
84
+ requirements: []
85
+
86
+ rubyforge_project: netsuite_client
87
+ rubygems_version: 1.3.7
88
+ signing_key:
89
+ specification_version: 3
90
+ summary: Ruby soap4r-based Netsuite client.
91
+ test_files:
92
+ - test/test_netsuite_client.rb
93
+ - test/test_helper.rb