netsuite_rails 0.2.2 → 0.3.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.
Files changed (34) hide show
  1. checksums.yaml +15 -0
  2. data/Gemfile +4 -2
  3. data/README.md +186 -10
  4. data/circle.yml +3 -0
  5. data/lib/netsuite_rails/configuration.rb +7 -5
  6. data/lib/netsuite_rails/netsuite_rails.rb +24 -4
  7. data/lib/netsuite_rails/poll_trigger.rb +10 -9
  8. data/lib/netsuite_rails/record_sync/poll_manager.rb +23 -11
  9. data/lib/netsuite_rails/record_sync/pull_manager.rb +2 -0
  10. data/lib/netsuite_rails/record_sync/push_manager.rb +76 -38
  11. data/lib/netsuite_rails/record_sync.rb +28 -11
  12. data/lib/netsuite_rails/routines/company_contact_match.rb +98 -0
  13. data/lib/netsuite_rails/spec/disabler.rb +27 -0
  14. data/lib/netsuite_rails/spec/query_helpers.rb +93 -0
  15. data/lib/netsuite_rails/spec/spec_helper.rb +2 -79
  16. data/lib/netsuite_rails/sync_trigger.rb +40 -17
  17. data/lib/netsuite_rails/tasks/netsuite.rb +33 -4
  18. data/lib/netsuite_rails/transformations.rb +59 -19
  19. data/lib/netsuite_rails/url_helper.rb +45 -12
  20. data/netsuite_rails.gemspec +2 -2
  21. data/spec/models/configuration_spec.rb +11 -0
  22. data/spec/models/poll_manager_spec.rb +11 -2
  23. data/spec/models/poll_trigger_spec.rb +31 -11
  24. data/spec/models/record_sync/push_manager_spec.rb +51 -0
  25. data/spec/models/record_sync_spec.rb +16 -0
  26. data/spec/models/spec_helper_spec.rb +1 -2
  27. data/spec/models/transformations_spec.rb +62 -0
  28. data/spec/models/url_helper_spec.rb +20 -9
  29. data/spec/spec_helper.rb +19 -0
  30. data/spec/support/example_models.rb +33 -1
  31. metadata +19 -25
  32. data/.travis.yml +0 -3
  33. data/lib/netsuite_rails/netsuite_configure.rb +0 -14
  34. data/spec/support/netsuite_rails.rb +0 -1
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YTVkYzk2NDQyNzI5MGZjODkxOTM4MWFkYjExYTNlZWZkNGFjNjFmMQ==
5
+ data.tar.gz: !binary |-
6
+ NjdmOWIxMmE0MjNkYjE2MTU2ZDBmMzg4MDViNWZjMzdmZGIwZjgzOQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ OWJlMzFhMGJhNTM1NGZkMjRmMGMwNmU2ZmUzZTFhODQxMGQzYjZhMWNmMjE2
10
+ M2NlYjgwMTc0MWU4Y2FhMTBjNWVjZGM0NzU2NWIxNDY1NjMzYTE2N2ZlYWM4
11
+ N2NkOTY2ZTJhNzBiMDg2NjY0MTY4ZWYyYmMxNjk4NDc3NzQ5NjQ=
12
+ data.tar.gz: !binary |-
13
+ MDE5NmE5OWZmYjhjYzIyMGQ3ZmFmMzljZDc1ZTkwZDQ1MjRjYjUyZTk4MjAx
14
+ YjI2NTM1YmZmNWE5NjE0MGZhMzg1NmZlOTA5ZDA0NWU0OWU2ODgyNmM4NzMx
15
+ YzUxOTY5ODQ1MDQ0YWE3NTNkY2YwYjA1NmY4YThlNjUyNTgwMDc=
data/Gemfile CHANGED
@@ -8,6 +8,8 @@ group :test do
8
8
  # gem 'rack-test'
9
9
  # gem 'webmock'
10
10
 
11
+ gem 'simplecov', :require => false
12
+
11
13
  gem 'faker'
12
14
  gem 'shoulda-matchers'
13
15
  gem 'rails', '3.2.16'
@@ -15,7 +17,7 @@ group :test do
15
17
 
16
18
  gem 'rspec-rails', '~> 3.1'
17
19
  gem 'pry-nav'
18
-
20
+
19
21
  gem 'rerun'
