netsuite 0.1.0 → 0.2.0

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.
Files changed (45) hide show
  1. data/Gemfile +0 -2
  2. data/README.md +154 -0
  3. data/lib/netsuite.rb +7 -0
  4. data/lib/netsuite/actions/add.rb +6 -21
  5. data/lib/netsuite/actions/delete.rb +7 -10
  6. data/lib/netsuite/actions/get.rb +7 -10
  7. data/lib/netsuite/actions/get_list.rb +77 -0
  8. data/lib/netsuite/actions/get_select_value.rb +71 -0
  9. data/lib/netsuite/actions/search.rb +151 -36
  10. data/lib/netsuite/actions/search_more_with_id.rb +3 -2
  11. data/lib/netsuite/actions/update.rb +16 -16
  12. data/lib/netsuite/configuration.rb +29 -15
  13. data/lib/netsuite/namespaces/list_support.rb +11 -0
  14. data/lib/netsuite/records/account.rb +1 -0
  15. data/lib/netsuite/records/base_ref_list.rb +33 -0
  16. data/lib/netsuite/records/cash_sale.rb +7 -0
  17. data/lib/netsuite/records/custom_field.rb +2 -1
  18. data/lib/netsuite/records/custom_field_list.rb +62 -12
  19. data/lib/netsuite/records/custom_record.rb +1 -7
  20. data/lib/netsuite/records/custom_record_ref.rb +7 -0
  21. data/lib/netsuite/records/customer.rb +4 -3
  22. data/lib/netsuite/records/phone_call.rb +29 -0
  23. data/lib/netsuite/records/record_ref.rb +1 -9
  24. data/lib/netsuite/records/sales_order.rb +2 -1
  25. data/lib/netsuite/records/support_case.rb +32 -0
  26. data/lib/netsuite/records/task.rb +4 -3
  27. data/lib/netsuite/records/transaction.rb +4 -21
  28. data/lib/netsuite/support/actions.rb +4 -0
  29. data/lib/netsuite/support/records.rb +3 -0
  30. data/lib/netsuite/support/search_result.rb +53 -29
  31. data/lib/netsuite/version.rb +1 -1
  32. data/netsuite.gemspec +1 -1
  33. data/spec/netsuite/actions/add_spec.rb +9 -13
  34. data/spec/netsuite/actions/delete_spec.rb +5 -8
  35. data/spec/netsuite/actions/get_spec.rb +39 -24
  36. data/spec/netsuite/actions/search_spec.rb +149 -0
  37. data/spec/netsuite/actions/update_spec.rb +9 -13
  38. data/spec/netsuite/configuration_spec.rb +3 -9
  39. data/spec/netsuite/records/custom_field_list_spec.rb +19 -5
  40. data/spec/netsuite/records/customer_spec.rb +2 -2
  41. data/spec/netsuite/records/phone_call_spec.rb +23 -0
  42. data/spec/netsuite/records/support_case_spec.rb +157 -0
  43. data/spec/support/fixtures/search/saved_search_customer.xml +54 -0
  44. data/spec/support/fixtures/search/saved_search_joined_custom_customer.xml +87 -0
  45. metadata +21 -4
data/Gemfile CHANGED
@@ -1,4 +1,2 @@
1
1
  source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in netsuite.gemspec
4
2
  gemspec
data/README.md CHANGED
@@ -100,6 +100,160 @@ search = NetSuite::Records::Customer.search({
100
100
  })
101
101
 
102
102
  `open https://system.netsuite.com/app/common/entity/custjob.nl?id=#{search.results.first.internal_id}`
