netsuite_rails 0.2.2 → 0.3.1

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