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
@@ -8,6 +8,10 @@ module NetSuiteRails
8
8
  # if a model has a pending/complete state we might want to only push on complete
9
9
 
10
10
  def attach(klass)
11
+ # don't attach to non-AR backed models
12
+ # it is the user's responsibility to trigger `Model#netsuite_push` when ActiveRecord isn't used
13
+ return unless klass.ancestors.include?(ActiveRecord::Base)
14
+
11
15
  if klass.include?(SubListSync)
12
16
  klass.after_save { SyncTrigger.sublist_trigger(self) }
13
17
  klass.after_destroy { SyncTrigger.sublist_trigger(self) }
@@ -17,20 +21,26 @@ module NetSuiteRails
17
21
  klass.before_save do
18
22
  @netsuite_sync_record_import = self.new_record? && self.netsuite_id.present?
19
23
 
24
+ if @netsuite_sync_record_import
25
+ # pull the record down if it has't been pulled yet
26
+ # this is useful when this is triggered by a save on a parent record which has this
27
+ # record as a related record
28
+
29
+ if !self.netsuite_pulled? && !self.netsuite_async_jobs?
30
+ SyncTrigger.record_pull_trigger(self)
31
+ end
32
+ end
33
+
20
34
  # if false record will not save
21
35
  true
22
36
  end
23
37
 
24
38
  klass.after_save do
25
- # need to implement this conditional on the save hook level
39
+ # this conditional is implemented as a save hook
26
40
  # because the coordination class doesn't know about model persistence state
27
41
 
28
42
  if @netsuite_sync_record_import
29
- # pull the record down if it has't been pulled yet
30
- # this is useful when this is triggered by a save on a parent record which has this
31
- # record as a related record
32
-
33
- unless self.netsuite_pulled?
43
+ if !self.netsuite_pulled? && self.netsuite_async_jobs?
34
44
  SyncTrigger.record_pull_trigger(self)
35
45
  end
36
46
  else
@@ -47,24 +57,32 @@ module NetSuiteRails
47
57
  def record_pull_trigger(local)
48
58
  return if NetSuiteRails::Configuration.netsuite_pull_disabled
49
59
 
60
+ sync_options = local.netsuite_sync_options
61
+
62
+ return if sync_options.has_key?(:pull_if) && !local.instance_exec(&sync_options[:pull_if])
63
+
50
64
  record_trigger_action(local, :netsuite_pull)
51
65
  end
52
66
 
53
- def record_push_trigger(netsuite_record_rep)
67
+ def record_push_trigger(local)
54
68
  # don't update when fields are updated because of a netsuite_pull
55
- return if netsuite_record_rep.netsuite_pulling?
69
+ if local.netsuite_pulling?
70
+ Rails.logger.info "NetSuite: Push Stopped. Record is pulling. " +
71
+ "local_record=#{local.class}, local_record_id=#{local.id}"
72
+ return
73
+ end
56
74
 
57
75
  return if NetSuiteRails::Configuration.netsuite_push_disabled
58
76
 
59
77
  # don't update if a read only record
60
- return if netsuite_record_rep.netsuite_sync == :read
78
+ return if local.netsuite_sync == :read
61
79
 
62
- sync_options = netsuite_record_rep.netsuite_sync_options
80
+ sync_options = local.netsuite_sync_options
63
81
 
64
82
  # :if option is a block that returns a boolean
65
- return if sync_options.has_key?(:if) && !netsuite_record_rep.instance_exec(&sync_options[:if])
83
+ return if sync_options.has_key?(:if) && !local.instance_exec(&sync_options[:if])
66
84
 
67
- record_trigger_action(netsuite_record_rep, :netsuite_push)
85
+ record_trigger_action(local, :netsuite_push)
68
86
  end
69
87
 
70
88
  def record_trigger_action(local, action)
@@ -79,15 +97,15 @@ module NetSuiteRails
79
97
  end
80
98
 
