netsuite_client 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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