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.
- checksums.yaml +15 -0
- data/Gemfile +4 -2
- data/README.md +186 -10
- data/circle.yml +3 -0
- data/lib/netsuite_rails/configuration.rb +7 -5
- data/lib/netsuite_rails/netsuite_rails.rb +24 -4
- data/lib/netsuite_rails/poll_trigger.rb +10 -9
- data/lib/netsuite_rails/record_sync/poll_manager.rb +23 -11
- data/lib/netsuite_rails/record_sync/pull_manager.rb +2 -0
- data/lib/netsuite_rails/record_sync/push_manager.rb +76 -38
- data/lib/netsuite_rails/record_sync.rb +28 -11
- data/lib/netsuite_rails/routines/company_contact_match.rb +98 -0
- data/lib/netsuite_rails/spec/disabler.rb +27 -0
- data/lib/netsuite_rails/spec/query_helpers.rb +93 -0
- data/lib/netsuite_rails/spec/spec_helper.rb +2 -79
- data/lib/netsuite_rails/sync_trigger.rb +40 -17
- data/lib/netsuite_rails/tasks/netsuite.rb +33 -4
- data/lib/netsuite_rails/transformations.rb +59 -19
- data/lib/netsuite_rails/url_helper.rb +45 -12
- data/netsuite_rails.gemspec +2 -2
- data/spec/models/configuration_spec.rb +11 -0
- data/spec/models/poll_manager_spec.rb +11 -2
- data/spec/models/poll_trigger_spec.rb +31 -11
- data/spec/models/record_sync/push_manager_spec.rb +51 -0
- data/spec/models/record_sync_spec.rb +16 -0
- data/spec/models/spec_helper_spec.rb +1 -2
- data/spec/models/transformations_spec.rb +62 -0
- data/spec/models/url_helper_spec.rb +20 -9
- data/spec/spec_helper.rb +19 -0
- data/spec/support/example_models.rb +33 -1
- metadata +19 -25
- data/.travis.yml +0 -3
- data/lib/netsuite_rails/netsuite_configure.rb +0 -14
- 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
|
-
#
|
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
|
-
|
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(
|
67
|
+
def record_push_trigger(local)
|
54
68
|
# don't update when fields are updated because of a netsuite_pull
|
55
|
-
|
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
|
78
|
+
return if local.netsuite_sync == :read
|
61
79
|
|
62
|
-
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) && !
|
83
|
+
return if sync_options.has_key?(:if) && !local.instance_exec(&sync_options[:if])
|
66
84
|
|
67
|
-
record_trigger_action(
|
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
|
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
|
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'].
|
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'].
|
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
|
-
|
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
|
-
|
16
|
-
.
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
"
|
30
|
-
elsif record_class == NetSuite::Records::
|
31
|
-
"
|
32
|
-
elsif
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
data/netsuite_rails.gemspec
CHANGED
@@ -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.
|
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', '
|
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
|
-
|
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.
|
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
|
-
|
7
|
+
allow(NetSuiteRails::RecordSync::PollManager).to receive(:poll)
|
8
8
|
|
9
|
-
|
9
|
+
NetSuiteRails::PollTrigger.sync list_models: [], record_models: [ StandardRecord ]
|
10
10
|
|
11
|
-
|
11
|
+
expect(NetSuiteRails::RecordSync::PollManager).to have_received(:poll)
|
12
12
|
end
|
13
13
|
|
14
|
-
it "should trigger syncing
|
15
|
-
|
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
|
-
|
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
|
-
|
20
|
+
timestamp = NetSuiteRails::PollTimestamp.for_class(StandardRecord)
|
21
|
+
timestamp.value = DateTime.now - 6.minutes
|
22
|
+
timestamp.save!
|
23
23
|
|
24
|
-
|
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
|
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
|
-
|
4
|
+
include ExampleModels
|
5
5
|
|
6
6
|
it 'should handle a netsuite record' do
|
7
|
-
|
8
|
-
|
7
|
+
NetSuite::Configuration.sandbox = true
|
8
|
+
c = NetSuite::Records::Customer.new internal_id: 123
|
9
|
+
url = NetSuiteRails::UrlHelper.netsuite_url(c)
|
9
10
|
|
10
|
-
|
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
|
-
|
15
|
-
|
15
|
+
NetSuite::Configuration.sandbox = true
|
16
|
+
s = StandardRecord.new netsuite_id: 123
|
17
|
+
url = NetSuiteRails::UrlHelper.netsuite_url(s)
|
16
18
|
|
17
|
-
|
19
|
+
expect(url).to eq('https://system.sandbox.netsuite.com/app/common/entity/entity.nl?id=123')
|
18
20
|
end
|
19
21
|
|
20
|
-
|
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
|