81
99
  # TODO need to pass off the credentials to the NS push command
82
-
100
+
83
101
  # You can force sync mode in different envoirnments with the global configuration variables
84
102
 
85
- if sync_options[:mode] == :sync || NetSuiteRails::Configuration.netsuite_sync_mode == :sync
103
+ if !local.netsuite_async_jobs?
86
104
  local.send(action, action_options)
87
105
  else
88
106
  action_options[:modified_fields] = NetSuiteRails::RecordSync::PushManager.modified_local_fields(local).keys
89
107
 
90
- # TODO support the rails4 DJ implementation
108
+ # TODO support ActiveJob
91
109
 
92
110
  if local.respond_to?(:delay)
93
111
  local.delay.send(action, action_options)
@@ -98,12 +116,17 @@ module NetSuiteRails
98
116
  end
99
117
 
100
118
  def sublist_trigger(sublist_item_rep)
119
+ # TODO don't trigger a push if the parent record is still pulling
120
+ # often sublists are managed in a after_pull hook; we want to prevent auto-pushing
121
+ # if sublist records are being updated. However, the netsuite_pulling? state is not persisted
122
+ # so there is no gaurentee that it isn't being pulled by checking parent.netsuite_pulling?
123
+
101
124
  parent = sublist_item_rep.send(sublist_item_rep.class.netsuite_sublist_parent)
102
-
125
+
103
126
  if parent.class.include?(RecordSync)
104
127
  record_push_trigger(parent)
105
128
  end
106
129
  end
107
130
 
108
131
  end
109
- end
132
+ end
@@ -5,11 +5,11 @@ namespace :netsuite do
5
5
  skip_existing: ENV['SKIP_EXISTING'].present? && ENV['SKIP_EXISTING'] == "true"
6
6
  }
7
7
 
8
- if ENV['RECORD_MODELS'].present?
8
+ if !ENV['RECORD_MODELS'].nil?
9
9
  opts[:record_models] = ENV['RECORD_MODELS'].split(',').map(&:constantize)
10
10
  end
11
11
 
12
- if ENV['LIST_MODELS'].present?
12
+ if !ENV['LIST_MODELS'].nil?
13
13
  opts[:list_models] = ENV['LIST_MODELS'].split(',').map(&:constantize)
14
14
  end
15
15
 
@@ -25,12 +25,15 @@ namespace :netsuite do
25
25
 
26
26
  desc "Sync all NetSuite records using import_all"
27
27
  task :fresh_sync => :environment do
28
- NetSuiteRails::PollTimestamp.delete_all
29
-
30
28
  if ENV['SKIP_EXISTING'].blank?
31
29
  ENV['SKIP_EXISTING'] = "true"
32
30
  end
33
31
 
32
+ opts = generate_options
33
+ opts[:record_models].each do |record_model|
34
+ NetSuiteRails::PollTimestamp.for_class(record_model).delete
35
+ end
36
+
34
37
  Rake::Task["netsuite:sync"].invoke
35
38
  end
36
39
 
@@ -47,4 +50,30 @@ namespace :netsuite do
47
50
  NetSuiteRails::PollTrigger.update_local_records(generate_options)
48
51
  end
49
52
 
53
+ task field_usage_report: :environment do |t|
54
+ Rails.application.eager_load!
55
+
56
+ NetSuiteRails::PollTrigger.instance_variable_get('@record_models').each do |record_model|
57
+ puts record_model.to_s
58
+
59
+ # TODO add the ability to document which fields
60
+
61
+ standard_fields = record_model.netsuite_field_map.values - [record_model.netsuite_field_map[:custom_field_list]]
62
+ custom_fields = record_model.netsuite_field_map[:custom_field_list].values
63
+
64
+ standard_fields.reject! { |f| f.is_a?(Proc) }
65
+ custom_fields.reject! { |f| f.is_a?(Proc) }
66
+
67
+ if custom_fields.present?
68
+ puts "Custom Fields: #{custom_fields.join(', ')}"
69
+ end
70
+
71
+ if standard_fields.present?
72
+ puts "Standard Fields: #{standard_fields.join(', ')}"
73
+ end
74
+
75
+ puts ""
76
+ end
77
+ end
78
+
50
79
  end