103
+
104
+ # advanced search building on saved search
105
+ search = NetSuite::Records::Customer.search({
106
+ saved: 500, # your saved search internalId
107
+ basic: [
108
+ {
109
+ field: 'entityId',
110
+ operator: 'hasKeywords',
111
+ value: 'Assumption',
112
+ },
113
+ {
114
+ field: 'stage',
115
+ operator: 'anyOf',
116
+ type: 'SearchMultiSelectCustomField',
117
+ value: [
118
+ '_lead', '_customer'
119
+ ]
120
+ },
121
+ {
122
+ field: 'customFieldList',
123
+ value: [
124
+ {
125
+ field: 'custentity_acustomfield',
126
+ operator: 'anyOf',
127
+ # type is needed for multiselect fields
128
+ type: 'SearchMultiSelectCustomField',
129
+ value: [
130
+ NetSuite::Records::CustomRecordRef.new(:internal_id => 1),
131
+ NetSuite::Records::CustomRecordRef.new(:internal_id => 2),
132
+ ]
133
+ }
134
+ ]
135
+ }
136
+ ]
137
+ })
138
+
139
+ # advanced search from stratch
140
+ search = NetSuite::Records::Transaction.search({
141
+ criteria: {
142
+ basic: [
143
+ {
144
+ field: 'type',
145
+ operator: 'anyOf',
146
+ type: 'SearchEnumMultiSelectField',
147
+ value: [ "_invoice", "_salesOrder" ]
148
+ },
149
+ {
150
+ field: 'tranDate',
151
+ operator: 'within',
152
+ # this is needed for date range search requests, for date requests with a single param type is not needed
153
+ type: 'SearchDateField',
154
+ value: [
155
+ # the following format is equivilent to ISO 8601
156
+ # Date.parse("1/1/2012").strftime("%Y-%m-%dT%H:%M:%S%z"),
157
+ # Date.parse("30/07/2013").strftime("%Y-%m-%dT%H:%M:%S%z")
158
+
159
+ # need to require the time library for this to work
160
+ Time.parse("01/01/2012").iso8601,
161
+ Time.parse("30/07/2013").iso8601,
162
+
163
+ # or you can use a string. Note that the format below is different from the format of the above code
164
+ # but it matches exactly what NS returns
165
+ # "2012-01-01T22:00:00.000-07:00",
166
+ # "2013-07-30T22:00:00.000-07:00"
167
+ ]
168
+ }
169
+ ],
170
+
171
+
172
+ # equivilent to the 'Account' label in the GUI
173
+ accountJoin: [
174
+ {
175
+ field: 'internalId',
176
+ operator: 'noneOf',
177
+ value: [ NetSuite::Records::Account.new(:internal_id => 215) ]
178
+ }
179
+ ],
180
+
181
+ itemJoin: [
182
+ {
183
+ field: 'customFieldList',
184
+ value: [
185
+ {
186
+ field: 'custitem_apcategoryforsales',
187
+ operator: 'anyOf',
188
+ type: 'SearchMultiSelectCustomField',
189
+ value: [
190
+ NetSuite::Records::Customer.new(:internal_id => 1),
191
+ NetSuite::Records::Customer.new(:internal_id => 2),
192
+ ]
193
+ }
194
+ ]
195
+ }
196
+ ]
197
+ },
198
+
199
+ # the column syntax is a WIP. This will change in the future
200
+ columns: {
201
+ 'tranSales:basic' => [
202
+ 'platformCommon:internalId/' => {},
203
+ 'platformCommon:email/' => {},
204
+ 'platformCommon:tranDate/' => {}
205
+ ],
206
+ 'tranSales:accountJoin' => [
207
+ 'platformCommon:internalId/' => {}
208
+ ],
209
+ 'tranSales:contactPrimaryJoin' => [
210
+ 'platformCommon:internalId/' => {}
211
+ ],
212
+ 'tranSales:customerJoin' => [
213
+ 'platformCommon:internalId/' => {}
214
+ ],
215
+ 'tranSales:itemJoin' => [
216
+ 'platformCommon:customFieldList' => [
217
+ 'platformCore:customField/' => {
218
+ '@internalId' => 'custitem_apcategoryforsales',
219
+ '@xsi:type' => "platformCore:SearchColumnSelectCustomField"
220
+ }
221
+ ]
222
+ ]
223
+ },
224
+
225
+ preferences: {
226
+ page_size: 10
227
+ }
228
+ })
229
+
230
+ # basic search with pagination / SearchMorewithId
231
+ search = NetSuite::Records::Customer.search(
232
+ criteria: {
233
+ basic: [
234
+ {
235
+ # no operator for booleans
236
+ field: 'isInactive',
237
+ value: false,
238
+ },
239
+ ]
240
+ },
241
+
242
+ preferences: {
243
+ page_size: 10,
244
+ }
245
+ )
246
+
247
+ search.results_in_batches do |batch|
248
+ puts batch.map(&:internal_id)
249
+ end
250
+
251
+ # making a call that hasn't been implemented yet
252
+ NetSuite::Configuration.connection.call :get_customization_id, message: {
253
+ 'platformMsgs:customizationType' => { '@getCustomizationType' => 'customRecordType'},
254
+ 'platformMsgs:includeInactives' => 'false'
255
+ }
256
+
103
257
  ```
104
258
 
105
259
 
@@ -13,6 +13,7 @@ module NetSuite
13
13
  autoload :ActSched, 'netsuite/namespaces/act_sched'
14
14
  autoload :ListAcct, 'netsuite/namespaces/list_acct'
15
15
  autoload :ListRel, 'netsuite/namespaces/list_rel'
16
+ autoload :ListSupport, 'netsuite/namespaces/list_support'
16
17
  autoload :PlatformCommon, 'netsuite/namespaces/platform_common'
17
18
  autoload :PlatformCore, 'netsuite/namespaces/platform_core'
18
19
  autoload :TranCust, 'netsuite/namespaces/tran_cust'
@@ -35,6 +36,8 @@ module NetSuite
35
36
  autoload :Add, 'netsuite/actions/add'
36
37
  autoload :Delete, 'netsuite/actions/delete'
37
38
  autoload :Get, 'netsuite/actions/get'
39
+ autoload :GetList, 'netsuite/actions/get_list'
40
+ autoload :GetSelectValue, 'netsuite/actions/get_select_value'
38
41
  autoload :Initialize, 'netsuite/actions/initialize'
39
42
  autoload :Update, 'netsuite/actions/update'
40
43
  autoload :Search, 'netsuite/actions/search'
@@ -45,8 +48,10 @@ module NetSuite
45
48
  autoload :AssemblyItem, 'netsuite/records/assembly_item'
46
49
  autoload :Account, 'netsuite/records/account'
47
50
  autoload :AccountingPeriod, 'netsuite/records/accounting_period'
51
+ autoload :BaseRefList, 'netsuite/records/base_ref_list'
48
52
  autoload :BillAddress, 'netsuite/records/bill_address'
49
53
  autoload :BinNumberList, 'netsuite/records/bin_number_list'
54
+ autoload :CashSale, 'netsuite/records/cash_sale'
50
55
  autoload :Classification, 'netsuite/records/classification'
51
56
  autoload :CreditMemo, 'netsuite/records/credit_memo'
52
57
  autoload :CreditMemoApply, 'netsuite/records/credit_memo_apply'
@@ -88,6 +93,7 @@ module NetSuite
88
93
  autoload :Location, 'netsuite/records/location'
89
94
  autoload :NonInventorySaleItem, 'netsuite/records/non_inventory_sale_item'
90
95
  autoload :PaymentMethod, 'netsuite/records/payment_method'
96
+ autoload :PhoneCall, 'netsuite/records/phone_call'
91
97
  autoload :PricingMatrix, 'netsuite/records/pricing_matrix'
92
98
  autoload :RecordRef, 'netsuite/records/record_ref'
93
99
  autoload :RevRecTemplate, 'netsuite/records/rev_rec_template'
@@ -95,6 +101,7 @@ module NetSuite
95
101
  autoload :SalesOrderItem, 'netsuite/records/sales_order_item'
96
102
  autoload :SalesOrderItemList, 'netsuite/records/sales_order_item_list'
97
103
  autoload :ShipAddress, 'netsuite/records/ship_address'
104
+ autoload :SupportCase, 'netsuite/records/support_case'
98
105
  autoload :Task, 'netsuite/records/task'
99
106
  autoload :Term, 'netsuite/records/term'
100
107
  autoload :Transaction, 'netsuite/records/transaction'
@@ -10,20 +10,7 @@ module NetSuite
10
10
  private
11
11
 
12
12
  def request
13
- NetSuite::Configuration.connection(
14
- namespaces: {
15
- 'xmlns:platformMsgs' => "urn:core_#{NetSuite::Configuration.api_version}.platform.webservices.netsuite.com",
16
- 'xmlns:platformCore' => "urn:core_#{NetSuite::Configuration.api_version}.platform.webservices.netsuite.com",
17
- 'xmlns:listRel' => "urn:relationships_#{NetSuite::Configuration.api_version}.lists.webservices.netsuite.com",
18
- 'xmlns:tranSales' => "urn:sales_#{NetSuite::Configuration.api_version}.transactions.webservices.netsuite.com",
19
- 'xmlns:platformCommon' => "urn:common_#{NetSuite::Configuration.api_version}.platform.webservices.netsuite.com",
20
- 'xmlns:listAcct' => "urn:accounting_#{NetSuite::Configuration.api_version}.lists.webservices.netsuite.com",
21
- 'xmlns:actSched' => "urn:scheduling_#{NetSuite::Configuration.api_version}.activities.webservices.netsuite.com",
22
- 'xmlns:tranCust' => "urn:customers_#{NetSuite::Configuration.api_version}.transactions.webservices.netsuite.com",
23
- 'xmlns:setupCustom' => "urn:customization_#{NetSuite::Configuration.api_version}.setup.webservices.netsuite.com",
24
- 'xmlns:tranGeneral' => "urn:general_#{NetSuite::Configuration.api_version}.transactions.webservices.netsuite.com",
25
- },
26
- ).call :add, :message => request_body
13
+ NetSuite::Configuration.connection.call :add, :message => request_body
27
14
  end
28
15
 
29
16
  # <soap:Body>
@@ -37,20 +24,18 @@ module NetSuite
37
24
 
38
25
  def request_body
39
26
  hash = {
40
- 'platformMsgs:record' => @object.to_record,
41
- :attributes! => {
42
- 'platformMsgs:record' => {
43
- 'xsi:type' => @object.record_type
44
- }
27
+ 'platformMsgs:record' => {
28
+ :content! => @object.to_record,
29
+ '@xsi:type' => @object.record_type
45
30
  }
46
31
  }
47
32
 
48
33
  if @object.respond_to?(:internal_id) && @object.internal_id
49
- hash[:attributes!]['platformMsgs:record']['platformMsgs:internalId'] = @object.internal_id
34
+ hash['platformMsgs:record']['@platformMsgs:internalId'] = @object.internal_id
50
35
  end
51
36
 
52
37
  if @object.respond_to?(:external_id) && @object.external_id
53
- hash[:attributes!]['platformMsgs:record']['platformMsgs:externalId'] = @object.external_id
38
+ hash['platformMsgs:record']['@platformMsgs:externalId'] = @object.external_id
54
39
  end
55
40
 
56
41
  hash
@@ -30,27 +30,24 @@ module NetSuite
30
30
  # </soap:Body>
31
31
  def request_body
32
32
  body = {
33
- 'platformMsgs:baseRef' => {},
34
- :attributes! => {
35
- 'platformMsgs:baseRef' => {
36
- 'xsi:type' => (@options[:custom] ? 'platformCore:CustomRecordRef' : 'platformCore:RecordRef')
37
- }
38
- }
33
+ 'platformMsgs:baseRef' => {
34
+ '@xsi:type' => (@options[:custom] ? 'platformCore:CustomRecordRef' : 'platformCore:RecordRef')
35
+ },
39
36
  }
40
37
 
41
38
  if @object.respond_to?(:external_id) && @object.external_id
42
- body[:attributes!]['platformMsgs:baseRef']['externalId'] = @object.external_id
39
+ body['platformMsgs:baseRef']['@externalId'] = @object.external_id
43
40
  end
44
41
 
45
42
  if @object.respond_to?(:internal_id) && @object.internal_id
46
- body[:attributes!]['platformMsgs:baseRef']['internalId'] = @object.internal_id
43
+ body['platformMsgs:baseRef']['@internalId'] = @object.internal_id
47
44
  end
48
45
 
49
46
  if @object.class.respond_to?(:type_id) && @object.class.type_id
50
- body[:attributes!]['platformMsgs:baseRef']['typeId'] = @object.class.type_id
47
+ body['platformMsgs:baseRef']['@typeId'] = @object.class.type_id
51
48
  end
52
49
 
53
- body[:attributes!]['platformMsgs:baseRef']['type'] = soap_type unless @options[:custom]
50
+ body['platformMsgs:baseRef']['@type'] = soap_type unless @options[:custom]
54
51
 
55
52
  body
56
53
  end
@@ -16,7 +16,7 @@ module NetSuite
16
16
  'xmlns:platformMsgs' => "urn:messages_#{NetSuite::Configuration.api_version}.platform.webservices.netsuite.com",
17
17
  'xmlns:platformCore' => "urn:core_#{NetSuite::Configuration.api_version}.platform.webservices.netsuite.com"
18
18
  },
19
- ).call :get, message: request_body#, message_tag: :platformMsgs
19
+ ).call :get, message: request_body
20
20
  end
21
21
 
22
22
  def soap_type
@@ -32,17 +32,14 @@ module NetSuite
32
32
  # </soap:Body>
33
33
  def request_body
34
34
  body = {
35
- 'platformMsgs:baseRef' => {},
36
- :attributes! => {
37
- 'platformMsgs:baseRef' => {
38
- 'xsi:type' => (@options[:custom] ? 'platformCore:CustomRecordRef' : 'platformCore:RecordRef')
39
- }
35
+ 'platformMsgs:baseRef' => {
36
+ '@xsi:type' => (@options[:custom] ? 'platformCore:CustomRecordRef' : 'platformCore:RecordRef')
40
37
  }
41
38
  }
42
- body[:attributes!]['platformMsgs:baseRef']['externalId'] = @options[:external_id] if @options[:external_id]
43
- body[:attributes!]['platformMsgs:baseRef']['internalId'] = @options[:internal_id] if @options[:internal_id]
44
- body[:attributes!]['platformMsgs:baseRef']['typeId'] = @options[:type_id] if @options[:type_id]
45
- body[:attributes!]['platformMsgs:baseRef']['type'] = soap_type unless @options[:custom]
39
+ body['platformMsgs:baseRef']['@externalId'] = @options[:external_id] if @options[:external_id]
40
+ body['platformMsgs:baseRef']['@internalId'] = @options[:internal_id] if @options[:internal_id]
41
+ body['platformMsgs:baseRef']['@typeId'] = @options[:type_id] if @options[:type_id]
42
+ body['platformMsgs:baseRef']['@type'] = soap_type unless @options[:custom]
46
43
  body
47
44
  end
48
45
 
@@ -0,0 +1,77 @@
1
+ module NetSuite
2
+ module Actions
3
+ class GetList
4
+ include Support::Requests
5
+
6
+ def initialize(klass, options = { })
7
+ @klass = klass
8
+ @options = options
9
+ end
10
+
11
+ private
12
+
13
+ def request
14
+ NetSuite::Configuration.connection.call :get_list, :message => request_body
15
+ end
16
+
17
+ def request_body
18
+ if @options[:type_id]
19
+ type = @options[:type_id]
20
+ record_type = 'platformCore:CustomRecordRef'
21
+ else
22
+ type = @klass.to_s.split('::').last.lower_camelcase
23
+ record_type = 'platformCore:RecordRef'
24
+ end
25
+
26
+ list = @options.is_a?(Hash) ? @options[:list] : @options
27
+
28
+ {
29
+ baseRef: list.map do |internal_id|
30
+ {
31
+ '@internalId' => internal_id,
32
+ '@typeId' => type,
33
+ '@xsi:type' => record_type
34
+ }
35
+ end
36
+ }
37
+ end
38
+
39
+ def response_header
40
+ @response_header ||= response_header_hash
41
+ end
42
+
43
+ def response_header_hash
44
+ @response_header_hash = @response.header[:document_info]
45
+ end
46
+
47
+ def response_body
48
+ @response_body ||= @response.body[:get_list_response][:read_response_list][:read_response]
49
+ end
50
+
51
+ def success?
52
+ # each returned record has its own status; for now if one fails, the entire operation has failed
53
+ @success ||= response_body.detect { |r| r[:status][:@is_success] != 'true' }.nil?
54
+ end
55
+
56
+ module Support
57
+ def self.included(base)
58
+ base.extend(ClassMethods)
59
+ end
60
+
61
+ module ClassMethods
62
+ def get_list(options = { })
63
+ response = NetSuite::Actions::GetList.call(self, options)
64
+
65
+ if response.success?
66
+ response.body.map do |record|
67
+ new(record[:record])
68
+ end
69
+ else
70
+ false
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,71 @@
1
+ module NetSuite
2
+ module Actions
3
+ class GetSelectValue
4
+ include Support::Requests
5
+
6
+ def initialize(klass, options = {})
7
+ @klass = klass
8
+ @options = options
9
+ end
10
+
11
+ private
12
+
13
+ def request
14
+ NetSuite::Configuration.connection(
15
+ namespaces: {
16
+ 'xmlns:platformMsgs' => "urn:messages_#{NetSuite::Configuration.api_version}.platform.webservices.netsuite.com",
17
+ 'xmlns:platformCore' => "urn:core_#{NetSuite::Configuration.api_version}.platform.webservices.netsuite.com"
18
+ },
19
+ ).call :get_select_value, :message => @options
20
+ end
21
+
22
+ def success?
23
+ @success ||= response_hash[:status][:@is_success] == 'true'
24
+ end
25
+
26
+ def response_body
27
+ @response_body ||= response_hash[:base_ref_list]
28
+ end
29
+
30
+ def response_hash
31
+ @response_hash = @response.body[:get_select_value_response][:get_select_value_result]
32
+ end
33
+
34
+ module Support
35
+
36
+ def self.included(base)
37
+ base.extend(ClassMethods)
38
+ end
39
+
40
+ module ClassMethods
41
+
42
+ def get_select_value(options = {})
43
+ message = {
44
+ pageIndex: (options.delete(:pageIndex) || 1),
45
+ fieldDescription: field_description(options)
46
+ }
47
+
48
+ response = NetSuite::Actions::GetSelectValue.call(self, message)
49
+
50
+ if response.success?
51
+ new(response.body)
52
+ else
53
+ raise RecordNotFound, "#{self} with OPTIONS=#{options.inspect} could not be found"
54
+ end
55
+ end
56
+
57
+ private
58
+ # TODO this goes against the design of the rest of the gem; should be removed in the future
59
+ def field_description(options)
60
+ options.inject({}) do |h, (k, v)|
61
+ h["platformCore:#{k}"] = v
62
+ h
63
+ end
64
+ end
65
+
66
+ end
67
+ end
68
+
69
+ end
70
+ end
71
+ end