20
22
  gem 'rb-fsevent'
21
- end
23
+ end
data/README.md CHANGED
@@ -1,10 +1,90 @@
1
- [![Build Status](https://travis-ci.org/NetSweet/netsuite_rails.svg?branch=master)](https://travis-ci.org/NetSweet/netsuite_rails)
1
+ [![Circle CI](https://circleci.com/gh/NetSweet/netsuite_rails.svg?style=svg)](https://circleci.com/gh/NetSweet/netsuite_rails)
2
+ [![Slack Status](https://opensuite-slackin.herokuapp.com/badge.svg)](http://opensuite-slackin.herokuapp.com)
2
3
 
3
4
  # NetSuite Rails
4
5
 
5
- **Note:** Documentation is horrible... look at the code for details.
6
+ **<span style="color: red">Note:</span>** Documentation is horrible: PRs welcome. Look at the code for details.
6
7
 
7
- Build custom Ruby on Rails applications that sync to NetSuite.
8
+ Build Ruby on Rails applications that sync ActiveRecord (ActiveModel and plain old ruby objects too) in real-time to NetSuite. Here's an example:
9
+
10
+ ```ruby
11
+ class Item < ActiveRecord::Base
12
+ include NetSuiteRails::RecordSync
13
+
14
+ # specify the NS record that your rails model maps to
15
+ netsuite_record_class NetSuite::Records::InventoryItem
16
+
17
+ netsuite_sync :read_write,
18
+ # specify the frequency that your app should poll NetSuite for updates
19
+ frequency: 1.day,
20
+ # it's possible to base syncing off of a saved search. Be sure that "Internal ID" is one of your search result columns
21
+ saved_search_id: 123,
22
+ # limit pushing to NetSuite based on conditional
23
+ if: -> { self.a_condition? },
24
+ # limit pulling from NetSuite based on conditional. This is only
25
+ # considered when handling a single pull
26
+ pull_if: -> { self.another_condition? },
27
+
28
+ # accepted values are :async and :sync. Default is :async
29
+ mode: :sync
30
+
31
+
32
+ # local => remote field mapping
33
+ netsuite_field_map({
34
+ :item_number => :item_id,
35
+ :name => :display_name,
36
+
37
+ # the corresponding NetSuite field must be manually specified in before_netsuite_push
38
+ :user => Proc.new do |local_rails_record, netsuite_record, sync_direction|
39
+ if direction == :pull
40
+
41
+ elsif direction == :push
42
+
43
+ end
44
+ end,
45
+
46
+ :custom_field_list => {
47
+ :a_local_field => :custrecord_remote_field
48
+ :a_special_local_field => Proc.new do |local, ns_record, direction|
49
+ if direction == :push
50
+ # if proc is used with a field mapping, the field must be specified in `netsuite_manual_fields`
51
+ ns_record.custom_field_list.custentity_special_long = 1
52
+ ns_record.custom_field_list.custentity_special_long.type = 'platformCore:LongCustomFieldRef'
53
+ end
54
+ end
55
+ }
56
+ })
57
+
58
+ # sanitizes input from rails to ensure NS doesn't throw a fatal error
59
+ netsuite_field_hints({
60
+ :phone => :phone,
61
+ :email => :email
62
+ })
63
+
64
+ before_netsuite_push do |netsuite_record|
65
+ self.netsuite_manual_fields = [ :entity, :custom_field_list ]
66
+ end
67
+ end
68
+ ```
69
+
70
+ Your ruby model:
71
+
72
+ * Needs to have a `netsuite_id` and `netsuite_id=` method
73
+ * Does not need to be an `ActiveRecord` model. If you don't use ActiveRecord it is your responsibility
74
+ to trigger `Model#netsuite_push`.
75
+
76
+ Notes:
77
+
78
+ * If `sync_mode == :async` `model.save` will be run if a record is created referencing an existing NetSuite object: `model.create! netsuite_id: 123`
79
+ * If you are using `update`, a `update` call will not be run if no changed fields are detected. If you are manually using fields specify them with `netsuite_manual_fields`
80
+
81
+ ## Using Upsert
82
+
83
+ TODO generating external ID tag
84
+
85
+ TODO configuring upsert
86
+
87
+ TODO add vs upsert consideration
8
88
 
9
89
  ## Installation
10
90
 
@@ -12,43 +92,121 @@ Build custom Ruby on Rails applications that sync to NetSuite.
12
92
  gem 'netsuite_rails'
13
93
  ```
14
94
 
15
- Install the database migration for poll timestamps
95
+ Install the database migration to persist poll timestamps:
16
96
 
17
97
  ```bash
18
98
  rails g netsuite_rails:install
19
99
  ```
20
100
 
21
- ### Date & Time
101
+ This helps netsuite_rails to know when the last time your rails DB was synced with the NS.
102
+
103
+ ## Date
104
+
105
+
106
+ ## Time
107
+
108
+ "Time of Day" fields in NetSuite are especially tricky. To ensure that times don't shift when you push them to NetSuite here are some tips:
109
+
110
+ 1. Take a look at the company time zone setup. This is in Setup
111
+ 2. Ensure your WebService's Employee record has either:
112
+ * No time zone set
113
+ * The same time zone as the company
114
+ 3. Ensure that the WebService's GUI preferences have the same time zone settings as the company. This effects how times are translated via SuiteTalk.
115
+ 4. Set the `netsuite_instance_time_zone_offset` setting to your company's time zone
22
116
 
23
117
  ```ruby
24
118
  # set your timezone offset
25
119
  NetSuiteRails::Configuration.netsuite_instance_time_zone_offset(-6)
26
120
  ```
27
121
 
122
+ ### Changing WebService User's TimeZone Preferences
123
+
124
+ It might take a couple hours for time zone changes to take effect. [From my experience](http://mikebian.co/netsuite-suitetalk-user-role-edits-are-delayed/), either the time zone changes have some delay associated with them or the time zone implementation is extremely buggy.
125
+
28
126
  ## Usage
29
127
 
30
- modes: :read, :read_write, :aggressive
128
+ ### Syncing Options
129
+
130
+ ```
131
+ netsuite_record_class NetSuite::Records::Customer
132
+ netsuite_record_class NetSuite::Records::CustomRecord, 123
133
+
134
+ netsuite_sync: :read
135
+ netsuite_sync: :read_write
136
+ # TODO not after_netsuite_push replacement for aggressive sync
137
+
138
+ netsuite_sync: :read, frequency: :never
139
+ netsuite_sync: :read, frequency: 5.minutes
140
+ netsuite_sync: :read, if: -> { self.condition_met? }
141
+
142
+ ```
31
143
 
32
144
  When using a proc in a NS mapping, you are responsible for setting local and remote values
33
145
 
146
+ The default sync frequency is [one day](https://github.com/NetSweet/netsuite_rails/blob/c453326a4190e68a2fd9d7690b2b1f2f105ec8b9/lib/netsuite_rails/poll_trigger.rb#L27).
147
+
34
148
  for pushing tasks to DJ https://github.com/collectiveidea/delayed_job/wiki/Rake-Task-as-a-Delayed-Job
35
149
 
36
150
  `:if` for controlling when syncing occurs
37
151
 
38
- TODO hooks for before/after push/pull
152
+ Easily disable/enable syncing via env vars:
153
+
154
+ ```ruby
155
+ NetSuiteRails.configure do
156
+ netsuite_pull_disabled ENV['NETSUITE_PULL_DISABLED'].present? && ENV['NETSUITE_PULL_DISABLED'] == "true"
157
+ netsuite_push_disabled ENV['NETSUITE_PUSH_DISABLED'].present? && ENV['NETSUITE_PUSH_DISABLED'] == "true"
158
+
159
+ if ENV['NETSUITE_DISABLE_SYNC'].present? && ENV['NETSUITE_DISABLE_SYNC'] == "true"
160
+ netsuite_pull_disabled true
161
+ netsuite_push_disabled true
162
+ end
163
+ end
164
+
165
+ ```
166
+
167
+ ### Hooks
168
+
169
+ ```ruby
170
+ # the netsuite record is passed a single argument to this block (or method reference)
171
+ # this provides the opportunity to set custom fields or run custom logic to prepare
172
+ # the record for the NetSuite envoirnment
173
+ before_netsuite_push
174
+ after_netsuite_push
175
+
176
+ # netsuite_pulling? is true when this callback is executed
177
+ after_netsuite_pull
178
+ ```
39
179
 
40
- ### Syncing
180
+ ### Rake Tasks for Syncing
41
181
 
42
182
  ```bash
183
+ # update & create local records modified in netsuite sync the last sync time
43
184
  rake netsuite:sync
44
185
 
186
+ # pull all records in NetSuite and update/create local records
45
187
  rake netsuite:fresh_sync
188
+
189
+ # only update records that have already been synced
190
+ rake netsuite:sync_local RECORD_MODELS=YourModel LIST_MODELS=YourListModel
46
191
  ```
47
192
 
48
193
  Caveats:
49
194
 
50
195
  * If you have date time fields, or custom fields that will trigger `changed_attributes` this might cause issues when pulling an existing record
51
- * `changed_attributes` doesn't work well with store
196
+ * `changed_attributes` doesn't work well with `store`s
197
+
198
+ ### Delayed Job
199
+
200
+ The more records that use netsuite_rails, the longer you'll need your job timeout to be:
201
+
202
+ ```ruby
203
+ # config/initializers/delayed_job.rb
204
+ Delayed::Worker.max_run_time = 80.minutes
205
+ ```
206
+
207
+ ## Non-AR Backed Model
208
+
209
+ Implement `changed_attributes` in your non-AR backed model
52
210
 
53
211
  ## Testing
54
212
 
@@ -57,6 +215,24 @@ Caveats:
57
215
  require 'netsuite_rails/spec/spec_helper'
58
216
  ```
59
217
 
218
+ # Syncing Using Rake Tasks
219
+
220
+ ```ruby
221
+ # clockwork.rb
222
+ every(1.minutes, 'netsuite sync') {
223
+ # prevent multiple netsuite:sync DJ commands from being added; only one is needed in the queue at a time
224
+ unless Delayed::Job.where(failed_at: nil, locked_by: nil).detect { |j| j.payload_object.class == DelayedRake && j.payload_object.task == 'netsuite:sync'}
225
+ Delayed::Job.enqueue DelayedRake.new("netsuite:sync")
226
+ end
227
+ }
228
+
229
+ # schedule.rb
230
+ # DelayedRake: https://github.com/collectiveidea/delayed_job/wiki/Rake-Task-as-a-Delayed-Job
231
+ every 2.minutes do
232
+ runner 'Delayed::Job.enqueue(DelayedRake.new("netsuite:sync"),priority:1,run_at: Time.now);'
233
+ end
234
+ ```
235
+
60
236
  ## Author
61
237
 
62
- * Michael Bianco @iloveitaly
238
+ * Michael Bianco @iloveitaly
data/circle.yml ADDED
@@ -0,0 +1,3 @@
1
+ database:
2
+ override:
3
+ - echo "no database setup"
@@ -14,7 +14,7 @@ module NetSuiteRails
14
14
 
15
15
  def netsuite_sync_mode(mode = nil)
16
16
  if mode.nil?
17
- attributes[:sync_mode] ||= :none
17
+ attributes[:sync_mode] ||= :async
18
18
  else
19
19
  attributes[:sync_mode] = mode
20
20
  end
@@ -22,17 +22,19 @@ module NetSuiteRails
22
22
 
23
23
  def netsuite_push_disabled(flag = nil)
24
24
  if flag.nil?
25
- attributes[:flag] ||= false
25
+ attributes[:push_disabled] = false if attributes[:push_disabled].nil?
26
+ attributes[:push_disabled]
26
27
  else
27
- attributes[:flag] = flag
28
+ attributes[:push_disabled] = flag
28
29
  end
29
30
  end
30
31
 
31
32
  def netsuite_pull_disabled(flag = nil)
32
33
  if flag.nil?
33
- attributes[:flag] ||= false
34
+ attributes[:pull_disabled] = false if attributes[:pull_disabled].nil?
35
+ attributes[:pull_disabled]
34
36
  else
35
- attributes[:flag] = flag
37
+ attributes[:pull_disabled] = flag
36
38
  end
37
39
  end
38
40
 
@@ -14,6 +14,8 @@ require 'netsuite_rails/record_sync/poll_manager'
14
14
  require 'netsuite_rails/record_sync/pull_manager'
15
15
  require 'netsuite_rails/record_sync/push_manager'
16
16
 
17
+ require 'netsuite_rails/routines/company_contact_match'
18
+
17
19
  require 'netsuite_rails/list_sync'
18
20
  require 'netsuite_rails/list_sync/poll_manager'
19
21
 
@@ -23,14 +25,32 @@ module NetSuiteRails
23
25
  Rails::VERSION::MAJOR >= 4
24
26
  end
25
27
 
28
+ def self.configure_from_env(&block)
29
+ self.configure do
30
+ reset!
31
+
32
+ netsuite_pull_disabled ENV['NETSUITE_PULL_DISABLED'].present? && ENV['NETSUITE_PULL_DISABLED'] == "true"
33
+ netsuite_push_disabled ENV['NETSUITE_PUSH_DISABLED'].present? && ENV['NETSUITE_PUSH_DISABLED'] == "true"
34
+
35
+ if ENV['NETSUITE_DISABLE_SYNC'].present? && ENV['NETSUITE_DISABLE_SYNC'] == "true"
36
+ netsuite_pull_disabled true
37
+ netsuite_push_disabled true
38
+ end
39
+
40
+ polling_page_size if ENV['NETSUITE_POLLING_PAGE_SIZE'].present?
41
+ end
42
+
43
+ self.configure(&block) if block
44
+ end
45
+
46
+ def self.configure(&block)
47
+ NetSuiteRails::Configuration.instance_eval(&block)
48
+ end
49
+
26
50
  class Railtie < ::Rails::Railtie
27
51
  rake_tasks do
28
52
  load 'netsuite_rails/tasks/netsuite.rb'
29
53
  end
30
-
31
- config.before_configuration do
32
- require 'netsuite_rails/netsuite_configure'
33
- end
34
54
  end
35
55
 
36
56
  end
@@ -27,32 +27,33 @@ module NetSuiteRails
27
27
  sync_frequency = klass.netsuite_sync_options[:frequency] || 1.day
28
28
 
29
29
  if sync_frequency == :never
30
- Rails.logger.info "Not syncing #{klass.to_s}"
30
+ Rails.logger.info "NetSuite: Not syncing #{klass.to_s}"
31
31
  next
32
32
  end
33
33
 
34
- Rails.logger.info "NetSuite: Syncing #{klass.to_s}"
35
-
36
- preference = PollTimestamp.for_class(klass)
34
+ last_class_poll = PollTimestamp.for_class(klass)
35
+ poll_execution_time = DateTime.now
37
36
 
38
37
  # check if we've never synced before
39
- if preference.new_record?
38
+ if last_class_poll.new_record?
39
+ Rails.logger.info "NetSuite: Syncing #{klass} for the first time"
40
40
  klass.netsuite_poll({ import_all: true }.merge(opts))
41
41
  else
42
42
  # TODO look into removing the conditional parsing; I don't think this is needed
43
- last_poll_date = preference.value
43
+ last_poll_date = last_class_poll.value
44
44
  last_poll_date = DateTime.parse(last_poll_date) unless last_poll_date.is_a?(DateTime)
45
45
 
46
46
  if DateTime.now.to_i - last_poll_date.to_i > sync_frequency
47
- Rails.logger.info "NetSuite: Syncing #{klass} modified since #{last_poll_date}"
47
+ Rails.logger.info "NetSuite: #{klass} is due to be synced, last checked #{last_poll_date}"
48
48
  klass.netsuite_poll({ last_poll: last_poll_date }.merge(opts))
49
49
  else
50
50
  Rails.logger.info "NetSuite: Skipping #{klass} because of syncing frequency"
51
+ next
51
52
  end
52
53
  end
53
54
 
54
- preference.value = DateTime.now
55
- preference.save!
55
+ last_class_poll.value = poll_execution_time
56
+ last_class_poll.save!
56
57
  end
57
58
  end
58
59
 
@@ -19,7 +19,7 @@ module NetSuiteRails
19
19
  end
20
20
 
21
21
  unless netsuite_batch
22
- binding.pry
22
+ raise "NetSuite: #{klass}. Error running NS search. No Netsuite batch found. Most likely a search timeout."
23
23
  end
24
24
 
25
25
  netsuite_batch.each do |netsuite_record|
@@ -35,12 +35,14 @@ module NetSuiteRails
35
35
  }.merge(opts)
36
36
 
37
37
  opts[:netsuite_record_class] ||= klass.netsuite_record_class
38
+ opts[:netsuite_custom_record_type_id] ||= klass.netsuite_custom_record_type_id if opts[:netsuite_record_class] == NetSuite::Records::CustomRecord
38
39
  opts[:saved_search_id] ||= klass.netsuite_sync_options[:saved_search_id]
40
+ opts[:body_fields_only] ||= false
39
41
 
40
42
  search = opts[:netsuite_record_class].search(
41
43
  poll_criteria(klass, opts).merge({
42
44
  preferences: {
43
- body_fields_only: false,
45
+ body_fields_only: opts[:body_fields_only],
44
46
  page_size: opts[:page_size]
45
47
  }
46
48
  })
@@ -48,7 +50,7 @@ module NetSuiteRails
48
50
 
49
51
  # TODO more robust error reporting
50
52
  unless search
51
- raise 'error running netsuite sync'
53
+ raise "NetSuite: #{klass}. Error running NS search. Most likely a search timeout."
52
54
  end
53
55
 
54
56
  process_search_results(klass, opts, search)
@@ -90,7 +92,7 @@ module NetSuiteRails
90
92
 
91
93
  if opts[:netsuite_record_class] == NetSuite::Records::CustomRecord
92
94
  opts[:netsuite_custom_record_type_id] ||= klass.netsuite_custom_record_type_id
93
-
95
+
94
96
  criteria << {
95
97
  field: 'recType',
96
98
  operator: 'is',
@@ -116,14 +118,14 @@ module NetSuiteRails
116
118
  full_record_data: -1,
117
119
  }.merge(opts)
118
120
 
119
- Rails.logger.info "NetSuite: Processing #{search.total_records} over #{search.total_pages} pages"
120
-
121
121
  # TODO need to improve the conditional here to match the get_list call conditional belo
122
122
  if opts[:import_all] && opts[:skip_existing]
123
123
  synced_netsuite_list = klass.pluck(:netsuite_id)
124
124
  end
125
-
125
+
126
126
  search.results_in_batches do |batch|
127
+ Rails.logger.info "NetSuite: Syncing #{klass}. Current Page: #{search.current_page}. Processing #{search.total_records} over #{search.total_pages} pages."
128
+
127
129
  # a saved search is processed as a advanced search; advanced search often does not allow you to retrieve
128
130
  # all of the fields (ex: addressbooklist on customer) that a normal search does
129
131
  # the only way to get those fields is to pull down the full record again using getAll
@@ -136,7 +138,16 @@ module NetSuiteRails
136
138
  end
137
139
 
138
140
  if filtered_netsuite_id_list.present?
139
- opts[:netsuite_record_class].get_list(list: filtered_netsuite_id_list)
141
+ Rails.logger.info "NetSuite: Syncing #{klass}. Running get_list for #{filtered_netsuite_id_list.length} records"
142
+
143
+ if opts[:netsuite_record_class] == NetSuite::Records::CustomRecord
144
+ NetSuite::Records::CustomRecord.get_list(
145
+ list: filtered_netsuite_id_list,
146
+ type_id: opts[:netsuite_custom_record_type_id]
147
+ )
148
+ else
149
+ opts[:netsuite_record_class].get_list(list: filtered_netsuite_id_list)
150
+ end
140
151
  else
141
152
  []
142
153
  end
@@ -164,7 +175,8 @@ module NetSuiteRails
164
175
  end
165
176
 
166
177
  def needs_get_list?(opts)
167
- (opts[:saved_search_id].present? && opts[:full_record_data] != false) || opts[:full_record_data] == true
178
+ (opts[:saved_search_id].present? && opts[:full_record_data] != false) ||
179
+ opts[:full_record_data] == true
168
180
  end
169
181
 
170
182
  # TODO this should remain in the pull manager
@@ -180,7 +192,7 @@ module NetSuiteRails
180
192
 
181
193
  custom_field_value
182
194
  end
183
-
195
+
184
196
  end
185
197
  end
186
- end
198
+ end
@@ -3,6 +3,8 @@ module NetSuiteRails
3
3
  module PullManager
4
4
  extend self
5
5
 
6
+ # TODO pull relevant methods out of poll manager and into this class
7
+
6
8
  def extract_custom_field_value(custom_field_value)
7
9
  if custom_field_value.present? && custom_field_value.is_a?(Hash) && custom_field_value.has_key?(:name)
8
10
  custom_field_value = custom_field_value[:name]