@@ -2,43 +2,83 @@ module NetSuiteRails
2
2
  module Transformations
3
3
  class << self
4
4
 
5
- def transform(type, value)
6
- self.send(type, value)
5
+ def transform(type, value, direction)
6
+ self.send(type, value, direction)
7
7
  end
8
8
 
9
9
  # NS limits firstname fields to 33 characters
10
- def firstname(firstname)
11
- firstname[0..33]
10
+ def firstname(firstname, direction = :push)
11
+ if direction == :push
12
+ firstname[0..33]
13
+ else
14
+ firstname
15
+ end
12
16
  end
13
17
 
14
- def phone(phone)
15
- formatted_phone = phone.strip
16
- .gsub(/ext(ension)?/, 'x')
17
- .gsub(/[^0-9x ]/, '')
18
- .gsub(/[ ]{2,}/m, ' ')
19
-
20
- formatted_phone.gsub!(/x.*$/, '') if formatted_phone.size > 22
18
+ def phone(phone, direction = :push)
19
+ if direction == :push
20
+ formatted_phone = phone.
21
+ strip.
22
+ gsub(/ext(ension)?/, 'x').
23
+ # remove anything that isn't a extension indicator or a number
24
+ gsub(/[^0-9x]/, '').
25
+ # if the first part of the phone # is 10 characters long and starts with a 1 the 22 char error is thrown
26
+ gsub(/^1([0-9]{10})/, '\1')
21
27
 
22
- formatted_phone
28
+ # eliminate the extension if the number is still too long
29
+ formatted_phone.gsub!(/x.*$/, '') if formatted_phone.size > 22
30
+
31
+ formatted_phone
32
+ else
33
+ phone
34
+ end
23
35
  end
24
36
 
25
37
  # NS will throw an error if whitespace bumpers the email string
26
- def email(email)
27
- email.strip
38
+ def email(email, direction = :push)
39
+ if direction == :push
40
+ # any whitespace will cause netsuite to throw a fatal error
41
+ email = email.gsub(' ', '')
42
+
43
+ # TODO consider throwing an exception instead of returning nil?
44
+ # netsuite will throw a fatal error if a valid email address is not sent
45
+ # http://stackoverflow.com/questions/742451/what-is-the-simplest-regular-expression-to-validate-emails-to-not-accept-them-bl
46
+ if email !~ /.+@.+\..+/
47
+ return nil
48
+ end
49
+
50
+ # an error will be thrown if period is on the end of a sentence
51
+ email = email.gsub(/[^A-Za-z]+$/, '')
52
+
53
+ email
54
+ else
55
+ email
56
+ end
28
57
  end
29
58
 
30
59
  # https://www.reinteractive.net/posts/168-dealing-with-timezones-effectively-in-rails
31
60
  # http://stackoverflow.com/questions/16818180/ruby-rails-how-do-i-change-the-timezone-of-a-time-without-changing-the-time
32
61
  # http://alwayscoding.ca/momentos/2013/08/22/handling-dates-and-timezones-in-ruby-and-rails/
33
62
 
34
- def date(date)
35
- date.change(offset: "-07:00", hour: 24 - (8 + NetSuiteRails::Configuration.netsuite_instance_time_zone_offset))
63
+ def date(date, direction = :push)
64
+ if direction == :push
65
+ # setting the hour to noon eliminates the chance that some strange timezone offset
66
+ # shifting would cause the date to drift into the next or previous day
67
+ date.to_datetime.change(offset: "-07:00", hour: 12)
68
+ else
69
+ date.change(offset: Time.zone.formatted_offset)
70
+ end
36
71
  end
37
72
 
38
- def datetime(datetime)
39
- datetime.change(offset: "-08:00") - (8 + NetSuiteRails::Configuration.netsuite_instance_time_zone_offset).hours
73
+ def datetime(datetime, direction = :push)
74
+ if direction == :push
75
+ datetime.change(offset: "-08:00", year: 1970, day: 01, month: 01) - (8 + NetSuiteRails::Configuration.netsuite_instance_time_zone_offset).hours
76
+ else
77
+ datetime = datetime.change(offset: Time.zone.formatted_offset) + (8 + NetSuiteRails::Configuration.netsuite_instance_time_zone_offset).hours
78
+ datetime
79
+ end
40
80
  end
41
81
 
42
82
  end
43
83
  end
44
- end
84
+ end
@@ -4,7 +4,8 @@ module NetSuiteRails
4
4
  # TODO create a xxx_netsuite_url helper generator
5
5
 
6
6
  def self.netsuite_url(record = self)
7
- prefix = "https://system#{".sandbox" if NetSuite::Configuration.sandbox}.netsuite.com/app"
7
+ domain = NetSuite::Configuration.wsdl_domain.sub('webservices.', 'system.')
8
+ prefix = "https://#{domain}/app"
8
9
 
9
10
  if record.class.to_s.start_with?('NetSuite::Records')
10
11
  record_class = record.class
@@ -21,19 +22,51 @@ module NetSuiteRails
21
22
  # https://system.sandbox.netsuite.com/app/common/scripting/scriptrecordlist.nl
22
23
  # https://system.sandbox.netsuite.com/app/common/scripting/script.nl
23
24
 
24
- if is_custom_record
25
- "#{prefix}/common/custom/custrecordentry.nl?id=#{internal_id}&rectype=#{record.class.netsuite_custom_record_type_id}"
26
- elsif [ NetSuite::Records::InventoryItem, NetSuite::Records::NonInventorySaleItem, NetSuite::Records::AssemblyItem].include?(record_class)
27
- "#{prefix}/common/item/item.nl?id=#{internal_id}"
25
+ # dependent record links
26
+ # https://system.na1.netsuite.com/core/pages/itemchildrecords.nl?id=12413&t=InvtItem%05ProjectCostCategory&rectype=-10
27
+ # https://system.na1.netsuite.com/app/accounting/transactions/payments.nl?id=91964&label=Customer+Refund&type=custrfnd&alllinks=T
28
+
29
+ suffix = if is_custom_record
30
+ "/common/custom/custrecordentry.nl?id=#{internal_id}&rectype=#{record.class.netsuite_custom_record_type_id}"
31
+ elsif [
32
+ NetSuite::Records::InventoryItem,
33
+ NetSuite::Records::NonInventorySaleItem,
34
+ NetSuite::Records::AssemblyItem,
35
+ NetSuite::Records::ServiceSaleItem,
36
+ NetSuite::Records::DiscountItem,
37
+ ].include?(record_class)
38
+ "/common/item/item.nl?id=#{internal_id}"
28
39
  elsif record_class == NetSuite::Records::Task
29
- "#{prefix}/crm/calendar/task.nl?id=#{internal_id}"
30
- elsif record_class == NetSuite::Records::Customer
31
- "#{prefix}/common/entity/custjob.nl?id=#{internal_id}"
32
- elsif record_class == NetSuite::Records::Contact
33
- "#{prefix}/common/entity/contact.nl?id=#{internal_id}"
34
- elsif [ NetSuite::Records::SalesOrder, NetSuite::Records::Invoice, NetSuite::Records::CustomerRefund ].include?(record_class)
35
- "#{prefix}/accounting/transactions/transaction.nl?id=#{internal_id}"
40
+ "/crm/calendar/task.nl?id=#{internal_id}"
41
+ elsif record_class == NetSuite::Records::Roles
42
+ "/setup/role.nl?id=#{internal_id}"
43
+ elsif [
44
+ NetSuite::Records::Contact,
45
+ NetSuite::Records::Customer,
46
+ NetSuite::Records::Vendor,
47
+ NetSuite::Records::Partner,
48
+ NetSuite::Records::Employee
49
+ ].include?(record_class)
50
+ "/common/entity/entity.nl?id=#{internal_id}"
51
+ elsif [
52
+ NetSuite::Records::SalesOrder,
53
+ NetSuite::Records::Invoice,
54
+ NetSuite::Records::CustomerRefund,
55
+ NetSuite::Records::CashSale,
56
+ NetSuite::Records::ItemFulfillment,
57
+ NetSuite::Records::CustomerDeposit,
58
+ NetSuite::Records::CustomerPayment,
59
+ NetSuite::Records::CreditMemo,
60
+ NetSuite::Records::Deposit
61
+ ].include?(record_class)
62
+ "/accounting/transactions/transaction.nl?id=#{internal_id}"
63
+ elsif NetSuite::Records::Account == record_class
64
+ "/accounting/account/account.nl?id=#{internal_id}"
65
+ else
66
+ # TODO unsupported record type error?
36
67
  end
68
+
69
+ prefix + suffix
37
70
  end
38
71
 
39
72
  end
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |s|
6
6
  s.name = "netsuite_rails"
7
- s.version = "0.2.2"
7
+ s.version = "0.3.1"
8
8
  s.authors = ["Michael Bianco"]
9
9
  s.email = ["mike@cliffsidemedia.com"]
10
10
  s.summary = %q{Write Rails applications that integrate with NetSuite}
@@ -16,7 +16,7 @@ Gem::Specification.new do |s|
16
16
  s.test_files = s.files.grep(%r{^(test|spec|features)/})
17
17
  s.require_paths = ["lib"]
18
18
 
19
- s.add_dependency 'netsuite', '~> 0.3.2'
19
+ s.add_dependency 'netsuite', '> 0.5.2'
20
20
  s.add_dependency 'rails', '>= 3.2.16'
21
21
 
22
22
  s.add_development_dependency "bundler", "~> 1.6"
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe NetSuiteRails::Configuration do
4
+ it 'should disable netsuite push and pull' do
5
+ NetSuiteRails::Configuration.netsuite_push_disabled true
6
+ NetSuiteRails::Configuration.netsuite_pull_disabled false
7
+
8
+ expect(NetSuiteRails::Configuration.netsuite_pull_disabled).to eq(false)
9
+ expect(NetSuiteRails::Configuration.netsuite_push_disabled).to eq(true)
10
+ end
11
+ end
@@ -30,9 +30,10 @@ describe NetSuiteRails::RecordSync::PollManager do
30
30
  end
31
31
 
32
32
  it "should sync only available local records" do
33
- NetSuiteRails::Configuration.netsuite_push_disabled true
33
+ # disabling NS pull since this is treated as a record import (NS ID = exists, new_record? == true)
34
+ NetSuiteRails::Configuration.netsuite_pull_disabled true
34
35
  StandardRecord.create! netsuite_id: 123
35
- NetSuiteRails::Configuration.netsuite_push_disabled false
36
+ NetSuiteRails::Configuration.netsuite_pull_disabled false
36
37
 
37
38
  allow(NetSuite::Records::Customer).to receive(:get_list).and_return([OpenStruct.new(internal_id: 123)])
38
39
  allow(NetSuiteRails::RecordSync::PollManager).to receive(:process_search_result_item)
@@ -42,4 +43,12 @@ describe NetSuiteRails::RecordSync::PollManager do
42
43
  expect(NetSuite::Records::Customer).to have_received(:get_list)
43
44
  end
44
45
 
46
+ it 'runs netsuite_pull on a newly created record with a netsuite_id defined' do
47
+ record = StandardRecord.new netsuite_id: 123
48
+ allow(record).to receive(:netsuite_pull)
49
+
50
+ record.save!
51
+
52
+ expect(record).to have_received(:netsuite_pull)
53
+ end
45
54
  end
@@ -4,23 +4,43 @@ describe NetSuiteRails::PollTrigger do
4
4
  include ExampleModels
5
5
 
6
6
  it "should properly sync for the first time" do
7
- allow(StandardRecord).to receive(:netsuite_poll).with(hash_including(:import_all => true))
7
+ allow(NetSuiteRails::RecordSync::PollManager).to receive(:poll)
8
8
 
9
- NetSuiteRails::PollTrigger.sync list_models: []
9
+ NetSuiteRails::PollTrigger.sync list_models: [], record_models: [ StandardRecord ]
10
10
 
11
- expect(StandardRecord).to have_received(:netsuite_poll)
11
+ expect(NetSuiteRails::RecordSync::PollManager).to have_received(:poll)
12
12
  end
13
13
 
14
- it "should trigger syncing when the time has passed is greater than frequency" do
15
- allow(StandardRecord).to receive(:netsuite_poll)
14
+ it "should trigger syncing is triggered from the model when time passed is greater than frequency" do
15
+ allow(StandardRecord).to receive(:netsuite_poll)
16
+ allow(StandardRecord.netsuite_record_class).to receive(:search).and_return(OpenStruct.new(results: []))
16
17
 
17
- StandardRecord.netsuite_sync_options[:frequency] = 5.minutes
18
- timestamp = NetSuiteRails::PollTimestamp.for_class(StandardRecord)
19
- timestamp.value = DateTime.now - 7.minutes
20
- timestamp.save!
18
+ StandardRecord.netsuite_sync_options[:frequency] = 5.minutes
21
19
 
22
- NetSuiteRails::PollTrigger.sync list_models: []
20
+ timestamp = NetSuiteRails::PollTimestamp.for_class(StandardRecord)
21
+ timestamp.value = DateTime.now - 6.minutes
22
+ timestamp.save!
23
23
 
24
- expect(StandardRecord).to have_received(:netsuite_poll)
24
+ NetSuiteRails::PollTrigger.sync list_models: [], record_models: [ StandardRecord ]
25
+
26
+ expect(StandardRecord).to have_received(:netsuite_poll)
27
+ end
28
+
29
+ it 'should not change the poll timestamp when sync does not occur' do
30
+ allow(StandardRecord).to receive(:netsuite_poll)
31
+
32
+ StandardRecord.netsuite_sync_options[:frequency] = 5.minutes
33
+
34
+ last_timestamp = DateTime.now - 3.minutes
35
+
36
+ timestamp = NetSuiteRails::PollTimestamp.for_class(StandardRecord)
37
+ timestamp.value = last_timestamp
38
+ timestamp.save!
39
+
40
+ NetSuiteRails::PollTrigger.sync list_models: [], record_models: [ StandardRecord ]
41
+
42
+ expect(StandardRecord).to_not have_received(:netsuite_poll)
43
+ timestamp = NetSuiteRails::PollTimestamp.find(timestamp.id)
44
+ expect(timestamp.value).to eq(last_timestamp)
25
45
  end
26
46
  end
@@ -0,0 +1,51 @@
1
+ describe NetSuiteRails::RecordSync::PushManager do
2
+ include ExampleModels
3
+
4
+ it 'should handle a modified field with a Proc instead of a netsuite field key' do
5
+ record = StandardRecord.new netsuite_id: 234
6
+ allow(record).to receive(:new_netsuite_record?).and_return(false)
7
+
8
+ ns_record = record.netsuite_record_class.new
9
+
10
+ NetSuiteRails::RecordSync::PushManager.push(record, { modified_fields: [ :company ] })
11
+ end
12
+
13
+ it 'should ignore modified fields if the record has not yet been pushed to NetSuite' do
14
+ record = StandardRecord.new
15
+
16
+ expect(NetSuiteRails::RecordSync::PushManager).to receive(:push_add).once
17
+ expect(NetSuiteRails::RecordSync::PushManager).to receive(:build_netsuite_record).with(instance_of(StandardRecord), hash_including({
18
+ :modified_fields => hash_including(:phone, :company)
19
+ }))
20
+
21
+ NetSuiteRails::RecordSync::PushManager.push(record, { modified_fields: [ :company ] })
22
+ end
23
+
24
+ context "AR" do
25
+ xit "should look at the NS ID of a has_one relationship on the record sync model"
26
+
27
+ xit "should properly determine the changed attributes"
28
+ end
29
+
30
+ context "not AR" do
31
+ xit "should execute properly for a simple active model class"
32
+
33
+ end
34
+
35
+ context 'record building' do
36
+ it "should properly handle custom records" do
37
+ custom = CustomRecord.new netsuite_id: 234
38
+ record = NetSuiteRails::RecordSync::PushManager.build_netsuite_record_reference(custom)
39
+
40
+ expect(record.internal_id).to eq(234)
41
+ expect(record.rec_type.internal_id).to eq(123)
42
+ end
43
+
44
+ it "should properly handle records using external ID" do
45
+ local = ExternalIdRecord.new(netsuite_id: 123, phone: "234")
46
+ record = NetSuiteRails::RecordSync::PushManager.build_netsuite_record_reference(local, { use_external_id: true })
47
+
48
+ expect(record.external_id).to eq(local.netsuite_external_id)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe NetSuiteRails::RecordSync do
4
+ include ExampleModels
5
+
6
+ context 'custom records' do
7
+ it "should properly pull the NS rep" do
8
+ allow(NetSuite::Records::CustomRecord).to receive(:get).with(hash_including(:internal_id => 234, type_id: 123))
9
+
10
+ custom_record = CustomRecord.new netsuite_id: 234
11
+ custom_record.netsuite_pull_record
12
+
13
+ expect(NetSuite::Records::CustomRecord).to have_received(:get)
14
+ end
15
+ end
16
+ end
@@ -2,8 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  require 'netsuite_rails/spec/spec_helper'
4
4
 
5
- describe NetSuiteRails::TestHelpers do
6
- include NetSuiteRails::TestHelpers
5
+ describe 'netsuite_rails test helpers' do
7
6
  include ExampleModels
8
7
 
9
8
  let(:fake_search_results) { OpenStruct.new(results: [ OpenStruct.new(internal_id: 0) ]) }
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ # TODO https://github.com/NetSweet/netsuite_rails/issues/16
4
+ # there are still some unresolved issues with NS datetime/date conversions
5
+ # the tests + implementation may still not be correct.
6
+
7
+ describe NetSuiteRails::Transformations do
8
+ before do
9
+ ENV['TZ'] = 'EST'
10
+ Rails.configuration.time_zone = 'Eastern Time (US & Canada)'
11
+ Time.zone = ActiveSupport::TimeZone[-5]
12
+
13
+ NetSuiteRails::Configuration.netsuite_instance_time_zone_offset -6
14
+ end
15
+
16
+ it 'handles very long phone numbers' do
17
+ long_phone_number = '+1 (549)-880-4834 ext. 51077'
18
+
19
+ expect(NetSuiteRails::Transformations.phone(long_phone_number)).to eq('5498804834x51077')
20
+
21
+ weird_long_phone_number = '12933901964x89914'
22
+ expect(NetSuiteRails::Transformations.phone(weird_long_phone_number)).to eq('2933901964x89914')
23
+ end
24
+
25
+ it "translates local date into NS date" do
26
+ # from what I can tell, NetSuite stores dates with a -07:00 offset
27
+ # and subtracts (PST - NS instance timezone) hours from the stored datetime
28
+
29
+ local_date = DateTime.parse("2015-07-24T00:00:00.000-05:00")
30
+ transformed_date = NetSuiteRails::Transformations.date(local_date, :push)
31
+ expect(transformed_date.to_s).to eq("2015-07-24T12:00:00-07:00")
32
+ end
33
+
34
+ it "translates local datetime into NS datetime" do
35
+ # TODO set local timezone
36
+ local_date = DateTime.parse('Fri May 29 11:52:47 EDT 2015')
37
+ NetSuiteRails::Configuration.netsuite_instance_time_zone_offset -6
38
+
39
+ transformed_date = NetSuiteRails::Transformations.datetime(local_date, :push)
40
+ # TODO this will break as PDT daylight savings is switched; need to freeze the system time for testing
41
+ expect(transformed_date.to_s).to eq('1970-01-01T09:52:47-08:00')
42
+ end
43
+
44
+ it 'transforms a datetime value pulled from netsuite correctly' do
45
+ # assuming that the date in CST is displayed as 5am
46
+ # in the rails backend we want to store the date as EST with a CST hour
47
+
48
+ netsuite_time = DateTime.parse('1970-01-01T03:00:00.000-08:00')
49
+ transformed_netsuite_time = NetSuiteRails::Transformations.datetime(netsuite_time, :pull)
50
+ expect(transformed_netsuite_time.to_s).to eq('1970-01-01T05:00:00-05:00')
51
+ end
52
+
53
+ it 'transforms a invalid email' do
54
+ netsuite_email = ' hey@example.com. '
55
+ transformed_netsuite_email = NetSuiteRails::Transformations.email(netsuite_email, :push)
56
+ expect(transformed_netsuite_email.to_s).to eq('hey@example.com')
57
+
58
+ netsuite_email = ' example+second@example.family. '
59
+ transformed_netsuite_email = NetSuiteRails::Transformations.email(netsuite_email, :push)
60
+ expect(transformed_netsuite_email.to_s).to eq('example+second@example.family')
61
+ end
62
+ end
@@ -1,23 +1,34 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe NetSuiteRails::UrlHelper do
4
- include ExampleModels
4
+ include ExampleModels
5
5
 
6
6
  it 'should handle a netsuite record' do
7
- c = NetSuite::Records::Customer.new internal_id: 123
8
- url = NetSuiteRails::UrlHelper.netsuite_url(c)
7
+ NetSuite::Configuration.sandbox = true
8
+ c = NetSuite::Records::Customer.new internal_id: 123
9
+ url = NetSuiteRails::UrlHelper.netsuite_url(c)
9
10
 
10
- expect(url).to include("cust")
11
+ expect(url).to eq('https://system.sandbox.netsuite.com/app/common/entity/entity.nl?id=123')
11
12
  end
12
13
 
13
14
  it "should handle a record sync enabled record" do
14
- s = StandardRecord.new netsuite_id: 123
15
- url = NetSuiteRails::UrlHelper.netsuite_url(s)
15
+ NetSuite::Configuration.sandbox = true
16
+ s = StandardRecord.new netsuite_id: 123
17
+ url = NetSuiteRails::UrlHelper.netsuite_url(s)
16
18
 
17
- expect(url).to include("cust")
19
+ expect(url).to eq('https://system.sandbox.netsuite.com/app/common/entity/entity.nl?id=123')
18
20
  end
19
21
 
20
- it "should handle a list sync enabled record" do
21
-
22
+ xit "should handle a list sync enabled record" do
23
+
24
+ end
25
+
26
+ it 'should change the prefix URL when a non-sandbox datacenter is in use' do
27
+ NetSuite::Configuration.sandbox = false
28
+
29
+ s = StandardRecord.new netsuite_id: 123
30
+ url = NetSuiteRails::UrlHelper.netsuite_url(s)
31
+
32
+ expect(url).to eq('https://system.netsuite.com/app/common/entity/entity.nl?id=123')
22
33
  end
23
34
  end